The PR #147 review forged origin: static as a JWT principal through
three tools the first version never touched (brain_edit, brain_ingest_raw,
ingest_raw + brain_promote), and through YAML spellings a key-name match
missed (quoted, escaped, tagged, flow-mapped, merge keys, lone CR).
- api.StampProvenance removes every spelling of origin/written_by from
the frontmatter and appends the real values. A line is dropped if
EITHER yaml.v3 (decoding quotes, escapes, tags, flow maps) or the
brain's line-oriented readers would read it as provenance; complex
keys, orphan values and merge keys are dropped outright. Lone CRs in
frontmatter become LF. Principal values that are not bare words are
quoted.
- Applied as the last step of writeHallNote, UpdateNote, PromoteNote
(re-attributed to the promoter), brain_edit (re-attributed to the
editor) and both ingests (every page written or merged). REST
/write and /promote carry no principal and stamp unknown.
- Ingest: newlines in scalar fields fold to spaces; a page type must be
a plain lower-case name (../raw wrote outside wiki/, pre-existing).
- Audit: outcome is ok|error, never the error text, which echoed
argument values.
Tests: every escape from the review is a table case asserting the
invariant on the file (parses as YAML, yaml.v3 and brain_get agree,
exactly the stamped lines). Plus REST /write, REST capture AuthPath,
body-line provenance in search, and quoted principals; two of the
review's surviving flips now go red. Three tests renamed so names claim
only what they assert.
Refs #145#144
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brain_write was an ungated, durable prompt-injection path: a session that
just read an untrusted page could write an entry every later session
retrieves as trusted guidance (OWASP MCP06). Decision (#145): tag
provenance rather than stage writes.
Every write and update stamps origin (static | jwt | unknown) and
written_by (the authenticated principal) into frontmatter. Server-owned:
origin/written_by in caller content or an update fragment are discarded,
so a caller cannot claim a more trusted origin. No principal stamps
origin: unknown, never silence. brain_update re-stamps to the last
writer. capture (the claude.ai connector door) stamps too, via a new
server-derived CaptureContext.AuthPath set by both capture doors after
decode. REST POST /write carries no principal and stamps unknown.
brain_query results carry origin/written_by when the note has them;
older notes omit them rather than fabricate. brain_get already returns
frontmatter.
Refs #145
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes#125. Skips updated_at/supersedes/supersede_reason in the caller fragment merge loop so they cannot be set via the fragment, only via the intended entry points.
delete_branch_after_merge=true
Co-authored-by: Mathias <mthbqv@gmail.com>
The BrainStore is not the only door. POST /write — the HTTP path CLAUDE.md
hands to curl and scripts — calls api.WriteNote directly, so the strip
shipped one commit ago covered brain_write and capture while leaving the raw
door ungated. A gate one door wide is not a gate.
The BrainStore keeps its own call so it can still report Ref.Unlinked to the
MCP caller; running the strip twice is a no-op on content the primitive has
already cleaned.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Implements the api-layer half of #45. UpdateNote supersedes a note in
place (whole-note body replace, frontmatter re-stamp: updated_at,
supersedes=prior content hash, supersede_reason), preserving created_at,
wing, hall, and any custom fields. Never creates — a missing target is
an error so callers fall back to brain_write. ReadNote is the read-after-
write primitive (frontmatter + body + content_hash). ContentHash is the
sha256 handle that round-trips write/update → get.
Frontmatter is edited via a line-preserving ordered editor rather than a
yaml.v3 round-trip, which would reorder keys and strip comments — the
brain writes flat key:value frontmatter by hand.
Embeddings are not refreshed here: the rewritten file's mtime advances,
which the out-of-band vectorstore.Sync ticker uses to re-embed it — the
same mechanism brain_write relies on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>