fix(code-intelligence): gbrain search/export are consent-gated and receipted

The Sourcebot side got this in the last round; gbrain had the same hole —
search() and export() sent repo-derived query text into a possibly-remote
DATABASE_URL with no consent check and no egress receipt, bypassing the
deny-tier veto. Both now assert consent before any bytes move, receipts
record the actual consent state (never a hardcoded true), and search
receipts carry the query's sha256. gbrain stays fail-closed: the adapter
cannot see where DATABASE_URL points, so every send requires consent.
7 new tests, red-first.
This commit is contained in:
Garry Tan
2026-08-14 21:04:18 -07:00
parent e0171f480c
commit a64106535b
3 changed files with 189 additions and 7 deletions
+10
View File
@@ -224,6 +224,16 @@ async function cmdSearch(terms: string[]): Promise<void> {
// loopback providers need no consent, so their path is unchanged.
const searchRoot = getRoot(provider!.id) ?? resolve(process.cwd());
const consented = hasConsent(searchRoot, undefined, "read");
// Honest pre-flight (mirrors cmdIndex): the adapter enforces the same gate
// (assertEgressConsent throws PROVIDER_NOT_CONSENTED before any bytes or
// receipt exist), but the CLI names WHY — missing consent vs a deny repo
// trust policy — instead of surfacing a generic provider error.
if (!provider!.local && !consented) {
const recorded = readSelection().consents[searchRoot] === true;
fail(recorded
? `${provider!.label} search is blocked by the repo trust policy (deny — the query text is repo-derived content). Change with: gstack-gbrain-repo-policy set <origin-url> read-only (search allowed) or read-write`
: `${provider!.label} would send the query text (repo-derived content) off this machine. Run \`gstack-code-intelligence consent ${searchRoot} yes\` first.`);
}
try {
const hits = await provider!.search(query, { limit: 10, consented });
if (!hits.length) {