mirror of
https://github.com/garrytan/gstack.git
synced 2026-06-17 23:30:09 +02:00
fix(security): close cross-model (Codex) adversarial findings
Codex adversarial review found a HIGH the Claude pass missed plus 3 mediums: - C1 (HIGH): gstack-decision-search --all returned every decide and IGNORED redact events, so a redacted secret still resurfaced via --all until compact ran. --all now excludes redacted (redact = expunge from every read path), still showing superseded history. - C-med: semantic (external gbrain) slug/snippet were printed raw — datamark them too so a gbrain hit can't spoof role markers / fences into agent context. - C4: semanticRecall fell back to an UNSCOPED gbrain search when no curated-memory source resolved, pulling code/doc corpora mislabeled as 'related decisions'. Now returns null (degrade) when there's no worktree-backed memory source. - C5: validateDecide scanned only decision/rationale/alternatives; branch and issue are stored + surfaced (raw via --json), so include them in the injection+secret scan. C2 (snapshot staleness) / C3 (compact TOCTOU residual): accepted for a single-user store — atomic appends never lose the event, rebuilds self-heal, and the compact size-recheck leaves only a sub-ms window; full append-locking would break the lock-free append design. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,8 +48,13 @@ const semantic = args.includes("--semantic");
|
||||
|
||||
let rows: ActiveDecision[];
|
||||
if (showAll) {
|
||||
// include superseded: every decide event from the full log (no active filter)
|
||||
rows = readEvents(paths).filter((e): e is ActiveDecision => e.kind === "decide");
|
||||
// --all includes SUPERSEDED decisions (history), but NEVER redacted ones — a redact
|
||||
// is an expunge, so it must remove the text from every read path, not just active.
|
||||
const events = readEvents(paths);
|
||||
const redacted = new Set(
|
||||
events.filter((e) => e.kind === "redact" && e.supersedes).map((e) => e.supersedes as string),
|
||||
);
|
||||
rows = events.filter((e): e is ActiveDecision => e.kind === "decide" && !redacted.has(e.id));
|
||||
} else {
|
||||
rows = readSnapshot(paths);
|
||||
// Rebuild only when a snapshot is absent but a log exists (don't write a snapshot
|
||||
@@ -94,8 +99,10 @@ if (semantic && queryRaw) {
|
||||
if (hits && hits.length) {
|
||||
console.log("\nRelated from memory (gbrain semantic recall):");
|
||||
for (const h of hits) {
|
||||
const snip = h.snippet.length > 100 ? `${h.snippet.slice(0, 100)}…` : h.snippet;
|
||||
console.log(` [${h.score.toFixed(2)}] ${h.slug}: ${snip}`);
|
||||
// gbrain hits are EXTERNAL corpus content — datamark slug + snippet too so they
|
||||
// can't spoof role markers / fences when printed into agent context.
|
||||
const snip = datamark(h.snippet.length > 100 ? `${h.snippet.slice(0, 100)}…` : h.snippet);
|
||||
console.log(` [${h.score.toFixed(2)}] ${datamark(h.slug)}: ${snip}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user