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
+46
View File
@@ -48,6 +48,11 @@ type Result struct {
// (infra#72). Read from frontmatter when present, otherwise
// inferred from the parent directory.
Tier string `json:"tier,omitempty"`
// Origin/WrittenBy surface write provenance (hyperguild#145) so a
// reader can discount an entry a connector session wrote. Omitted for
// notes written before provenance existed — never fabricated.
Origin string `json:"origin,omitempty"`
WrittenBy string `json:"written_by,omitempty"`
}
// tierWeight maps the DIKW tier to a score multiplier applied right
@@ -146,6 +151,7 @@ func QueryContext(ctx context.Context, brainDir string, opts QueryOptions) ([]Re
rel = filepath.ToSlash(rel)
wing, hall := extractWingHall(string(content), rel)
tier := extractTier(string(content), rel)
origin, writtenBy := extractProvenance(string(content))
results = append(results, Result{
Path: rel,
Title: extractTitle(string(content), d.Name()),
@@ -154,6 +160,9 @@ func QueryContext(ctx context.Context, brainDir string, opts QueryOptions) ([]Re
Wing: wing,
Hall: hall,
Tier: tier,
Origin: origin,
WrittenBy: writtenBy,
})
return nil
})
@@ -272,6 +281,7 @@ func hydrate(brainDir, relPath string) (Result, error) {
}
wing, hall := extractWingHall(string(content), relPath)
tier := extractTier(string(content), relPath)
origin, writtenBy := extractProvenance(string(content))
return Result{
Path: relPath,
Title: extractTitle(string(content), filepath.Base(relPath)),
@@ -279,9 +289,45 @@ func hydrate(brainDir, relPath string) (Result, error) {
Wing: wing,
Hall: hall,
Tier: tier,
Origin: origin,
WrittenBy: writtenBy,
}, nil
}
// extractProvenance reads origin/written_by from the leading frontmatter
// block only — a body line that happens to start with "origin:" is not
// provenance.
func extractProvenance(content string) (origin, writtenBy string) {
scanner := bufio.NewScanner(strings.NewReader(content))
inFrontmatter := false
for scanner.Scan() {
line := scanner.Text()
if strings.TrimSpace(line) == "---" {
if !inFrontmatter {
inFrontmatter = true
continue
}
break
}
if !inFrontmatter {
return "", ""
}
key, val, ok := strings.Cut(line, ":")
if !ok || strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
continue
}
v := strings.Trim(strings.TrimSpace(val), `"'`)
switch strings.TrimSpace(key) {
case "origin":
origin = v
case "written_by":
writtenBy = v
}
}
return origin, writtenBy
}
// resolveRoots returns the directories to walk for the given wing/hall
// filters. Validates hall against the closed vocabulary when set.
func resolveRoots(brainDir, wing, hall string) ([]string, error) {