Files
stack/packages/cli/tests/refusal-redaction.test.mjs

61 lines
3.4 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
const secret = 'sk-test-NOT-A-SECRET-1234';
// JSON.parse quotes a prefix near this fault on Node 26; check partial leaks too.
const malformed = secret;
function scratch(t) {
const root = mkdtempSync(join(tmpdir(), 'r3-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
return root;
}
function write(file, text = malformed) {
mkdirSync(dirname(file), { recursive: true });
writeFileSync(file, text, { mode: 0o600 });
return file;
}
import { spawnSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { fixture } from './helpers.mjs';
const repo = join(import.meta.dirname, '../../..');
test('task file JSON refusal preserves exit 2 and file without parser bytes', t => {
const file = write(join(scratch(t), 'task.json'));
const r = spawnSync(process.execPath, [join(repo, 'scripts/mosaic-task.mjs'), 'validate', file], { encoding: 'utf8', env: { PATH: process.env.PATH, HOME: process.env.HOME, TMPDIR: process.env.TMPDIR } });
assert.equal(r.status, 2, r.stderr);
assert.ok(r.stderr.includes(file), r.stderr);
assert.match(r.stderr, /task file is not valid JSON/);
assert.doesNotMatch(r.stdout + r.stderr, /sk-test|Unexpected token/);
});
test('task file read refusal retains exit 2, file and filesystem code', t => {
const file = write(join(scratch(t), 'task.json'), '{}');
// A directory is refused by lstat before the read. Inject a read failure
// after that unchanged regular-file gate, including on root-run CI hosts.
const preload = `import fs from 'node:fs';
const read = fs.readFileSync;
fs.readFileSync = function(path, ...args) {
if (path === ${JSON.stringify(file)}) throw Object.assign(new Error(${JSON.stringify(secret)}), { code: 'EIO' });
return read.call(this, path, ...args);
};`;
const r = spawnSync(process.execPath, ['--import', 'data:text/javascript,' + encodeURIComponent(preload), join(repo, 'scripts/mosaic-task.mjs'), 'validate', file], { encoding: 'utf8', env: { PATH: process.env.PATH, HOME: process.env.HOME, TMPDIR: process.env.TMPDIR } });
assert.equal(r.status, 2, r.stderr);
assert.ok(r.stderr.includes(file), r.stderr);
assert.match(r.stderr, /task file is not valid JSON.*\(EIO\)/);
assert.doesNotMatch(r.stderr, /sk-test/);
});
test('case2 precheck business helper omits parser bytes in its BUSINESS fact', t => {
const root = scratch(t), f = fixture(root);
const file = write(join(root, 'config/businesses/acme.json'));
// Execute the production facts helper, without host or credential checks.
const shell = readFileSync(join(repo, 'scripts/case2-precheck.sh'), 'utf8');
const start = shell.indexOf('facts="$(cd "$REPO" && node --input-type=module -e \'') + 'facts="$(cd "$REPO" && node --input-type=module -e \''.length;
assert.ok(start > 100);
const end = shell.indexOf("' \"$REPO\" \"$BUSINESS\"", start);
assert.ok(end > start);
const r = spawnSync(process.execPath, ['--input-type=module', '-e', shell.slice(start, end), repo, 'acme'], { encoding: 'utf8', env: { ...f.env, HOME: process.env.HOME, TMPDIR: process.env.TMPDIR, PATH: process.env.PATH } });
assert.equal(r.status, 0, r.stderr);
assert.ok(r.stdout.includes(`BUSINESS\tbad\tcannot read ${file}:`), r.stdout);
assert.doesNotMatch(r.stdout + r.stderr, /sk-test|Unexpected token/);
});