UpdateNote: supersede_reason settable via caller's own frontmatter fragment, bypassing the Reason option #125
Closed
opened 2026-09-22 21:28:34 +00:00 by mathias
·
1 comment
No Branch/Tag Specified
main
fix/pipeline-writes-158
fix/edit-path-io-192
fix/create-regular-188
fix/guard-special-179
fix/linkcheck-keeps-write-178
fix/write-dir-redirect-173
fix/tunnel-detect-174
fix/pipeline-root-157
w3/154-mcp-auth-wiring
fix/tunnel-traversal
test/in-brain-targets-171
fix/brain-scan-root-157
fix/graph-wiki-root-157
fix/vectorstore-root-157
fix/notes-root-157
fix/search-root-157
fix/brainwide-root-157
fix/raw-symlinks-153
feat/symlink-tripwire-157
fix/rest-auth-149
fix/123-brain-edit-envelope-check
feat/106-page-aware-extraction
fix/105-source-drift-on-main
fix/104-surface-merge-conflicts-on-main
agent/b1-compile-on-ingest
agent/104-surface-merge-conflicts
agent/105-source-drift
agent/107-loud-unconfigured-alerting
feat/brain-read-edit
fix/capture-i1-refusal-names-target
renovate/configure
feat/consolidate-single-harness
icebox/cmd-routing
feat/brain-pending-promote
fix/close-session-classification-refresh
fix/capture-summarywriter
v0.24.0
v0.23.1
v0.23.0
v0.22.0
v0.21.1
v0.21.0
v0.20.1
v0.20.0
v0.19.1
v0.19.0
v0.18.4
v0.18.3
v0.18.2
v0.18.1
v0.18.0
v0.17.0
v0.16.1
v0.16.0
v0.15.0
v0.14.0
v0.13.2
v0.13.1
v0.13.0
v0.12.1
v0.12.0
v0.11.2
icebox/cmd-routing-2026-07-01
v0.11.1
v0.11.0
v0.10.0
v0.9.0
v0.8.0
v0.7.0
v0.6.0
v0.5.0
v0.4.1
v0.4.0
v0.3.0
v0.2.0
v0.1.0
Labels
Clear labels
agent/ready
blocked/client
blocked/supplier
blocked/upstream
risk/high
risk/low
risk/medium
size/l
size/m
size/s
size/xs
status:dispatch
status:dispatched
status:review
status:reviewing
type/chore
type/decision
type/docs
type/feat
type/fix
type/ops
type/research
type/test
Meets the agent-ready contract; authorizes unattended execution
Waiting on the client
Waiting on a supplier
Waiting on an upstream project or vendor
Irreversible, security-relevant, or control plane
Reversible, local, test-covered
Touches shared state, deploys or data
Too big - split it
Under two days
Under half a day
Under an hour
Maintenance, no behaviour change
Human decision required; never agent/ready
Documentation only
New capability
Corrects broken behaviour
Infrastructure, deploy, runtime
Investigate; output is a finding, not code
Test coverage only
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: mathias/hyperguild#125
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Non-blocking observation from #111's review (PR #124, merge commit
5a5d938e).UpdateNote's merge loop (ingestion/internal/api/update.go) applies every field from the caller's leading frontmatter fragment (contentFM) ontofm, then unconditionally re-setsupdated_at/supersedesbut only setssupersede_reasonwhenopts.Reason != "". So a caller can setsupersede_reasondirectly via its own fragment (---\nsupersede_reason: whatever\n---\n...) even when theReasonoption is empty, bypassing the intended single entry point for that field.Judged non-blocking at review time: it's the same actor able to set the same field two ways, not a cross-provenance leak (unlike the original #111 bug, where a different, earlier author's envelope data could survive unvalidated). No confirmed exploit, just an inconsistency worth deciding on deliberately rather than leaving implicit.
Ask
Decide whether the merge loop's field set should be constrained (e.g. skip
updated_at/supersedes/supersede_reasonif present in the caller's fragment, since those three are meant to be server-derived, not caller-set) — or whether this is fine as-is and the fragment is intentionally a general-purpose "set anything" mechanism now that #111 validates its envelope subset.Low priority — speculative severity, not a confirmed defect.
Created via git-mcp on behalf of @mathias
Merged PR #137 (squash) as merge commit
a5f502eb9c.Review evidence:
ingestion/internal/api/update.go's merge loop now skipsl.keyin{"", "updated_at", "supersedes", "supersede_reason"}instead of only"". Two new tests inupdate_test.go(absence assertion for the leak, regression pin for the legitopts.Reasonpath).l.key == ""in a scratch worktree ->TestUpdateNoteSupersedeReasonNotSettableViaCallerFragmentWhenReasonOptionEmptywent red with the leakedsupersede_reason: snuck in via the fragmentvisible in the note body. Restored the fix -> both new tests green.UpdateNoteisbrainstore.Store.Update, which passesReasonviacapture.Note.Reason(the proper option), never via the content fragment. No MCP tool description or handler sets these fields through the fragment either. This is a pure gap-close, no behavior change for any real caller.claim.ValidateEnveloperuns on the full merged frontmatter (half_life/applies/claim_provenance), none of which are in the new skip list.ingestion/:go build ./...,go vet ./...,go test ./... -race -count=1all clean at head SHAfb69f4bf3d.Closing.