Repository navigation
feat(s135): secrets out of the data directory, and filesystem roots that fail closed - #128
Merged
Merged
Conversation
…ots that fail closed) A tool the agent calls could read Trinity's own keys: the data directory was always a filesystem root, the receipt key lived under it, and the default policy allows reads. With the default policy, fs_read of <data dir>/keys/receipts-ed25519.key returned the key. - Secrets leave the data directory for TRINITY_SECRETS_DIR (a per-OS default outside it); the boot moves existing keys, the MCP bearer token and the OAuth store there, never overwriting, and names the move on the boot receipt; keys keep their ids and earlier receipts verify. - Trinity.Tools.FS.Guard judges every path a tool is given: /proc and /dev, any symlink, any path under the data or secrets directory, special files and hard links are refused, and a boot-time (dev, inode) inventory catches hard links and bind mounts; the opened descriptor is checked again. A short deny-list runs last. A guard refusal is a hard deny no approval lifts. - A root equal to, above or below either directory refuses boot in every profile. - web_fetch asks once a session has read outside the roots; :regulated refuses network: :allow without an egress allow-list. - Every fs decision, denials included, is receipted with the canonical path and inode. Signed-off-by: Ayla Croft <aylacroft@proton.me>
Signed-off-by: Ayla Croft <aylacroft@proton.me> # Conflicts: # CHANGELOG.md # ci/headless/Containerfile
Signed-off-by: Ayla Croft <aylacroft@proton.me> # Conflicts: # CHANGELOG.md
…s refusal; SC-12 says where keys live now Slice 132's mapping, merged to main, requires every census and every regulated refusal to be cited by a row. Slice 135 adds one of each (test/trinity/tools/fs/secrets_census_test.exs, Trinity.Profile.check_egress/3), so the merge failed test/control_mapping_test.exs with both named. Two rows and one responsibility-matrix row cite them, and the SC-12 row no longer says key material lives in the data directory. Signed-off-by: Ayla Croft <aylacroft@proton.me>
…nstead of naming ed25519 On the FIPS leg the receipt key is P-384 (KeyCustody.select/0), so receipts-ed25519.key never exists there and AC4's boot test failed both fips runs on 7e0dab7. The first boot now prints the signer's own key path and the test asserts on that. Signed-off-by: Ayla Croft <aylacroft@proton.me>
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.
Slice 135, opened on the owner's 2026-10-08 answer sheet (S1).
The defect, reproduced on
main: with the default policy (read: :allow),fs_readof a key file under the data directory returned its contents with no approval, because the data directory was always a filesystem root (since slice 022) and the receipt key lived under it.network: :allowmeant a fetch could carry it out. The same reproduction on this branch returns{:error, {:fs_denied, ...}}.What changes
TRINITY_SECRETS_DIR, with a per-OS default outside it (Linux~/.config/trinity/secrets, macOS~/Library/Application Support/Trinity Secrets, Windows%LOCALAPPDATA%\Trinity\Secrets). The boot moves existing keys, the MCP bearer token and the OAuth store there, never overwriting, and names the move on the boot receipt. Keys keep their ids, and earlier receipts verify. The headless Containerfile setsTRINITY_SECRETS_DIR=/data/secrets.Trinity.Tools.FS.Guardjudges every path a tool is given. It refuses/procand/dev, a symlink anywhere in the path, anything under the data or secrets directory, special files and hard links. A boot-time(dev, inode)inventory catches hard links and bind mounts, and the opened descriptor isfstated again. A deny-list runs last. A guard refusal is a hard deny that no approval lifts.web_fetchasks once a session has read outside the roots.:regulatedrefusesnetwork: :allowwithout an egress allow-list (TRINITY_REGULATED_EGRESS).Proof: AC1 to AC6 automatic, each shown red first (on
mainevery route returned the key's bytes, and the census found 220 breaches). AC7, a bind mount on the hardened image, is manual. The full suite passes on SQLite (1080) and Postgres. The changed tests (79), format, credo and plan_check were re-run in review.Known before merge
hex.audituntil fix(s061): beam_mcp 0.10.1, the security patch hex.audit asks for #127 (beam_mcp 0.10.1) is onmain.ci/headless/Containerfile, which this edits. Whichever merges second resolves that.