mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-10 23:19:09 +02:00
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:
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user