Skip to content

sec: enforce Skuld callback host allowlist at enqueue time#15

Merged
valvesss merged 1 commit into
mainfrom
sec/skuld-enqueue-allowlist
Jul 3, 2026
Merged

sec: enforce Skuld callback host allowlist at enqueue time#15
valvesss merged 1 commit into
mainfrom
sec/skuld-enqueue-allowlist

Conversation

@valvesss

@valvesss valvesss commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

O que

Valida o host da callback URL contra a allowlist no enqueue (POST /v1/jobs/enqueue), não só na entrega pelo worker.

Por quê (Svalinn/deepsec — SSRF, HIGH)

O endpoint só checava typeof url === 'string' e enfileirava. A única defesa era o worker (worker/src/jobs/app-callback.ts), que rejeita host fora de SKULD_HOST_ALLOWLIST na entrega — tarde, após retries. Uma app com a SKULD_ENQUEUE_KEY (chave escopada, distribuída às instâncias) podia enfileirar callback para host interno.

Análise

  • O worker já mitigava o caminho crítico do SSRF (allowlist "empty = refuse all"). Este PR é defesa em profundidade + fail-fast no control-plane, que é o que o finding pede ("don't rely on the worker to enforce what the control plane should validate at enqueue time").
  • Allowlist de host, não bloqueio de faixa de IP: callbacks legítimos apontam para hosts internos (containers → IPs privados), então bloquear RFC1918 quebraria a feature.
  • Reusa a mesma SKULD_HOST_ALLOWLIST do worker → 1 linha de config cobre as duas camadas. Já setada no container do control-plane em prod (skuld-sink:8080,maglink.coldcodelabs.com), então o fail-closed não regride enqueues legítimos.

Aceite (verificado por teste de lógica, 8/8)

  • host fora da allowlist → 400 (não vai pra fila) ✓
  • host na allowlist → segue ✓
  • allowlist vazia → rejeita tudo (fail closed) ✓
  • URL inválida / vazia → 400 ✓
  • worker mantém sua checagem (duas camadas) ✓

Card Brokk: 642b42ba-069f-41bf-84d6-5d92c08f0d53 (svalinn:hauldr:deepsec:ssrf)

🤖 Generated with Claude Code

POST /v1/jobs/enqueue only checked that `url` was a non-empty string and
queued it; the worker (worker/src/jobs/app-callback.ts) was the sole place
enforcing SKULD_HOST_ALLOWLIST. An app holding the scoped SKULD_ENQUEUE_KEY
could enqueue a callback to any host and only get rejected late, at worker
delivery, after retries — poor DX and defense-in-depth gap flagged by the
Svalinn/deepsec scan (SSRF, HIGH).

Validate the callback host against the same allowlist at enqueue time:
parse `new URL(url)`, reject (400) any host not on config.skuldHostAllowlist.
Fail closed — an empty allowlist refuses everything. Mirrors the worker check
and reads the same SKULD_HOST_ALLOWLIST var, so one config line covers both
layers (already set on the control-plane container in prod). Host-allowlist,
not IP-range blocking: legit callbacks target internal container hosts, so
blocking RFC1918 would break the feature.

Card: Brokk 642b42ba (svalinn:hauldr:deepsec:ssrf)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@valvesss valvesss merged commit 6be7ee5 into main Jul 3, 2026
3 checks passed
@valvesss valvesss deleted the sec/skuld-enqueue-allowlist branch July 3, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant