Skip to content

fix(ai-review): BYOK round 2 — key-lifecycle audit, budget separation, per-record salt - #680

Merged
JSONbored merged 1 commit into
mainfrom
fix/byok-round2
Jun 14, 2026
Merged

fix(ai-review): BYOK round 2 — key-lifecycle audit, budget separation, per-record salt#680
JSONbored merged 1 commit into
mainfrom
fix/byok-round2

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Second hardening round from the BYOK security audit (follow-up to #670).

Changes

Not included (deferred with rationale)

Tests

  • Lifecycle audit trail: set → replace → delete recorded with the right actors and no key material; a no-op delete records nothing.
  • BYOK advisory runs with the free budget exhausted (estimatedNeurons === 0).
  • Crypto: v2 round-trip (fresh salt per record) + v1 legacy decryption (constant salt) + wrong-secret rejection.

Verification

typecheck ✅ · test:coverage ✅ (97% branch, 1676 tests) · test:workers ✅ · git diff --check

Closes #671, #672, #676, #677. Part of #525.

…, per-record salt

Second hardening round from the BYOK security audit (follow-up to #670).

- #671 Audit the provider-key lifecycle. upsert/delete now emit an
  `ai_key_change` ai_usage_events row (status set|replace|delete, actor,
  display-only last4 — NEVER any key material), recorded as a non-"ok" status
  so it never counts toward the neuron budget. getRepositoryAiKeyStatus now
  surfaces createdBy + updatedAt so the dashboard can show who set the key and
  when. The DELETE route threads the session actor through.

- #672 Stop counting BYOK advisory spend against the free Workers-AI daily
  neuron budget. The budget now meters only free calls (the consensus pair in
  block mode + the advisory leg when it is NOT BYOK); a BYOK advisory bills the
  maintainer's own account and still runs when the free budget is exhausted.

- #677 Per-record PBKDF2 salt for the key-encryption envelope (v2). encryptSecret
  now generates a fresh random salt per record and stores it beside the IV;
  decryptSecret falls back to the legacy constant salt when no per-record salt
  is present, so existing v1 rows keep decrypting. Migration 0030 adds the
  nullable `salt` column.

- #676 Clarify that repository_ai_keys.key_version is the crypto-envelope
  version (1 = legacy constant-salt, 2 = per-record salt), not a rotation
  counter.

Tests: lifecycle audit trail (set→replace→delete, no key material, no-op delete);
BYOK advisory runs with the free budget exhausted; v2 round-trip + v1 legacy
decryption; non-numeric env clamp. Branch coverage 97%.

Closes #671, #672, #676, #677. Part of #525.
@ghost

ghost commented Jun 14, 2026

Copy link
Copy Markdown

Note

Gittensory Gate skipped

PR closed before full evaluation. No late first comment was created.

Signal Result Evidence Action
Gate result ⚠️ Skipped #680 is no longer open. No action.

💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.

@ghost

ghost commented Jun 14, 2026

Copy link
Copy Markdown

gittensory · advisory review

Reviewed 9 changed file(s) — two independent AI reviewers.

Suggested action: 🛠️ Request changes. (reviewers split: request changes / merge) Address the suggestions below before merging.

Reviewer A · gpt-oss-120b — recommends 🛠️ request changes
This PR adds per‑record salts, key‑versioning, and lifecycle auditing for BYOK keys, and adjusts AI budget accounting. However, it introduces new columns (createdBy, updatedAt) in the repository key status and upserts without adding those columns to the database schema, which will cause runtime failures. It also adds an unnecessary node_modules placeholder.

Suggestions

  • Add createdBy (TEXT, nullable) and updatedAt (TEXT, nullable) columns to the repository_ai_keys table via a migration and update src/db/schema.ts accordingly.
  • Update the migration to set sensible defaults for the new columns (e.g., CURRENT_TIMESTAMP for updatedAt).
  • Remove the stray node_modules placeholder file from the repo.
  • Ensure any existing rows are back‑filled with appropriate values or handled gracefully during the migration.
  • Consider whether exposing createdBy in the public RepositoryAiKeyStatus is acceptable; if not, omit it from the public type.

Worth double-checking

  • Database schema mismatch leading to insert/update errors at runtime.
  • Potential exposure of maintainer usernames if createdBy is publicly returned.
  • Missing back‑fill of existing rows could cause nullability issues or break queries.

Reviewer B · nemotron-3-120b-a12b — recommends ✅ merge
This PR enhances BYOK key management with per-record salt for encryption (defense-in-depth), adds lifecycle audit logging (set/replace/delete) that excludes key material, and separates BYOK advisory calls from the free neuron budget. Changes are well-tested and maintain the public/private boundary by never exposing secrets in audit logs.

Suggestions

  • Consider adding a comment in recordAiKeyChange clarifying why status uses the action string (non-'ok' to exclude from budget).
  • In decryptSecret, the salt parameter is optional but could be made explicit with a default null for clarity.
  • Ensure the migration note about v1/v2 compatibility is kept up-to-date if envelope versions evolve.

Worth double-checking

  • Backward compatibility: existing v1 rows (salt=NULL) must continue decrypting with the legacy constant salt - verified in tests.
  • Audit log size: key lifecycle events are infrequent, but monitor ai_usage_events growth if key rotation becomes frequent.
  • Type safety: ensure the new salt column's TEXT type aligns with base64 storage (no length issues).

@ghost ghost added the gittensory-review label Jun 14, 2026

@superagent-security superagent-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 1 security concern(s).

Comment thread node_modules
@@ -0,0 +1 @@
/Users/shadowbook/Documents/gittensory/node_modules No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Symlink node_modules pointing to absolute local path committed to repository

Committed a node_modules symlink to an absolute local filesystem path.

Remove the symlink from git and ensure .gitignore blocks node_modules.

AI prompt
Check if this security scanner issue is valid. If so, understand the root cause and fix it. If appropriate, update or add tests. Keep the change focused and preserve intended behavior.

<file name="node_modules">
<violation number="1" location="node_modules:1">
<priority>P2</priority>
<title>Symlink `node_modules` pointing to absolute local path committed to repository</title>
<evidence>A symlink named `node_modules` was added to the repository root, pointing to the absolute local path `/Users/shadowbook/Documents/gittensory/node_modules`. This reveals local filesystem structure and can interfere with package installation by redirecting dependency resolution to an external directory.</evidence>
<recommendation>Remove the `node_modules` symlink from version control and add `node_modules` to `.gitignore` if not already present. Verify that no build or deployment pipeline follows this symlink unexpectedly.</recommendation>
</violation>
</file>

@superagent-security superagent-security Bot added the pr:flagged PR flagged for review by security analysis. label Jun 14, 2026
@JSONbored
JSONbored merged commit 880b08d into main Jun 14, 2026
9 of 10 checks passed
@JSONbored
JSONbored deleted the fix/byok-round2 branch June 14, 2026 02:01
@github-project-automation github-project-automation Bot moved this from Todo to Done in gittensory - v1 roadmap Jun 14, 2026
JSONbored added a commit that referenced this pull request Jun 14, 2026
#681)

Final round of the BYOK security-audit follow-ups (after #670, #680).

- #673 Stand up a UI test runner (vitest + @testing-library/react + jsdom) in
  the gittensory-ui workspace and add the first component tests: the
  MaintainerPanel role gate (non-maintainer → "Maintainer access required",
  BYOK field never mounts; maintainer → dashboard) and AiReviewSettings (key
  field is write-only/password and never hydrates a stored key; provider/key
  mismatch is rejected client-side without a request; a valid key posts then
  clears). Wired `ui:test` into the CI "UI check" step.

- #678 AI review panel UX + a11y: an initial loading state, an empty-state hint
  when no registered repos are detected, role="status" aria-live on the result
  banner, and aria-busy on the action buttons.

- #675 Maintainer-facing BYOK documentation (docs/maintainer-byok-ai-review.md):
  how the advisory vs consensus layers work, that BYOK calls the provider
  directly and bills the maintainer (consensus always free), encrypted/write-only
  key handling, and dashboard vs .gittensory.yml config. Kept out of
  docs.github-app.tsx to avoid conflicting with the open onboarding-docs PR #648.

Verified: ui:typecheck, ui:lint, ui:test (6 tests), ui:build, ui:openapi:check,
actionlint, npm audit (0 vulns).

Closes #673, #675, #678. Part of #525.
JSONbored added a commit that referenced this pull request Jun 14, 2026
A `node_modules` symlink pointing at its own absolute path was
accidentally committed in #680, shadowing the real install with a
circular link ("too many levels of symbolic links") that breaks
node_modules/.bin/* resolution on fresh checkouts. CI is unaffected
because `npm ci` removes node_modules before installing, but local
tooling (tsc, vitest) fails until the symlink is cleared.

The directory is already covered by .gitignore (node_modules/), so
untracking the symlink blob restores correct behavior with no
runtime or deploy impact.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:flagged PR flagged for review by security analysis.

Projects

No open projects
Status: Done

Development

Successfully merging this pull request may close these issues.

BYOK: audit-log provider-key lifecycle (set/replace/delete) + surface createdBy/updatedAt

1 participant