Skip to content

sec: persist Functions Plane CRON_SECRET across provisions#16

Merged
valvesss merged 1 commit into
mainfrom
sec/functions-cron-secret-persist
Jul 3, 2026
Merged

sec: persist Functions Plane CRON_SECRET across provisions#16
valvesss merged 1 commit into
mainfrom
sec/functions-cron-secret-persist

Conversation

@valvesss

@valvesss valvesss commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

O que

Persiste o CRON_SECRET do Functions Plane na tabela projects (control migration 0013) em vez de regenerá-lo a cada provision.

Por quê (Svalinn/deepsec — HIGH)

prepareFunctions() gerava randomBytes(24) a cada chamada e nunca armazenava. Todo reconcile/re-provision/update faz docker rm -f + docker run → container novo com secret novo. Consumidores que cachearam o valor antigo (timers systemd, crons de edge function) passavam a ser rejeitados (X-Cron-Secret não bate) → cron silenciosamente quebrado.

Como

  • control/0013_functions_cron_secret.sql: add column if not exists functions_cron_secret text (nullable, aplicada no boot via pnpm bootstrap; idempotente, não-bloqueante).
  • prepareFunctions(): lê a coluna; se NULL, gera+persiste; usa o valor estável. Só gera novo no 1º provision. Mesmo padrão do jwt_secret.

Aceite

  • 2ª chamada de provisionFunctions() no mesmo projeto → mesmo CRON_SECRET (estável entre recriações). ✓
  • 1º provision → gera e persiste. ✓
  • migration idempotente. ✓
  • Nota: um Functions Plane já provisionado sofre 1 rotação única do secret no próximo re-provision (coluna começa NULL) — mesmo efeito que um re-provision tem hoje — e fica estável depois.

Deploy

Não deployar agora. Código+migration acumulam na main; rollout numa janela recriando SÓ o container control-plane (aplica a migration no boot). App hauldr = compose com o Postgres compartilhado, não redeployar de dia.

Card Brokk: a8b1160c-f356-4892-a8f8-7688f1f89181

🤖 Generated with Claude Code

prepareFunctions() minted CRON_SECRET fresh on every call and never stored
it, so any reconcile / re-provision / update (docker rm -f + docker run)
silently rotated it. Cron callers that cached the old value — systemd timers,
edge-function crons — then failed every invocation because the presented
X-Cron-Secret no longer matched the container env. Flagged by Svalinn/deepsec
(HIGH).

Persist it like jwt_secret: add a nullable `functions_cron_secret` column to
`projects` (control migration 0013, add-column-if-not-exists, applied on boot
via `pnpm bootstrap`); prepareFunctions() reads it back and mints a new value
only when the column is NULL (first provision). The secret is now stable
across container replacements.

Note: an already-provisioned Functions Plane gets a one-time secret rotation on
its next re-provision (column starts NULL) — same effect a re-provision has
today — and is stable thereafter.

Card: Brokk a8b1160c (svalinn:hauldr:deepsec:cron-secret-rotation)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@valvesss valvesss merged commit d293470 into main Jul 3, 2026
2 checks passed
@valvesss valvesss deleted the sec/functions-cron-secret-persist branch July 3, 2026 14:57
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