fix(mcp): close claim-envelope bypass on value-only brain_edit diffs
Review of PR #136 found the gate trivially bypassable: touchesClaimEnvelope only fired when old_str/new_str contained the claim-envelope FIELD NAME as a substring, but a natural value-only diff (old_str: "asserted", new_str: "guessed" against a claim_provenance line) never mentions the field name and walked straight past it — notes.Edit wrote the invalid enum value to disk with zero error. Redesign the gate to be position-based instead of name-based: checkClaimEnvelope now checks whether old_str is physically contained within the note's frontmatter block (via the new frontmatterBlock helper), previews the exact strings.Replace the real edit would perform, and validates the resulting envelope before the edit is allowed to run. frontmatterScalars now builds on the shared frontmatterBlock helper. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -116,6 +116,31 @@ func TestBrainEdit_RejectsAnInvalidClaimEnvelopeWrittenViaStringReplace(t *testi
|
||||
assert.Contains(t, string(got), "claim_provenance: asserted", "rejected edit must leave the note byte-identical")
|
||||
}
|
||||
|
||||
func TestBrainEdit_RejectsAValueOnlyEditThatBreaksTheEnvelope(t *testing.T) {
|
||||
// Found in review: the original gate (touchesClaimEnvelope) checked
|
||||
// whether old_str/new_str mention the FIELD NAME — but a minimal diff
|
||||
// (the natural shape of any real edit, model-generated or human) can
|
||||
// target just the VALUE token and never mention the field name at all.
|
||||
// "asserted" -> "guessed" on a claim_provenance line walked straight
|
||||
// past the field-name substring check and wrote an invalid enum value
|
||||
// with no error — the exact bypass #123 was supposed to close, closed
|
||||
// in name only.
|
||||
dir := newBrainRepo(t)
|
||||
note := filepath.Join(dir, "wiki/homelab/facts/n.md")
|
||||
require.NoError(t, os.WriteFile(note,
|
||||
[]byte("---\ntitle: 'n'\nhalf_life: durable\napplies: universal\nclaim_provenance: asserted\n---\n\nbody\n"), 0o644))
|
||||
srv := mcp.NewServer(dir, nil, nil, nil)
|
||||
|
||||
resp := call(t, srv, "brain_edit", map[string]any{
|
||||
"path": "wiki/homelab/facts/n.md", "old_str": "asserted", "new_str": "guessed",
|
||||
})
|
||||
require.Contains(t, resp, "error", "%v", resp)
|
||||
|
||||
got, err := os.ReadFile(note)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(got), "claim_provenance: asserted", "rejected edit must leave the note byte-identical")
|
||||
}
|
||||
|
||||
func TestBrainEdit_AllowsAValidClaimEnvelopeEdit(t *testing.T) {
|
||||
dir := newBrainRepo(t)
|
||||
note := filepath.Join(dir, "wiki/homelab/facts/n.md")
|
||||
|
||||
Reference in New Issue
Block a user