fix(ai-review): BYOK round 2 — key-lifecycle audit, budget separation, per-record salt - #680
Conversation
…, 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.
|
Note Gittensory Gate skippedPR closed before full evaluation. No late first comment was created.
💰 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. |
|
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 · Suggestions
Worth double-checking
Reviewer B · Suggestions
Worth double-checking
|
| @@ -0,0 +1 @@ | |||
| /Users/shadowbook/Documents/gittensory/node_modules No newline at end of file | |||
There was a problem hiding this comment.
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>
#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.
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.
Second hardening round from the BYOK security audit (follow-up to #670).
Changes
upsert/deletenow emit anai_key_changeai_usage_eventsrow (status=set/replace/delete, the actor, and the display-onlylast4— never any key material), written with a non-okstatus so it never counts toward the neuron budget.getRepositoryAiKeyStatusnow surfacescreatedBy+updatedAtso the dashboard can show who set the key and when. TheDELETEroute threads the session actor through.encryptSecretgenerates a fresh random salt per record and stores it beside the IV;decryptSecretfalls back to the legacy constant salt when no per-record salt is present, so existing v1 rows keep decrypting. Migration0030adds the nullablesaltcolumn.key_versionas the crypto-envelope version (1 = legacy constant-salt, 2 = per-record salt), not a rotation counter.Not included (deferred with rationale)
ai-keywrites to owners) is intentionally not in this PR. Code analysis shows the "owned" access axis is installation-account-based, so naively restricting to it would lock out legitimate org-repo maintainers (org repos surface noOWNERauthor-association). The correct fix resolves real GitHub push permission via the installation (an API call) — a larger, focused change. Tracked in BYOK: tighten ai-key writes to repo owners (not org MEMBER / read COLLABORATOR) #674.Tests
set → replace → deleterecorded with the right actors and no key material; a no-op delete records nothing.estimatedNeurons === 0).Verification
typecheck✅ ·test:coverage✅ (97% branch, 1676 tests) ·test:workers✅ ·git diff --check✅Closes #671, #672, #676, #677. Part of #525.