feat(secrets): migrate Penpot token from plaintext file to secret table #826

Merged
charles merged 1 commit from feat/agent-tokens-to-secrets into main 2026-05-04 13:09:56 +00:00
Collaborator

Closes one of three slices of #823. Branch name says agent-tokens for historical reasons (initial scope) — actual scope reduced to Penpot only after discovering TOK-1/TOK-2 already migrated per-agent Forgejo tokens to the secret table under the FORGEJO_TOKEN_<TYPE> name pattern.

Summary

The Penpot API token moves from ~/.config/claude-hooks/penpot-token (plaintext, mode 0600) to the existing AES-256-GCM-encrypted secret table under name PENPOT_TOKEN. Idempotent migration 014 ports any existing file into the DB on first boot and renames the source to .penpot-token.migrated.bak (matching the TOK-1 pattern; not unlink so a panicked operator can recover).

The boot-time read in main.ts now resolves the secret via getSecretRowByName("PENPOT_TOKEN") + decryptSync and feeds setPenpotMcpConfig. The container / agent-env-sync render layer already substitutes ${SECRET:PENPOT_TOKEN} placeholders, so the in-container designer / design-reviewer flows pick up the same value with no further code change.

Plus dead-reference cleanup: comments and tests still pointing at the removed ~/.config/claude-hooks/tokens/<agent> files now match reality (TOK-1 migrated those long ago).

Changes

  • apps/server/src/infrastructure/database/migrations/014-migrate-penpot-token-to-secret.ts (new).
  • apps/server/src/infrastructure/database/migrations/014-migrate-penpot-token-to-secret.test.ts (new).
  • apps/server/src/infrastructure/database/db.ts — wired migration 014 into ensureSchema().
  • apps/server/src/main.ts — boot path swap (getSecretRowByName("PENPOT_TOKEN") + decryptSync with boot:penpot accessor).
  • apps/server/src/infrastructure/container/container.ts — comment cleanup; dropped dead hostTokenPath helper.
  • apps/server/src/infrastructure/container/container.test.ts — dropped the hostTokenPath describe.
  • apps/server/src/domain/agent/agent-runner.test.ts — removed the tokens/boss denied-list entry.
  • justfile — added one-line "lifted into secret table" comments above the operator-fallback bash in labels-bootstrap and deps-backfill. Kept the actual fallback since it's still useful for fresh manual token drops by the operator (post TOK-1 the migrated tokens/.<agent>.migrated.bak files don't match the ls -1 glob so the fallback is silently dead but not wrong).
  • docs/credentials.md — added a migrated-secrets table covering both FORGEJO_TOKEN_<TYPE> (TOK-1) and the new PENPOT_TOKEN.

Test plan

  • 5 new migration tests pass
  • just qa clean from project root
  • Run on a host with ~/.config/claude-hooks/penpot-token and confirm row + .bak rename
  • Confirm host-mode Penpot MCP still works end-to-end after restart (designer agent should still have token access)
  • Confirm agent-env-sync ${SECRET:PENPOT_TOKEN} substitution still resolves (in-container path)

🤖 Generated with Claude Code

Closes one of three slices of #823. Branch name says `agent-tokens` for historical reasons (initial scope) — actual scope reduced to **Penpot only** after discovering TOK-1/TOK-2 already migrated per-agent Forgejo tokens to the `secret` table under the `FORGEJO_TOKEN_<TYPE>` name pattern. ## Summary The Penpot API token moves from `~/.config/claude-hooks/penpot-token` (plaintext, mode 0600) to the existing AES-256-GCM-encrypted `secret` table under name `PENPOT_TOKEN`. Idempotent migration `014` ports any existing file into the DB on first boot and renames the source to `.penpot-token.migrated.bak` (matching the TOK-1 pattern; not `unlink` so a panicked operator can recover). The boot-time read in `main.ts` now resolves the secret via `getSecretRowByName("PENPOT_TOKEN") + decryptSync` and feeds `setPenpotMcpConfig`. The container / agent-env-sync render layer already substitutes `${SECRET:PENPOT_TOKEN}` placeholders, so the in-container designer / design-reviewer flows pick up the same value with no further code change. Plus dead-reference cleanup: comments and tests still pointing at the removed `~/.config/claude-hooks/tokens/<agent>` files now match reality (TOK-1 migrated those long ago). ## Changes - `apps/server/src/infrastructure/database/migrations/014-migrate-penpot-token-to-secret.ts` (new). - `apps/server/src/infrastructure/database/migrations/014-migrate-penpot-token-to-secret.test.ts` (new). - `apps/server/src/infrastructure/database/db.ts` — wired migration 014 into `ensureSchema()`. - `apps/server/src/main.ts` — boot path swap (`getSecretRowByName("PENPOT_TOKEN") + decryptSync` with `boot:penpot` accessor). - `apps/server/src/infrastructure/container/container.ts` — comment cleanup; dropped dead `hostTokenPath` helper. - `apps/server/src/infrastructure/container/container.test.ts` — dropped the `hostTokenPath` describe. - `apps/server/src/domain/agent/agent-runner.test.ts` — removed the `tokens/boss` denied-list entry. - `justfile` — added one-line "lifted into secret table" comments above the operator-fallback bash in `labels-bootstrap` and `deps-backfill`. **Kept the actual fallback** since it's still useful for fresh manual token drops by the operator (post TOK-1 the migrated `tokens/.<agent>.migrated.bak` files don't match the `ls -1` glob so the fallback is silently dead but not wrong). - `docs/credentials.md` — added a migrated-secrets table covering both `FORGEJO_TOKEN_<TYPE>` (TOK-1) and the new `PENPOT_TOKEN`. ## Test plan - [x] 5 new migration tests pass - [x] `just qa` clean from project root - [ ] Run on a host with `~/.config/claude-hooks/penpot-token` and confirm row + `.bak` rename - [ ] Confirm host-mode Penpot MCP still works end-to-end after restart (designer agent should still have token access) - [ ] Confirm agent-env-sync `${SECRET:PENPOT_TOKEN}` substitution still resolves (in-container path) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(secrets): migrate Penpot token from plaintext file to secret table
Some checks failed
qa / dockerfile (pull_request) Successful in 20s
qa / qa-1 (pull_request) Has been cancelled
qa / qa (pull_request) Has been cancelled
1ceefd88e2
Closes #823 (Penpot slice; TOK-1/TOK-2 already covered per-agent forge tokens).

