fix(mcp): drop the containment gate, always validate the post-edit envelope
CI / Lint / Test / Vet (pull_request) Successful in 41s
CI / Mirror to GitHub (pull_request) Skipped

Round-2 review found a third bypass: checkClaimEnvelope required old_str to
be fully contained within the extracted frontmatter block (which excludes
both "---" fence lines). An old_str disambiguated across the closing fence
into the body — a natural choice, since notes.Edit requires old_str to be
unique — failed that containment test, so the gate skipped validation
entirely while notes.Edit applied the replace verbatim, reaching the same
invalid-enum outcome as the two bypasses already closed.

Remove the containment gate structurally instead of patching another edge:
checkClaimEnvelope now always computes the post-edit doc and validates its
resulting frontmatter, regardless of old_str's textual relationship to the
fence. A body-only edit leaves the frontmatter unchanged (still valid, since
the note started valid), so this is a no-op for the common case and
fail-closed for every case that touches the envelope, however old_str is
phrased.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 10:27:30 +02:00
co-authored by Claude Sonnet 5
parent 062574dad8
commit 0f932d8f77
2 changed files with 36 additions and 4 deletions
+9 -4
View File
@@ -275,10 +275,15 @@ func checkClaimEnvelope(brainDir, path, oldStr, newStr string) error {
// — this function's job is the envelope, not read failures.
return nil
}
block, ok := frontmatterBlock(string(before))
if !ok || !strings.Contains(block, oldStr) {
return nil // old_str isn't in the frontmatter block at all
}
// No gate on where old_str falls relative to the frontmatter fence:
// round 2 found that requiring old_str to be fully contained in the
// extracted block was itself bypassable by an old_str disambiguated
// across the closing "---" into the body. Simpler and bypass-proof:
// always compute what the edit would produce and validate that
// resulting state's envelope. A body-only edit leaves the frontmatter
// (and thus the validation outcome) unchanged, so this is a no-op for
// the common case and fail-closed for every case that touches the
// envelope, regardless of how old_str is phrased.
after := strings.Replace(string(before), oldStr, newStr, 1)
return claim.ValidateEnvelope(frontmatterScalars(after))
}