Skip to content

feat(connectors): secure credential vault + dashboard UI (#116)#117

Merged
servathadi merged 1 commit into
mainfrom
feat/mupot-116-connectors
Jun 11, 2026
Merged

feat(connectors): secure credential vault + dashboard UI (#116)#117
servathadi merged 1 commit into
mainfrom
feat/mupot-116-connectors

Conversation

@servathadi

Copy link
Copy Markdown
Contributor

Summary

  • D1 migration 0023_connectors: connectors table (AES-GCM ciphertext, never plaintext) + connector_audit append-only log
  • Crypto module (src/connectors/crypto.ts): AES-GCM-256 + HKDF-SHA256, per-connector salt (connector UUID), domain-separated info string per type. Fail-closed on bad/absent key.
  • Service (src/connectors/service.ts): addConnector / rotateConnector / revokeConnector / listConnectors / resolveConnector. encrypted_secret is SELECT-ed in exactly one place (resolveConnector); all other queries use explicit safe-column lists.
  • Dashboard routes (src/dashboard/index.ts): GET /admin/connectors, POST /admin/connectors, POST /:id/rotate, POST /:id/revoke — all isAdmin-gated, CSRF-covered, tenant-scoped.
  • Telegram specifics: allowed_chats stored as JSON in meta; telegramAllowedChats() + isTelegramChatAllowed() helpers.
  • 32 new tests covering all 5 acceptance criteria + SQL invariant verification.

Encryption key situation

CONNECTOR_MASTER_KEY is a deploy prerequisite — not in wrangler.toml.

openssl rand -hex 32
npx wrangler secret put CONNECTOR_MASTER_KEY

Fail-closed: absent key = addConnector() throws, resolveConnector() returns null.

Write-only invariant — GREP VERIFIED

encrypted_secret appears in src/connectors/service.ts only in:

  1. INSERT write path (addConnector)
  2. UPDATE SET write path (rotateConnector)
  3. SELECT in resolveConnector ONLY — the single decrypt path

listConnectors() and rotateConnector() load queries use explicit safe-column lists with no encrypted_secret. Verified by test write-only SQL invariant.

Test plan

  • npm run typecheck — clean
  • 843/843 pre-existing tests pass (surface-caps.test.ts is untracked pre-existing WIP, excluded)
  • 32/32 connector tests pass
  • AC#1: encrypt-at-rest + list never exposes encrypted_secret
  • AC#2: resolveConnector scoped correctly; null on wrong type/absent key
  • AC#4: cross-tenant scope_id blocked
  • AC#5: revoke → resolve returns null
  • AC#6: rotate → new ciphertext, new hint

🤖 Generated with Claude Code

…t + dashboard UI (#116)

Deliverables:
- D1 migration 0023: connectors + connector_audit tables (write-only by design)
- AES-GCM-256+HKDF per-connector crypto (CONNECTOR_MASTER_KEY env secret)
- Service: addConnector / rotateConnector / revokeConnector / listConnectors / resolveConnector
- Dashboard routes: GET+POST /admin/connectors, POST /:id/rotate, POST /:id/revoke
- Telegram: allowed_chats JSON storage + isTelegramChatAllowed helper
- 32 new tests (all AC covered); 843/843 pre-existing tests clean

Security:
- encrypted_secret never SELECT-ed outside resolveConnector() (grep-verified)
- Write-only: list/rotate queries use explicit safe-column list (no encrypted_secret)
- CONNECTOR_MASTER_KEY fail-closed: absent = no encrypt/decrypt, addConnector throws
- isAdmin gate on all /admin/connectors routes (route layer, CSRF-covered)
- Tenant-scoped: scope_id validated against this pot's D1 only

Deploy prereq: `npx wrangler secret put CONNECTOR_MASTER_KEY` (openssl rand -hex 32)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@servathadi servathadi merged commit 8beb452 into main Jun 11, 2026
5 checks passed
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