feat(api): provision a per-org session-key salt - #431
Closed
JeremyFunk wants to merge 1 commit into
Closed
Conversation
JeremyFunk
force-pushed
the
ai/01-registry-artifact
branch
from
August 12, 2026 20:01
f3ea797 to
d6c7d9b
Compare
Adds `session_salt_{ciphertext,iv,tag}` to `org_ingest_keys` (migration
0040) and `OrgIngestKeysService.getSessionSalt`, which lazily provisions
and decrypts a per-org secret using the same AES-256-GCM envelope as the
private ingest key.
The salt exists so AI session keys can be stored as
`cityHash64(concat(salt, '\0', value))`: the raw key never reaches the
warehouse and hashes cannot be correlated across orgs. The columns are
nullable only because existing rows predate them — a null means "not
provisioned yet" and is backfilled on first read. There is deliberately no
reroll path: rotating the salt changes every hash and would break
SessionsApprox HLL continuity across the rotation boundary.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
JeremyFunk
force-pushed
the
ai/01-registry-artifact
branch
from
August 12, 2026 20:04
d6c7d9b to
1860da6
Compare
JeremyFunk
force-pushed
the
ai/02-org-session-salt
branch
from
August 12, 2026 20:04
34798b3 to
3490ce0
Compare
Collaborator
Author
|
Dropping this one. The salt bought no confidentiality: That also removes migration 0040, the lazy backfill, the AES-GCM decrypt on the ingest auth path, and the |
🍁 Maple PR previewNote Preview resources were removed when this pull request closed. Final commit |
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.
What this layer contains
The per-org secret that AI session-key hashing will need:
packages/db—session_salt_ciphertext/session_salt_iv/session_salt_tagonorg_ingest_keys, migration0040_org_session_salt.sqlplus its drizzle snapshot/journal.apps/api—OrgIngestKeysService.getSessionSalt, which lazily provisions and decrypts the salt using the same AES-256-GCM envelope (and the sameMAPLE_INGEST_KEY_ENCRYPTION_KEY) as the private ingest key, with tests.Why it is shaped this way
Session keys are stored as
cityHash64(concat(salt, '\0', value))so the raw key never reaches the warehouse and hashes cannot be correlated across orgs. That makes the salt a prerequisite of the write path rather than part of it, which is why it ships as its own reviewable unit ahead of the classifier.Two properties are deliberate. The columns are nullable only because existing rows predate them, so a null means "not provisioned yet" and is backfilled on first read — never "this org has no salt". And there is no reroll path: rotating the salt changes every hash, which breaks
SessionsApproxHLL continuity across the rotation boundary, double-counting sessions on either side of it.Gates run at this level
bun typecheck— 37/37 tasks pass.apps/apivitest,src/services/org/— 5 files, 62 tests pass.apps/apivitest,src/routes/v2/— 12 files, 142 tests pass (covers thev2-test-supportstub gaininggetSessionSalt).Dependencies
Stacked on #430 for ordering only — it shares no code with the registry artifact and is independently reviewable.
🤖 Generated with Claude Code
Stack created with GitHub Stacks CLI • Give Feedback 💬
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.