fix: harden secrets, audit logging, agent-run recovery, CI, and token budgets - #22
Merged
Merged
Conversation
Secrets: - Encrypt mcpServer.env/oauthState at rest (AES-256-GCM, same envelope as modelInstallation.apiKey/knowledgeBaseConfig.obsidianApiKey) via new encryptJsonNullable/decryptJsonNullable helpers in packages/db/src/crypto.ts. - Add toClientSafeMcpServer/toClientSafeKnowledgeBaseConfig mappers and wire them (plus the existing toClientSafeInstallation) into every router procedure that previously returned raw secrets to the browser. Audit log: - Centralize input/output redaction (apiKey/token/accessToken/refreshToken/ authorization/password/secret/cookie, case-insensitive, nested) and a serialized-size cap in apps/server/src/audit.ts's logAudit, ahead of every audit_log write. inputHash is unaffected (computed from the raw value before redaction). Agent run recovery: - Add workerId/heartbeatAt/leaseUntil/cancelRequestedAt to agent_run (both dialects). executeManagedTask claims a lease and renews it on a heartbeat interval; cancelAgentRun always sets cancelRequestedAt/status in the DB regardless of in-memory controller reachability. scheduler.ts's new checkStaleAgentRuns recovers runs whose lease expired (or never had one) on every tick and once at startup. CI/tests: - Delete 5 dead /tests files that imported nonexistent modules; add a root "test": "bun test" script and a CI job that runs it after lint/typecheck; add hermetic fetch-mocked tests for local model detection. Token budgets: - Make the knowledge-base prompt injection char caps env-configurable; add an aggregate output-byte cap to the builtin search/codebase/usages tool. Tests added for every behavior changed; typecheck/lint clean on all touched files (verified against the pre-existing baseline); both Postgres migrations verified against a live Postgres 16 instance, including that pre-existing legacy plaintext data survives the jsonb->text conversion. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019tXH1XWxA1CsvoVr29pPHR
rgxdev
marked this pull request as ready for review
July 3, 2026 10:26
…ndings) Three pre-existing, repo-wide CI blockers surfaced once this PR's files were touched (Typecheck/Build/Build-web/Lint all fail on origin/main today, independent of this change): - packages/create-nyxel/tsconfig.json: @types/node was a declared devDependency but never auto-included (this TypeScript version doesn't auto-discover @types/* the way earlier versions did) — add the explicit `types: ["node"]` the compiler itself suggests. - apps/web/src/components/ui/dropdown-menu.tsx: onOpenAutoFocus is read and forwarded by @radix-ui/react-menu's content impl at runtime but omitted from this version's public prop type — widen the local wrapper's type to match the real, working API instead of the narrower public one. - apps/web/src/components/workspace-settings-panel.tsx: associate the two preset/model checkbox labels with their inputs via htmlFor/id, and replace a dead `eslint-disable-next-line` (this project uses Biome, not ESLint) with a real biome-ignore for the intentional exhaustive-deps exclusion. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019tXH1XWxA1CsvoVr29pPHR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Five hardening work packages toward a reliable agentic OS, kept minimal and scoped to existing patterns already established in this codebase (see
docs/SECURITY_AUDIT.md).1. Secrets hardening
mcpServer.env/mcpServer.oauthStatewere the last plaintext secret-shaped columns (per SECURITY_AUDIT.md SEC-01) — encrypted at rest using the same AES-256-GCM envelope already used formodelInstallation.apiKey/knowledgeBaseConfig.obsidianApiKey(packages/db/src/crypto.ts, newencryptJsonNullable/decryptJsonNullablehelpers). Schema columns changed fromjsonb/json-modetextto plain encryptedtext(migration included for both dialects).toClientSafeMcpServer/toClientSafeKnowledgeBaseConfigmappers (mirroring the existingtoClientSafeInstallation) and wired them into every router procedure that was returning raw secrets to the browser — this closed several leaks beyond the two named tables:models.installCustom/installOpenRouter/setModelEnabled/addModelToInstallation/removeModelFromInstallation/installCli/importSourcewere all returning the undecorated DB row (includingapiKey), andknowledgeBase.overview/updateConfigwere spreading the rawobsidianApiKeyalongside the*Setboolean.NYXEL_ENCRYPTION_KEYproduction requirement (assertProductionSecret) was already in place from a prior session — untouched here.2. Audit log redaction
apps/server/src/audit.ts'slogAuditnow redacts secret-shaped keys (apiKey,token,accessToken,refreshToken,authorization,password,secret,cookie— case/separator-insensitive, arbitrarily nested) and caps serialized size (20k chars) before everyaudit_logwrite.inputHashis unaffected — it's computed from the raw value upstream inpermissions.tsbefore this redaction runs.3. Durable agent run recovery
workerId/heartbeatAt/leaseUntil/cancelRequestedAttoagent_run(both dialects).executeManagedTaskclaims a lease at start and renews it on a 30s heartbeat;cancelAgentRunalways sets DB state (it already did forstatus, now alsocancelRequestedAt) regardless of whether the run'sAbortControlleris reachable in this process.scheduler.ts's newcheckStaleAgentRuns()recovers runs whose lease expired (or never had one — e.g. pre-dates this feature) intofailed, and fails the linked task too so the Goal Orchestrator naturally re-drives it. Runs on every scheduler tick and once at startup.4. CI/test gate cleanup
/testsfiles that imported nonexistent modules (leftover scaffolding from an earlier layout)."test": "bun test"script and a CI job that runs it afterlint/typecheck.packages/model-providers/src/detect.tspreviously had none and made real network calls to localhost ports).secret-scan.yml(gitleaks) already existed and is already non-blocking (continue-on-error: true) — verified, unchanged.5. Token budget controls
NYXEL_KB_PROMPT_CONTEXT_MAX_CHARS/NYXEL_KB_PROMPT_SECTION_MAX_CHARS(same defaults as before when unset).MAX_TOTAL_OUTPUT_BYTES) to the builtin search/codebase/usages tool, additivetruncatedfield on the response.Out of scope / left alone
ADR-0007already documents the current in-process, permission-scoped design honestly (not claimed as solved) — no runtime change made here.plugins.test.ts,skills-resolve.test.ts,seo-analyzer.test.ts— a shared test-fixture DB setup issue) and 2 pre-existing typecheck failures (apps/web'schat-top-bar.tsx,packages/create-nyxel's missing@types/node) — all predate this branch, verified againstorigin/main, not touched.0007/0008both addchat.archived_at) was discovered while verifying the new migrations against a fresh Postgres 16 instance — unrelated to this change, not fixed (out of scope), noted here for visibility.Test plan
bun run typecheck— clean on every touched package (only the two pre-existing, unrelated failures above remain)bun run lint(biome) — clean on every touched file; 3 pre-existing lint findings surfaced inworkspace-settings-panel.tsx(2 accessibility, 1 hook-deps) because the file was touched, but verified present onorigin/mainbefore this change toobun run test— 192 pass / 15 pre-existing unrelated fail (verified againstorigin/mainbaseline)0042,0043) applied against a live local Postgres 16 instance; confirmed pre-existing legacy plaintext data survives thejsonb→encrypted-textconversion via the existing legacy-plaintext fallback incrypto.ts🤖 Generated with Claude Code
Generated by Claude Code