fix: rules-loader guarded read + private session-state dir (iss-66) - #33
Merged
Conversation
…e dir (iss-66) Trust-boundary hardening for the rules loader, consolidated into one guarded reader used by all three file readers: - C19: Load re-resolved .abcd/rules.json by name (Lstat then ReadFile), a symlink-swap TOCTOU. It now reads via readGuarded: open once with O_NOFOLLOW and validate the SAME fd (regular file, size cap) before reading. - F1 (fixes a regression the first cut introduced): O_NOFOLLOW alone let a FIFO leaf block open(O_RDONLY) forever, wedging the non-blocking-by-contract prompt-router hook. readGuarded adds O_NONBLOCK so a FIFO/device leaf returns immediately and the regular-file check rejects it. Mkfifo regression test. - P14: prompt-router session-state moved off the world-writable shared temp dir to the per-user cache dir (ABCD_RULES_STATE_DIR still overrides). The degraded uid-qualified temp fallback is documented as best-effort, not a guarantee. - LoadState and LoadBackstop now share readGuarded too (was the same Lstat-then- read TOCTOU); the size cap is enforced post-read as well as via fstat. P15 (a per-repo rules.json can set a default domain dormant / flip the kill switch) is document-accepted, not changed — rules are an opt-in, committed, opinionated-but-overridable layer; real enforcement is harness-level. The protected-guardrail-domain alternative is surfaced in DECISIONS.md. Assisted-by: Claude:claude-opus-4-8
Assisted-by: Claude:claude-opus-4-8
# Conflicts: # CHANGELOG.md
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.
Resolves iss-66 (native ledger) — rules-loader trust-boundary hardening from the clean-slate sweep.
Fixes (
internal/core/rules)All three file readers now share one guarded reader,
readGuarded, which opens once withO_NOFOLLOW+O_NONBLOCK, validates the same fd (regular file, size cap) and reads through aLimitReader:LoaddidLstat(path)thenos.ReadFile(path), re-resolving by name; a swap to a symlink in that window followed the link. Now closed by the open-once read.O_NOFOLLOWalone doesn't stop a FIFO/device leaf, andopen(fifo, O_RDONLY)blocks forever — which would wedge theUserPromptSubmitprompt-router hook whose contract is "never block a session."O_NONBLOCKmakes a FIFO open return immediately so the regular-file check rejects it. (O_NONBLOCKis a no-op for reads on regular files.)sha256(session)path, session defaulting to the constant"default") to the per-user cache dir, so a local co-tenant can't pre-create the path to suppress injection.ABCD_RULES_STATE_DIRstill overrides.LoadStateandLoadBackstopcarried the sameLstat→ReadFileTOCTOU; they now route throughreadGuardedtoo (fail-open preserved). A post-read length check complements the fstat size cap.Tests (each watched fail on revert)
TestLoadRefusesSymlinkedRulesFile— a symlinkedrules.jsonleaf errors (the old test only covered a symlinked.abcddir).TestLoadRefusesFifoRulesFile— a FIFO leaf is refused within 3s, not hung on.TestStateDirNotSharedTemp— default state lives under the user cache dir, not shared/tmp(skips on a degraded no-HOMEenv).make preflight+make record-lintgreen; rules package green under-race. Reviewed over two rounds: ruthless SHIP, security PASS — the security reviewer found the F1 FIFO BLOCK on the first cut and re-verified the fix.Deferred / document-accepted (see DECISIONS.md)
P15 — a per-repo
.abcd/rules.jsoncan set a default domain dormant and flip the global kill switch. This is the intended opinionated-but-overridable model:.abcd/rules.jsonis a committed file (editing it needs repo write access), and the real enforcement of dangerous actions is harness-level (git-guardrails hooks, the identity gate, pre-commit), not the injected advisory prose. Surfaced, not taken: a protected "guardrail" domain class that an override could not disable — a trust-model decision for the maintainer, not an autonomous change.Assisted-by: Claude:claude-opus-4-8