fix(mcp): drop the containment gate, always validate the post-edit envelope
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:
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user