Files
jason.woltje 4ff4f44c98 lead: commit researcher's untracked evidence (1406 files: queue-101 queue-105 queue-128 queue-134 queue-143 queue-148 queue-183 queue-228)
Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
2026-11-02 15:05:14 -06:00

104 lines
8.7 KiB
JavaScript

// Row 148 round 3 (sage-0621): a stack verdict for every regex in markdown-preview.js.
// Each regex is taken from the module's own source text (the needle must occur in it), then run on
// its worst shapes at 4 MiB, one process per (regex, shape), so one run's heap cannot help another.
// A verdict is "returns" or "RangeError" (or "other: <message>" / "timeout").
// Usage: node stack-audit.mjs --module F --out stack-audit.md [--kib 4096] [--timeout 60]
// node stack-audit.mjs one <needle-index> <shape-index> <kib> --module F (internal)
import { readFileSync, writeFileSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const args = process.argv.slice(2);
const opt = (name, dflt) => { const i = args.indexOf('--' + name); return i < 0 ? dflt : args[i + 1]; };
const MODULE = resolve(opt('module'));
const src = readFileSync(MODULE, 'utf8');
const SEP = String.fromCharCode(0x2028);
// [literal as written in the module, how it is used, shapes]. A shape is [name, n => string].
const R = (n, c) => c.repeat(n);
const REGEXES = [
['/[!-/:-@[-`{-~]/', 'test', [['letters', n => R(n, 'a')], ['punct', n => R(n, '!')]]],
['/^( {0,3})(`{3,}|~{3,})(.*)$/s', 'test', [['backticks', n => R(n, '`')], ['tildes', n => R(n, '~')], ['backticks + x', n => R(n, '`') + 'x'], ['backticks + separator', n => R(n, '`') + SEP], ['fence + blanks', n => '```' + R(n, ' ')]]],
['/^ {0,3}(#{1,6})(?:[ \\t]+(.*))?$/s', 'test', [['hashes', n => R(n, '#')], ['hash + blanks + x', n => '#' + R(n, ' ') + 'x'], ['hash + blanks', n => '#' + R(n, ' ')], ['hash + blanks + separator', n => '# ' + R(n, ' ') + SEP]]],
['/^ {0,3}(=+|-+)[ \\t]*$/', 'test', [['equals', n => R(n, '=')], ['dashes', n => R(n, '-')], ['equals + blanks + x', n => '=' + R(n, ' ') + 'x'], ['dash + tabs + x', n => '-' + R(n, '\t') + 'x']]],
['/^ {0,3}> ?/', 'test', [['quotes', n => R(n, '>')], ['quote + blanks', n => '>' + R(n, ' ')], ['blanks + quote', n => R(n, ' ') + '>']]],
['/^( {0,3})([-+*]|\\d{1,9}[.)])([ \\t]+|$)(.*)$/s', 'exec', [['dash + blanks', n => '-' + R(n, ' ')], ['dash + blanks + x', n => '-' + R(n, ' ') + 'x'], ['digits + dot', n => R(n, '1') + '.'], ['dash + tabs + separator', n => '-' + R(n, '\t') + SEP], ['dash + blanks + separator', n => '- ' + R(n, ' ') + SEP]]],
['/[\\u0000-\\u0020\\u007f-\\u009f\\\\\\u200b-\\u200f\\u2028-\\u202e\\u2066-\\u2069\\ufeff]/', 'test', [['letters', n => R(n, 'a')], ['letters + separator', n => R(n, 'a') + SEP]]],
['/^[A-Za-z][A-Za-z0-9+.-]*:/', 'test', [['letters', n => R(n, 'a')], ['letters + colon', n => R(n, 'a') + ':']]],
['/^https?:\\/\\/[^/?#]/i', 'test', [['h', n => R(n, 'h')], ['http + letters', n => 'http://' + R(n, 'a')]]],
['/[ \\t\\n]/', 'test', [['letters', n => R(n, 'a')]]],
['/[\\n<]/', 'test', [['letters', n => R(n, 'a')]]],
['/\\s/', 'test', [['letters', n => R(n, 'a')]]],
['/\\\\([!-/:-@[-`{-~])/g', 'replace', [['escaped punct', n => R(n / 2, '\\!')], ['backslashes', n => R(n, '\\')], ['escaped letters', n => R(n / 2, '\\a')]]],
['/[A-Za-z0-9]/', 'test', [['punct', n => R(n, '!')], ['letters', n => R(n, 'a')]]],
['/\\n/g', 'replace', [['newlines', n => R(n, '\n')], ['letters', n => R(n, 'a')]]],
['/^(`+|~+)[ \\t]*$/', 'exec', [['backticks', n => R(n, '`')], ['backticks + blank', n => R(n, '`') + ' '], ['backticks + x', n => R(n, '`') + 'x'], ['backtick + blanks + x', n => '`' + R(n, ' ') + 'x'], ['tildes + x', n => R(n, '~') + 'x']]],
['/\\r\\n?/g', 'replace', [['CR', n => R(n, '\r')], ['CRLF', n => R(n / 2, '\r\n')], ['letters', n => R(n, 'a')]]],
['/^[ \\t]+/', 'replace', [['blanks', n => R(n, ' ')], ['blanks + x', n => R(n, ' ') + 'x'], ['tabs', n => R(n, '\t')], ['letters', n => R(n, 'a')]]],
['/\\t/g', 'replace', [['tabs', n => R(n, '\t')], ['letters', n => R(n, 'a')]]],
];
// The regex round 3 removed, kept here as the control: the audit must report it.
const REMOVED = ['/^ {0,3}([-*_])(?:[ \\t]*\\1){2,}[ \\t]*$/', 'test', [['dashes', n => R(n, '-')], ['dashes + blank', n => R(n, '-') + ' '], ['spaced dashes', n => R(n / 2, '- ')], ['spaced stars', n => R(n / 2, '* ')], ['underscores', n => R(n, '_')]]];
if (args[0] === 'one') {
const [, ni, si, kib] = args;
const entry = ni === 'removed' ? REMOVED : REGEXES[Number(ni)];
const re = new Function('return ' + entry[0])();
const text = entry[2][Number(si)][1](Number(kib) * 1024);
const t0 = performance.now();
try {
if (entry[1] === 'test') re.test(text);
else if (entry[1] === 'exec') re.exec(text);
else if (entry[1] === 'replace') text.replace(re, w => w);
console.log(JSON.stringify({ verdict: 'returns', ms: +(performance.now() - t0).toFixed(1) }));
} catch (e) {
console.log(JSON.stringify({ verdict: e instanceof RangeError ? 'RangeError' : 'other: ' + e.message, ms: +(performance.now() - t0).toFixed(1) }));
}
process.exit(0);
}
const KIB = Number(opt('kib', '4096')), TIMEOUT = Number(opt('timeout', '60')), OUT = resolve(opt('out', 'stack-audit.md'));
const SELF = fileURLToPath(import.meta.url);
const lineOf = needle => { const out = []; src.split('\n').forEach((l, i) => { if (l.includes(needle)) out.push(i + 1); }); return out; };
const quantifiedGroup = lit => /\)[*+{]/.test(lit) || /\\[1-9]/.test(lit);
const L = ['# Stack verdict for every regex in markdown-preview.js (sage-0621)', '',
`Module ${MODULE}. Each regex is taken from the module's source text (it must occur there), then run alone on its worst shapes at ${KIB} KiB (${KIB * 1024} characters), one process per regex and shape, timeout ${TIMEOUT} s. A verdict is "returns" or "RangeError".`,
'Column "quantified group or backreference" is a text test on the literal: a `)` followed by `*`, `+` or `{`, or a `\\1`-style backreference. A `)?` is a group that matches at most once and is not counted.', '',
'| line(s) | regex | quantified group or backreference | shape | verdict | ms |', '|---|---|---|---|---|---|'];
let bad = 0, total = 0;
const run = (ni, si) => {
const r = spawnSync(process.execPath, [SELF, 'one', String(ni), String(si), String(KIB), '--module', MODULE], { encoding: 'utf8', timeout: TIMEOUT * 1000, killSignal: 'SIGKILL' });
if (r.error && r.error.code === 'ETIMEDOUT') return { verdict: 'timeout', ms: TIMEOUT * 1000 };
if (r.status !== 0) return { verdict: 'process failed: ' + (r.stderr || '').slice(0, 80), ms: 0 };
return JSON.parse(r.stdout);
};
const missing = [];
REGEXES.forEach((entry, ni) => {
const lines = lineOf(entry[0]);
if (!lines.length) missing.push(entry[0]);
entry[2].forEach((shape, si) => {
const v = run(ni, si); total++;
if (v.verdict !== 'returns') bad++;
L.push(`| ${lines.join(', ') || 'NOT FOUND'} | \`${entry[0].replace(/\|/g, '\\|')}\` | ${quantifiedGroup(entry[0]) ? 'yes' : 'no'} | ${shape[0]} | ${v.verdict} | ${v.ms} |`);
});
});
L.push('', '## Control: the regex round 3 removed (it must not be in the module, and it must fail here)', '', '| line(s) in module | regex | quantified group or backreference | shape | verdict | ms |', '|---|---|---|---|---|---|');
const inModule = lineOf(REMOVED[0]);
let controlFails = 0;
REMOVED[2].forEach((shape, si) => {
const v = run('removed', si);
if (v.verdict === 'RangeError') controlFails++;
L.push(`| ${inModule.join(', ') || 'none'} | \`${REMOVED[0]}\` | ${quantifiedGroup(REMOVED[0]) ? 'yes' : 'no'} | ${shape[0]} | ${v.verdict} | ${v.ms} |`);
});
L.push('', `${total} regex and shape runs on the module's regexes: ${bad} not "returns". Regexes missing from the module's source: ${missing.length}${missing.length ? ' (' + missing.join(', ') + ')' : ''}. Regexes in the module with a quantified group or backreference: ${REGEXES.filter(e => quantifiedGroup(e[0])).length}. The removed regex is ${inModule.length ? 'STILL IN THE MODULE' : 'not in the module'} and fails ${controlFails} of ${REMOVED[2].length} control shapes.`);
// The scan reads code only: whole-line comments and a trailing " // comment" are cut first.
const code = src.split('\n').filter(l => !l.trim().startsWith('//') && !l.trim().startsWith('*')).map(l => l.replace(/\s\/\/\s.*$/, '')).join('\n');
const literals = [...code.matchAll(/(?<![\w)\]$"'`])\/(?![/*])(?:\\.|\[(?:\\.|[^\]\\])*\]|[^/\\\n[])+\/[a-z]*/g)].map(m => m[0]);
const known = new Set(REGEXES.map(e => e[0]));
const unlisted = literals.filter(l => !known.has(l));
L.push('', `Regex literals found by a scan of the module source that are not in the table above: ${unlisted.length}${unlisted.length ? ' - ' + unlisted.map(x => '`' + x + '`').join(', ') : ''}.`);
writeFileSync(OUT, L.join('\n') + '\n');
console.log(`${total} runs, ${bad} not returns, missing ${missing.length}, unlisted ${unlisted.length}, control fails ${controlFails}/${REMOVED[2].length}`);
process.exit(bad || missing.length || unlisted.length || inModule.length ? 1 : 0);