redact6. A refusal names the field, the file or the instance. It never quotes a
config value, a host path, or a raw e.message or stderr that carries one.
- organization: checkCredentialRef names the field and each forbidden root by
label; OrgError keeps .path only for a document field ($...); the registry,
fence and lock refusals use the quiet helpers (call and code).
- cli: loadSystem never relays mosaic-config stderr: instance refusals keep
their code and names, everything else reads as a field and its constraint
against a labelled config. Project-key, access warning, halt gate, sessions
file, launch socket, harness relays and the workspace rename name labels.
Launch-log reasons read a Node system error as its call and code.
- scripts/mosaic-config: configuration not found names where the config came from.
- discord, seat, control-board: config, token, context, tool-root, journal,
binding, registration and seen-marks refusals name labels.
Exit codes, refusal codes and check order are unchanged. Each new or changed
test fails on BASE be698e602.
48 lines
2.5 KiB
JavaScript
48 lines
2.5 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;
|
|
// #1873: the sentinel is a path component, so a refusal that quotes a path quotes it too.
|
|
function scratch(t) {
|
|
const root = mkdtempSync(join(tmpdir(), 'r3-'));
|
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const dir = join(root, secret);
|
|
mkdirSync(dir);
|
|
return dir;
|
|
}
|
|
function write(file, text = malformed) {
|
|
mkdirSync(dirname(file), { recursive: true });
|
|
writeFileSync(file, text, { mode: 0o600 });
|
|
return file;
|
|
}
|
|
function safe(label, ErrorType, properties = {}) {
|
|
return error => {
|
|
assert.ok(error instanceof ErrorType);
|
|
for (const [key, value] of Object.entries(properties)) assert.equal(error[key], value);
|
|
assert.ok(error.message.includes(label), error.message);
|
|
assert.ok(!error.message.includes(tmpdir()), error.message);
|
|
assert.doesNotMatch(error.message, /sk-test|Unexpected token/);
|
|
assert.doesNotMatch(error.stack, /sk-test/);
|
|
assert.equal(error.cause, undefined);
|
|
return true;
|
|
};
|
|
}
|
|
import { SeatError, loadDataRoot, readRegistration, registrationPath } from '../src/seat.mjs';
|
|
for (const kind of ['config', 'registration']) test(`${kind} JSON refusal names only the file`, t => {
|
|
const root = scratch(t), file = write(kind === 'config' ? join(root, 'config.json') : registrationPath(root, 'builder', 'repo'));
|
|
assert.throws(() => kind === 'config' ? loadDataRoot(file) : readRegistration(root, 'builder', 'repo'), safe(kind === 'config' ? 'config is not valid JSON' : 'registration repo/builder is not valid JSON', SeatError, { exitCode: 2 }));
|
|
});
|
|
for (const kind of ['config', 'registration']) test(`${kind} read refusal retains the filesystem code`, t => {
|
|
const root = scratch(t), file = kind === 'config' ? join(root, 'config.json') : registrationPath(root, 'builder', 'repo');
|
|
mkdirSync(file, { recursive: true });
|
|
assert.throws(() => kind === 'config' ? loadDataRoot(file) : readRegistration(root, 'builder', 'repo'), error => {
|
|
safe(kind === 'config' ? 'config is not valid JSON' : 'registration repo/builder is not valid JSON', SeatError, { exitCode: 2 })(error);
|
|
assert.match(error.message, /\(EISDIR\)$/);
|
|
return true;
|
|
});
|
|
});
|