feat(security): receipt admin scripts + user git-ops (zero exceptions)

Wire the remaining shell egress through gstack-egress-lib.sh:

- gstack-gbrain-mcp-verify: both JSON-RPC probe POSTs (initialize +
  tools/list) receipted fail-closed via payload files (hash == wire
  bytes). A refused receipt lands in the NETWORK class — no send.
- gstack-security-dashboard / gstack-community-dashboard: the
  community-pulse GETs receipted fail-open (read-only stats must not
  break over an audit hiccup).
- gstack-gbrain-supabase-provision: api_call receipted fail-closed.
  Each retry attempt hands the helper a fresh copy of the body file
  (the helper consumes its payload). The receipt hashes the request
  body only — the PAT never reaches the ledger or any log. Refusal
  exits 8 without retrying.
- git-class sha256:null receipts, fail-open: gstack-artifacts-init
  (ls-remote, initial push, fetch/pull recovery, retry push),
  gstack-brain-restore (staging clone, existing-repo fetch),
  gstack-session-update (self-update pull).

gstack-team-init needs no wiring: every git clone in it is inside an
echoed instruction string, not an executed command.

The lib now self-locates with shell builtins only (no dirname), so
sourcing works under the whitelist-PATH test harnesses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit b8c5e2055b21ab72878b3e46f8047782ee65a11c)
This commit is contained in:
Garry Tan
2026-08-12 15:31:48 -07:00
parent 52288947ec
commit 97ace4a452
10 changed files with 111 additions and 24 deletions
+4 -1
View File
@@ -345,7 +345,10 @@ describe('gstack-brain-sync egress receipt gate', () => {
const commitsBefore = git(['rev-list', '--count', 'HEAD']).stdout.trim();
// Make the receipt unwritable: security dir exists but is read-only.
fs.mkdirSync(path.join(tmpHome, 'security'), { recursive: true, mode: 0o500 });
// (artifacts-init may have created it already — mkdirSync's mode is a
// no-op on an existing dir, so chmod explicitly.)
fs.mkdirSync(path.join(tmpHome, 'security'), { recursive: true });
fs.chmodSync(path.join(tmpHome, 'security'), 0o500);
try {
const refused = run(['gstack-brain-sync', '--once']);
expect(refused.status).toBe(1);
+10 -1
View File
@@ -51,13 +51,20 @@ function makeFakeCurl(opts: {
printf 'CURL_CALL '"'"'%s'"'"' ' "$@" >> "${curlCallLog}"
echo "" >> "${curlCallLog}"
# Walk argv to find -o <out> and -d <data>.
# Walk argv to find -o <out> and the request body (-d <data> or the
# receipted --data-binary @<payload-file> shape).
out=""
data=""
while [ $# -gt 0 ]; do
case "$1" in
-o) out="$2"; shift 2 ;;
-d) data="$2"; shift 2 ;;
--data-binary)
case "$2" in
@*) data="$(cat "\${2#@}" 2>/dev/null)" ;;
*) data="$2" ;;
esac
shift 2 ;;
*) shift ;;
esac
done
@@ -83,6 +90,8 @@ function runVerify(token: string, url: string): { code: number; stdout: string;
...process.env,
PATH: `${fakeBinDir}:${process.env.PATH}`,
GBRAIN_MCP_TOKEN: token,
// The probe writes egress receipts — keep them in the temp home.
GSTACK_HOME: tmpDir,
},
encoding: 'utf-8',
});