mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-28 14:59:47 +00:00
* wip: federated sync v2 pre-merge snapshot
* v0.40.5.0 Federated Sync v2 — parallel source sync + push triggers + per-source health
Bump VERSION + package.json + CHANGELOG header + migration walkthrough filename
to v0.40.5.0 (claiming the next free slot in the v0.40.x patch series after
master's v0.40.1.0).
What ships (6 components, all behind sync.federated_v2 feature flag default-on):
1. Per-source sync lock — syncLockId(sourceId), phantom-redirect parity
2. Parallel sync --all — pMapAllSettled fan-out, --max-sources N cap
3. embed-backfill minion handler — D2 per-source lock + D6 $10/job budget + D15.1
fire-and-forget submission + D19 source-level cooldown + 24h $25 rolling cap
4. sync trigger CLI + POST /webhooks/github — HMAC-verified (60 req/min/IP),
X-GitHub-Event=push + ref filter against tracked_branch
5. sources status + federation_health doctor — batched GROUP BY pipeline
(4 queries instead of 6×N per-source roundtrips)
6. sources federate/unfederate hook — auto-submit embed-backfill on flip
Correctness fixes (unconditional):
- D21: sync.ts:959 facts backstop now passes sourceId to engine.getPage
- D15.4: redactSourceConfig + CI guard prevent webhook_secret leak
- D15.5: safeHexEqual extracted to src/core/timing-safe.ts
Schema:
- Migration v89 (sources_github_repo_index): partial expression index on
config->>'github_repo' for fast webhook source-lookup
Tests:
- 14 new test files, 112 cases. 4 IRON-RULE regressions pinned (SYNC_LOCK_ID
back-compat, phantom per-source lock, embed-backfill kill+resume,
webhook HMAC prefix-strip). All 9449 unit tests pass.
Caught at test-write time: the webhook handler had a Buffer.from('sha256=...',
'hex') truncation bug — without the prefix-strip, every signature would have
"matched" empty buffers. Pinned by a test/sources-webhook.test.ts IRON-RULE.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(check-source-config-leak): tighten regex to source-row patterns only
The v0.40.5.0 wave added scripts/check-source-config-leak.sh with a
too-broad pattern (JSON\.stringify\(.*config) that flagged any variable
named 'config' — catching the GLOBAL gbrain config.json serializers in
src/commands/init.ts (status envelopes) and src/core/config.ts (the
config-file write site). On the CI runner without rg installed, the
grep -rE fallback fired correctly and produced 4 false positives that
broke the `verify` script.
Tightened the patterns to specifically match `(source|src|row|s).config`
property access — the actual risk shape (a sources-table row being
serialized whole). The global gbrain config has a different shape and
threat model (file-mode 0o600 at the write site), so it's safe to
exempt at the regex level rather than per-file whitelist.
Also fixed a latent bug: the rg branch used `--include='*.ts'` (grep's
flag, not rg's). rg silently rejected it and CANDIDATES came back empty,
so the local-dev runs (which have rg) would never have caught a real
leak. Now branches on tool availability: `-g '*.ts'` for rg, `--include`
for grep -rE. Both branches verified against a synthetic leak fixture.
Also added init.ts + config.ts to the whitelist as a belt-and-suspenders
since they handle gbrain-global config (not source rows) and could
otherwise reflect-back via regex iteration.
CI: `bun run verify` exit 0 locally with both the original false-positive
fixture (clean repo) and a synthetic leak fixture (correctly caught,
exit 1).
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
111 lines
4.4 KiB
Bash
Executable File
111 lines
4.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# v0.40 D15.4 CI guard — prevent webhook_secret leak through sources.config
|
|
# serialization paths.
|
|
#
|
|
# After v0.40, sources.config can contain secrets (webhook_secret). Any code
|
|
# path that returns the raw config object via JSON.stringify / serializer
|
|
# without first running it through redactSourceConfig() will leak the secret.
|
|
#
|
|
# This script greps for risky patterns:
|
|
# 1. JSON.stringify on a `config` field where the source is a row from `sources`
|
|
# 2. New endpoints / ops that return raw `config` without `redactSourceConfig`
|
|
#
|
|
# Failure mode is loose-positive on purpose — false positives cost one
|
|
# 30-second comment-or-fix; false negatives leak production secrets.
|
|
|
|
set -euo pipefail
|
|
|
|
cd "$(dirname "$0")/.."
|
|
|
|
FOUND=0
|
|
|
|
# Pattern A: sources.config field referenced in a JSON serializer call site
|
|
# without redactSourceConfig nearby. Covers MCP op handlers, admin API
|
|
# routes, sources.ts subcommands that print --json output.
|
|
#
|
|
# Whitelist:
|
|
# - src/core/source-config-redact.ts itself (defines the redactor)
|
|
# - src/core/sources-load.ts (returns raw rows; callers redact)
|
|
# - src/commands/sources.ts runFederate/runWebhook* (mutators write raw)
|
|
# - src/core/migrate.ts (DDL data references not serialization)
|
|
# - src/core/sources-ops.ts (CLI feedback prints structured fields, not raw config)
|
|
# - test/ (tests are allowed to introspect raw config)
|
|
|
|
# Grep for `r.config\|src.config\|source.config` near JSON.stringify/console.log/res.json
|
|
# where redactSourceConfig is NOT used in the same hunk.
|
|
RAW_PATTERN='\b(\.config\b|config:[[:space:]]*src\.config)\b'
|
|
|
|
# Tightened patterns: match serializers that pass a source-row's .config
|
|
# field (source.config, src.config, row.config, s.config, or a property
|
|
# access like `.config` on an object likely sourced from a `sources` row),
|
|
# NOT every variable named "config" (which would catch global gbrain config).
|
|
#
|
|
# The risk pattern is `JSON.stringify(<srcVar>.config)` where srcVar holds
|
|
# a row from the sources table. Variables that hold the GLOBAL gbrain
|
|
# config.json are also commonly named `config` — that's a different shape
|
|
# and a different threat model (already protected at the file-mode 0o600
|
|
# write site in src/core/config.ts).
|
|
#
|
|
# rg uses `-g` for globs; grep -rE uses `--include`. Branch accordingly so
|
|
# CI runners without rg still match cleanly.
|
|
if command -v rg >/dev/null 2>&1; then
|
|
CANDIDATES=$(rg -n \
|
|
-e 'JSON\.stringify\((source|src|row|s)\.config' \
|
|
-e 'res\.json\((source|src|row|s)\.config' \
|
|
-e 'res\.json\(\{[^}]*\.config[^.]' \
|
|
-e 'console\.log\(JSON\.stringify\((source|src|row|s)\.config' \
|
|
-g '*.ts' \
|
|
src/ 2>/dev/null || true)
|
|
else
|
|
CANDIDATES=$(grep -rEn \
|
|
-e 'JSON\.stringify\((source|src|row|s)\.config' \
|
|
-e 'res\.json\((source|src|row|s)\.config' \
|
|
-e 'res\.json\(\{[^}]*\.config[^.]' \
|
|
-e 'console\.log\(JSON\.stringify\((source|src|row|s)\.config' \
|
|
--include='*.ts' \
|
|
src/ 2>/dev/null || true)
|
|
fi
|
|
|
|
# Filter out files we trust (handle sources.config redaction themselves OR
|
|
# handle the gbrain global config, which is a different object).
|
|
FILTERED=$(echo "$CANDIDATES" | \
|
|
grep -v 'src/core/source-config-redact.ts' | \
|
|
grep -v 'src/core/sources-load.ts' | \
|
|
grep -v 'src/commands/sources.ts' | \
|
|
grep -v 'src/core/migrate.ts' | \
|
|
grep -v 'src/core/sources-ops.ts' | \
|
|
grep -v 'src/commands/init.ts' | \
|
|
grep -v 'src/core/config.ts' || true)
|
|
|
|
if [ -n "$FILTERED" ]; then
|
|
# For each candidate, check if redactSourceConfig appears within 10 lines above.
|
|
while IFS= read -r LINE; do
|
|
[ -z "$LINE" ] && continue
|
|
FILE=$(echo "$LINE" | cut -d: -f1)
|
|
LINENO=$(echo "$LINE" | cut -d: -f2)
|
|
# Look in surrounding 20 lines
|
|
START=$((LINENO - 10))
|
|
[ "$START" -lt 1 ] && START=1
|
|
END=$((LINENO + 5))
|
|
CONTEXT=$(sed -n "${START},${END}p" "$FILE" 2>/dev/null || true)
|
|
if ! echo "$CONTEXT" | grep -q 'redactSourceConfig'; then
|
|
echo "POTENTIAL_LEAK: $LINE"
|
|
echo " Context lacks redactSourceConfig — verify webhook_secret cannot be serialized."
|
|
FOUND=1
|
|
fi
|
|
done <<< "$FILTERED"
|
|
fi
|
|
|
|
if [ "$FOUND" -eq 1 ]; then
|
|
echo ""
|
|
echo "v0.40 D15.4 guard: every sources.config serializer MUST go through"
|
|
echo "redactSourceConfig() from src/core/source-config-redact.ts."
|
|
echo ""
|
|
echo "If a flagged site is a known false positive (e.g. CLI command that"
|
|
echo "only prints metadata, not the raw object), update the whitelist in"
|
|
echo "scripts/check-source-config-leak.sh."
|
|
exit 1
|
|
fi
|
|
|
|
exit 0
|