Permissions

0.4 NEW — capability-based allowlist для host-импортов. Плагин из недоверенного источника не сможет делать произвольные HTTP, читать чужой кеш или подписывать прокси-URL.

Зачем

Когда permissions не задан в манифесте, плагину разрешено всё (legacy/permissive default — для приставочных JS-модулей и встроенных плагинов из wasm_modules/). Как только админ добавляет хоть одно правило — режим переключается на explicit allowlist: всё, что не разрешено, запрещено.

Это критически важно для hub.alcopa.cc — маркетплейс плагинов, где автор плагина не равен админу lampac-go.

Грамматика

{
  "permissions": [
    "http:*",                      // любой HTTP/HTTPS
    "http:example.com",            // только этот хост (включая порт)
    "http:*.example.com",          // wildcard subdomain — НЕ матчит сам apex
    "http:api.example.com",        // exact subdomain
    "cache",                       // host_cache_get / host_cache_set
    "proxy",                       // host_proxy_url (signed /proxy/ URL)
    "log",                         // host_log — всегда разрешён, для документации
    "config"                       // host_config — всегда разрешён
  ]
}
Правило Что разрешает
http:* Любой HTTP-запрос через host_http
http:example.com Точное совпадение Hostname()
http:*.foo.com a.foo.com, a.b.foo.com — но не foo.com
cache host_cache_get + host_cache_set
proxy host_proxy_url
log, config Всегда разрешены — указывайте для документации

Hostname() нечувствителен к регистру и игнорирует порт: правило http:example.com пропустит https://example.com:8443/x.

Пример: balanced плагин

{
  "id": "my_balancer",
  "permissions": [
    "http:api.example.com",
    "http:*.cdn.example.com",
    "cache",
    "proxy"
  ]
}

Плагин может:

  • ходить только на API-домен и CDN с любым subdomain,
  • кешировать ответы,
  • подписывать стрим-URL через /proxy/.

Всё остальное (другие домены, отправка email через host_http куда попало) — заблокировано на уровне runtime.

Пример: пассивный middleware

{
  "id": "voice_dedup",
  "target": "middleware",
  "upstreams": ["collaps", "filmix"],
  "permissions": ["log"]
}

Middleware читает upstream-ответы из inv.Config.__upstreams — это не host_http. Сам в сеть не ходит, кеш не нужен. Минимальные права.

Пример: client-target ratings widget

{
  "id": "ratings",
  "target": "client",
  "permissions": ["log"]
}

Client-плагины не используют host_http (ходить через сеть в Lampa-плагине = fetch() через JS, не через WASM). Permissions для client-target проверяются только если плагин both и часть запросов идёт через server-сторону.

Что происходит при отказе

host_http с заблокированным URL:

{ "error": "permission denied: host evil.com not in allowlist" }

Плагин получает это как ошибку HTTP-вызова, а не аборт — может среагировать (например, fallback на разрешённый источник).

host_proxy_url с отсутствующим proxy:

{ "error": "permission denied: proxy not granted" }

host_cache_get/set без cache:

  • Get всегда возвращает 0 (cache miss),
  • Set тихо ничего не делает.

Это сделано осознанно — плагин не может отличить “кеш отключён” от “key не найден”, что не даёт ему oracle по защите.

Production checklist для маркетплейса

При публикации плагина на hub.alcopa.cc:

  • permissions явно перечислен — нет http:* без обоснования.
  • Если плагин ходит на 2-3 домена — перечисли поимённо.
  • cache указан, только если плагин действительно кеширует.
  • proxy указан, только если плагин выдаёт стрим-URL.
  • Plugin reviewer проверяет, что список совпадает с поведением.

Почему не WASI capabilities

WASI имеет свою модель capability (preopens, fd-permissions). Мы её не используем потому что:

  • наш host API — не файлы, а host_http / host_cache_* / host_proxy_url,
  • WASI capabilities не покрывают допустимые HTTP-домены (только сам факт наличия сети).

Текущая модель — application-level capabilities, проверяемые в Go перед каждым host-вызовом.

Roadmap

  • Глобальная политика “запрещать http:* для не-builtin плагинов”
  • Time-budget per request (сколько мс плагин может бегать) — сейчас защищает только timeout context’а, а не CPU
  • Memory cap per module (linear memory size limit) — wazero это умеет, нужно прокинуть в Manager