mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-14 08:59:01 +02:00
fix(test): banner-tripwire exec used JSON.stringify as shell quoting — vacuous pass + stray artifact
JSON escaping is not shell escaping. Interpolating JSON.stringify(script)
into `bash -c ${...}` left every JSON "\n" as a literal backslash-n inside
shell double quotes, collapsing the extracted release-body tripwire block
onto one line: `then\n` parsed as the command word `thenn`, and
`>&2\nelse\n` parsed as the redirect `>&2nelsen` — so every full-suite run
littered a `2nelsen` file (containing "bash: thenn: command not found") in
the repo root, and the test's single not-contains assertion passed
VACUOUSLY because all output had been redirected into that file. The
"and it actually fires" functional check never verified anything.
Fix: pass the script as an argv element (spawnSync array form) and assert
both branches for real — ABORT case must print the leak message to stderr,
clean case must print "banner tripwire clean" to stdout.
Verified: `bun test test/binding-template-drift.test.ts` previously created
the artifact deterministically; the full free suite now runs artifact-free.
The other shell-interpolation sites (evidence, schema-aware concurrency,
empty-find-fallthrough, branch-slug-hygiene) already use correct quoting.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
05e6f1cfe5
commit
d85fbccf7a
@@ -68,21 +68,47 @@ describe('content-binding template drift', () => {
|
|||||||
|
|
||||||
// Functional: execute the template's tripwire block against a 0-banner
|
// Functional: execute the template's tripwire block against a 0-banner
|
||||||
// original and a 1-banner outgoing body — the ABORT branch must fire.
|
// original and a 1-banner outgoing body — the ABORT branch must fire.
|
||||||
|
//
|
||||||
|
// Pass the script as an ARGV element (spawnSync array form), never by
|
||||||
|
// interpolating JSON.stringify into a shell line: JSON escaping is not
|
||||||
|
// shell escaping. Inside shell double quotes a JSON "\n" stays a literal
|
||||||
|
// backslash-n, which collapsed this multi-line script onto one line where
|
||||||
|
// `then\n` became the command word `thenn` and `>&2\nelse\n` became the
|
||||||
|
// redirect `>&2nelsen` — silently littering a `2nelsen` file (containing
|
||||||
|
// "bash: thenn: command not found") in the repo root on every suite run,
|
||||||
|
// while the old not-contains assertion passed vacuously because ALL
|
||||||
|
// output had been redirected into that file.
|
||||||
const block = body.match(/_ORIG_BANNERS=\$\(grep[\s\S]*?fi\n/);
|
const block = body.match(/_ORIG_BANNERS=\$\(grep[\s\S]*?fi\n/);
|
||||||
expect(block).not.toBeNull();
|
expect(block).not.toBeNull();
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { execSync } = require('child_process');
|
const { spawnSync } = require('child_process');
|
||||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-banner-'));
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-banner-'));
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(path.join(dir, 'orig.md'), 'clean body\n');
|
const scriptFor = (origContent: string, newContent: string) => {
|
||||||
fs.writeFileSync(path.join(dir, 'new.md'), 'body with UNTRUSTED TRACKER CONTENT banner leak\n');
|
fs.writeFileSync(path.join(dir, 'orig.md'), origContent);
|
||||||
const script = block![0]
|
fs.writeFileSync(path.join(dir, 'new.md'), newContent);
|
||||||
.replaceAll('/tmp/gstack-pr-body-orig-$$.md', path.join(dir, 'orig.md'))
|
return block![0]
|
||||||
.replaceAll('/tmp/gstack-pr-body-$$.md', path.join(dir, 'new.md'));
|
.replaceAll('/tmp/gstack-pr-body-orig-$$.md', path.join(dir, 'orig.md'))
|
||||||
const out = execSync(`bash -c ${JSON.stringify(script + '; true')}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
.replaceAll('/tmp/gstack-pr-body-$$.md', path.join(dir, 'new.md'));
|
||||||
expect(out).not.toContain('banner tripwire clean');
|
};
|
||||||
|
|
||||||
|
// Banner leaked into the outgoing body → the ABORT branch fires, loudly.
|
||||||
|
const abort = spawnSync('bash', ['-c', scriptFor(
|
||||||
|
'clean body\n',
|
||||||
|
'body with UNTRUSTED TRACKER CONTENT banner leak\n',
|
||||||
|
)], { encoding: 'utf-8' });
|
||||||
|
expect(abort.stderr).toContain('ABORT: envelope banner leaked');
|
||||||
|
expect(abort.stdout).not.toContain('banner tripwire clean');
|
||||||
|
|
||||||
|
// No banner delta → the clean branch fires.
|
||||||
|
const clean = spawnSync('bash', ['-c', scriptFor(
|
||||||
|
'clean body\n',
|
||||||
|
'also clean body\n',
|
||||||
|
)], { encoding: 'utf-8' });
|
||||||
|
expect(clean.stdout).toContain('banner tripwire clean');
|
||||||
|
expect(clean.stderr).not.toContain('ABORT');
|
||||||
} finally {
|
} finally {
|
||||||
fs.rmSync(dir, { recursive: true, force: true });
|
fs.rmSync(dir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user