feat(brain): stamp server-owned write provenance; surface it in brain_query
CI / Lint / Test / Vet (pull_request) Successful in 43s
CI / Mirror to GitHub (pull_request) Skipped

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>
This commit is contained in:
2026-10-01 20:49:11 +02:00
co-authored by Claude Opus 5.5
parent a9a9eebe26
commit 32dedb318d
13 changed files with 277 additions and 25 deletions
+15
View File
@@ -90,6 +90,7 @@ func (s *Server) brainCapture(ctx context.Context, args json.RawMessage) (json.R
// Principal and origin are server-derived — never taken from the body.
in.Context.Principal = info.principal
in.Context.Origin = s.capture.resolver.Resolve(info.principal, info.viaStatic)
in.Context.AuthPath, _ = provenanceFrom(ctx)
rec, err := s.capture.svc.Capture(ctx, in)
if err != nil {
@@ -99,3 +100,17 @@ func (s *Server) brainCapture(ctx context.Context, args json.RawMessage) (json.R
}
return json.Marshal(rec)
}
// provenanceFrom maps the authenticated principal in ctx to the provenance
// stamped on a brain write (hyperguild#145). No principal → "unknown", never
// silence: an unstamped note must not read as trusted.
func provenanceFrom(ctx context.Context) (origin, writtenBy string) {
info, ok := ctx.Value(principalKey).(principalInfo)
if !ok {
return "unknown", ""
}
if info.viaStatic {
return "static", info.principal
}
return "jwt", info.principal
}