- Migration 014 — first-boot sweep that lifts `~/.config/claude-hooks/penpot-token`
  into the SC-6 `secret` table at `PENPOT_TOKEN`. Mirrors the TOK-1 (#757)
  pattern: encrypt under the master key, insertSecretRow, rename source to
  `.penpot-token.migrated.bak`, idempotent on re-runs.
- `main.ts` boot path now resolves the Penpot token via
  `getSecretRowByName("PENPOT_TOKEN")` + `decryptSync` (one audit row per
  boot tagged `boot:penpot`). Missing row falls through to the same warning
  the previous missing-file path emitted.
- Cleaned dead `tokens/<agent>` references: container.ts module comment,
  container.test.ts hostTokenPath block (function dropped, unused post
  TOK-1/TOK-2), agent-runner.test.ts touchesHostSecret denied-list (the
  webhook-secret line still exercises the same regex).
- justfile: `labels-bootstrap` / `deps-backfill` operator-fallback comments
  point at the secret-store source of truth without breaking the
  one-off-CLI fallback path.
- docs/credentials.md: added a migrated-secrets table covering both TOK-1
  agent tokens and the new PENPOT_TOKEN row.
charles force-pushed feat/agent-tokens-to-secrets from 1ceefd88e2
Some checks failed
qa / dockerfile (pull_request) Successful in 20s
qa / qa-1 (pull_request) Has been cancelled
qa / qa (pull_request) Has been cancelled
to 5793451547
Some checks failed
qa / dockerfile (pull_request) Successful in 10s
qa / qa-1 (pull_request) Has been cancelled
qa / qa (pull_request) Has been cancelled
2026-05-04 12:37:09 +00:00
Compare
charles force-pushed feat/agent-tokens-to-secrets from 5793451547
Some checks failed
qa / dockerfile (pull_request) Successful in 10s
qa / qa-1 (pull_request) Has been cancelled
qa / qa (pull_request) Has been cancelled
to 3ecd2fb727
All checks were successful
qa / dockerfile (pull_request) Successful in 10s
qa / qa-1 (pull_request) Successful in 4m23s
qa / qa (pull_request) Successful in 0s
2026-05-04 12:53:18 +00:00
Compare
charles force-pushed feat/agent-tokens-to-secrets from 3ecd2fb727
All checks were successful
qa / dockerfile (pull_request) Successful in 10s
qa / qa-1 (pull_request) Successful in 4m23s
qa / qa (pull_request) Successful in 0s
to 76ba3ad0c6
All checks were successful
qa / dockerfile (pull_request) Successful in 23s
qa / qa-1 (pull_request) Successful in 4m6s
qa / qa (pull_request) Successful in 0s
2026-05-04 12:58:05 +00:00
Compare
reviewer approved these changes 2026-05-04 13:05:58 +00:00
reviewer left a comment

CI green. Migration 014 correctly guards on row-already-present, file-missing, file-empty, and encrypt-failed; renames to .bak best-effort. sessions.ts SQLite port is clean — LIKE escaping in dropAllForIssue is the right fix. Test coverage is thorough across both migrations.

CI green. Migration 014 correctly guards on row-already-present, file-missing, file-empty, and encrypt-failed; renames to `.bak` best-effort. `sessions.ts` SQLite port is clean — LIKE escaping in `dropAllForIssue` is the right fix. Test coverage is thorough across both migrations.
charles force-pushed feat/agent-tokens-to-secrets from 76ba3ad0c6
All checks were successful
qa / dockerfile (pull_request) Successful in 23s
qa / qa-1 (pull_request) Successful in 4m6s
qa / qa (pull_request) Successful in 0s
to 76c25c6155
All checks were successful
qa / dockerfile (pull_request) Successful in 22s
qa / qa-1 (pull_request) Successful in 3m40s
qa / qa (pull_request) Successful in 0s
2026-05-04 13:05:58 +00:00
Compare
Collaborator

PR has conflicts — please rebase on main before merging.

PR has conflicts — please rebase on main before merging.
charles deleted branch feat/agent-tokens-to-secrets 2026-05-04 13:09:56 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!826
No description provided.