feat(brain): stamp server-owned write provenance; surface it in brain_query
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:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user