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.
64 lines
3.0 KiB
JavaScript
64 lines
3.0 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 { DiscordError } from '../src/errors.mjs';
|
|
import { loadDataRoot, loadBinding } from '../src/binding.mjs';
|
|
import { readApprovals } from '../src/approvals.mjs';
|
|
import { readInboxIds } from '../src/journal.mjs';
|
|
import extension from '../extension/tools.mjs';
|
|
import { TOOLS_ENV } from '../src/tools.mjs';
|
|
for (const [name, read, relative, label] of [
|
|
['config', (file) => loadDataRoot(file), 'config.json', 'config is not valid JSON'],
|
|
['binding', (file) => loadBinding(file), 'binding.json', 'binding binding is not valid JSON'],
|
|
['approvals', (_file, root) => readApprovals(root), 'approvals.jsonl', 'approvals.jsonl:2: not valid JSON'],
|
|
['journal', (_file, root) => readInboxIds(root), 'inbox.jsonl', 'inbox.jsonl:2: not valid JSON'],
|
|
]) test(`${name} JSON refusal omits parser bytes`, t => {
|
|
const root = scratch(t), ledger = name === 'approvals' || name === 'journal';
|
|
const file = write(join(root, relative), ledger ? '{}\n' + malformed : malformed);
|
|
assert.throws(() => read(file, root), safe(label, DiscordError, { exitCode: 2 }));
|
|
});
|
|
test('tools environment JSON refusal names the config key without its bytes', () => {
|
|
const previous = process.env[TOOLS_ENV];
|
|
process.env[TOOLS_ENV] = malformed;
|
|
try { assert.throws(() => extension({}), safe(TOOLS_ENV, Error)); }
|
|
finally { if (previous === undefined) delete process.env[TOOLS_ENV]; else process.env[TOOLS_ENV] = previous; }
|
|
});
|
|
test('config read refusal retains the filesystem code', t => {
|
|
const file = scratch(t);
|
|
assert.throws(() => loadDataRoot(file), error => {
|
|
safe('config is not valid JSON', DiscordError, { exitCode: 2 })(error);
|
|
assert.match(error.message, /\(EISDIR\)$/);
|
|
return true;
|
|
});
|
|
});
|