test(mods): fleet adversarial probe for every plugin guard (W6)

scripts/probe-mod-guards.mjs bundles each hooks/guard.ts with esbuild and runs a
97-probe corpus through its verdict: invisible/bidi splits, nesting to 5000,
wide/huge inputs, secrets in object keys, encodings, regex-DoS stress timed per
call (50 ms gate), benign look-alikes. Calibrates each guard's real secret
surface first so a miss is a real bypass. A fast subset runs last in
smoke-all-plugins against a known-holes baseline. Findings (4 root causes across
32 guards, no ReDoS/crash/hang) are in v3/docs/validation/mod-guard-probe-2026-10.md.
No plugin code changed.

Co-Authored-By: RuFlo <ruv@ruv.net>
This commit is contained in:
ruv committed 2026-10-04 19:35:38 -04:00
1 parent cd010ee76c
commit 7de1657db8
8 files changed
+1898 -1

No files matched your search

+186
View File
@@ -0,0 +1,186 @@
#!/usr/bin/env node
/**
* Tests for scripts/probe-mod-guards.mjs against synthetic guards: a naive one (depth/size caps, values only, no invisible-character
* stripping) that the probe must flag, and a screening one it must pass. Also pins the CLI contract (exit codes, baseline handling)
* and that the corpus never carries a secret-shaped literal.
*
* Run via: node --test scripts/__tests__/probe-mod-guards.test.mjs
*/
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { spawnSync } from 'node:child_process'
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { ALL_SECRETS, BENIGN, SECRET_TYPES, evasionProbes, stressProbes } from '../lib/guard-probe-corpus.mjs'
const HERE = dirname(fileURLToPath(import.meta.url))
const CLI = join(HERE, '..', 'probe-mod-guards.mjs')
const tmp = mkdtempSync(join(tmpdir(), 'probe-mod-guards-test-'))
process.on('exit', () => rmSync(tmp, { recursive: true, force: true }))
const SECRET_RE = String.raw`/(?:gh[pousr]_[A-Za-z0-9]{30,}|AKIA[0-9A-Z]{16}|sk-(?:ant-)?[A-Za-z0-9_-]{24,}|xox[abprs]-[A-Za-z0-9-]{10,}|eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}|Bearer\s+[A-Za-z0-9._~+/=-]{24,}|-----BEGIN [A-Z ]*PRIVATE KEY-----|api_?key\s*[:=]\s*[A-Za-z0-9/+=_.-]{16,})/i`
const NAIVE = `
const SECRET = ${SECRET_RE}
const texts = (v: unknown, depth = 0): string[] =>
depth > 3 ? [] : typeof v === 'string' ? [v.slice(0, 5000)] : Array.isArray(v) ? v.slice(0, 20).flatMap(x => texts(x, depth + 1)) :
typeof v === 'object' && v !== null ? Object.values(v).slice(0, 20).flatMap(x => texts(x, depth + 1)) : []
export function verdict(tool: string, input: unknown): string | undefined {
if (tool !== 'memory_store') return undefined
return texts(input).some(t => SECRET.test(t)) ? 'naive: secret' : undefined
}
`
const SCREENING = `
const SECRET = ${SECRET_RE}
const HIDDEN = /[\\p{Cf}\\u034f]/gu
const MAX_NODES = 20000
const MAX_CHARS = 8_000_000
export function verdict(tool: string, input: unknown): string | undefined {
if (tool !== 'memory_store') return undefined
const stack: unknown[] = [input]
let nodes = 0
let chars = 0
while (stack.length > 0) {
const v = stack.pop()
if (++nodes > MAX_NODES) return 'screening: too large to screen'
if (typeof v === 'string') {
chars += v.length
if (chars > MAX_CHARS) return 'screening: too large to screen'
if (SECRET.test(v.replace(HIDDEN, ''))) return 'screening: secret'
} else if (Array.isArray(v)) {
for (const x of v) stack.push(x)
} else if (typeof v === 'object' && v !== null) {
for (const k of Object.keys(v)) { stack.push(k); stack.push((v as Record<string, unknown>)[k]) }
}
}
return undefined
}
`
function fixtures(dir, sources) {
for (const [name, src] of Object.entries(sources)) {
mkdirSync(join(dir, name, 'hooks'), { recursive: true })
writeFileSync(join(dir, name, 'hooks', 'guard.ts'), src)
}
return dir
}
const run = (...a) => spawnSync(process.execPath, [CLI, ...a], { encoding: 'utf8', timeout: 240_000 })
test('the corpus never carries a secret-shaped literal, only builders', () => {
const src = ['guard-probe-corpus.mjs', 'guard-probe-worker.mjs'].map(f => readFileSync(join(HERE, '..', 'lib', f), 'utf8')).join('\n')
assert.doesNotMatch(src, /gh[pousr]_[A-Za-z0-9]{30,}|AKIA[0-9A-Z]{16}|sk-ant-[A-Za-z0-9_-]{24,}|xox[abprs]-[A-Za-z0-9-]{10,}|BEGIN RSA PRIVATE KEY/)
})
test('every secret type builds a distinct, non-empty value and benign look-alikes are not secrets', () => {
const built = Object.values(SECRET_TYPES).map(f => f())
assert.equal(new Set(built).size, built.length)
assert.ok(built.every(s => s.length > 10))
assert.ok(ALL_SECRETS.includes(built[0]))
const re = new RegExp(SECRET_RE.slice(1, SECRET_RE.lastIndexOf('/')), 'i')
for (const b of BENIGN) assert.equal(re.test(typeof b === 'string' ? b : JSON.stringify(b)), false, `benign text must not look like a secret: ${String(b).slice(0, 40)}`)
})
test('corpus ids are unique and cover every required class', () => {
const ids = [...evasionProbes(1000), ...stressProbes(1000)].map(p => p.id)
assert.equal(new Set(ids).size, ids.length)
const classes = new Set(evasionProbes(1000).map(p => p.cls))
for (const c of ['invisible', 'nesting', 'keys', 'json', 'encoding', 'confusable', 'huge', 'wide']) assert.ok(classes.has(c), c)
assert.ok(ids.includes('nest-obj-5000') && ids.includes('s-a') && ids.includes('s-pem-spaces'))
const deep = evasionProbes(1000).find(p => p.id === 'nest-obj-5000').build('x')
let depth = 0
for (let v = deep; typeof v === 'object'; v = v.k) depth++
assert.equal(depth, 5000)
assert.equal(stressProbes(1_048_576).find(p => p.id === 's-a').build().length, 1_048_576)
})
test('flags the naive guard on nesting, keys, invisible characters and size; passes the screening guard', () => {
const dir = fixtures(join(tmp, 'plugins'), { naive: NAIVE, screening: SCREENING })
const r = run('--fast', '--plugins-dir', dir, '--format', 'json')
assert.equal(r.status, 1, r.stderr)
const out = JSON.parse(r.stdout)
const by = Object.fromEntries(out.rows.map(x => [x.name, x]))
const failed = name => new Set(by[name].results.filter(x => x.status === 'fail').map(x => x.id))
assert.equal(by.naive.meta.calibrated > 0, true)
for (const id of ['nest-obj-12', 'nest-obj-5000', 'nest-arr-60', 'wide-array-250', 'key-name', 'key-name-nested-3', 'shy-split', 'bidi-isolate', 'after-25k-padding']) {
assert.ok(failed('naive').has(id), `naive guard must fail ${id}`)
}
assert.ok(!failed('naive').has('plain'), 'the plain secret is caught: that is the control')
assert.equal(by.naive.results.find(x => x.id === 'benign-lookalikes').status, 'pass')
assert.deepEqual([...failed('screening')], [], `screening guard failed: ${[...failed('screening')].join(', ')}`)
const advisory = by.screening.results.filter(x => x.status === 'info').map(x => x.id)
assert.ok(advisory.includes('base64'), 'encodings are advisory misses, not failures')
assert.ok(out.newHoles.every(h => h.startsWith('naive:')))
})
test('a throwing guard and a slow guard are failures of the stress probes', () => {
const dir = fixtures(join(tmp, 'plugins-bad'), {
thrower: `export function verdict(tool: string, input: unknown): string | undefined {
if (JSON.stringify(input).length > 1000) throw new RangeError('boom')
return tool === 'memory_store' ? undefined : undefined
}`,
slow: `export function verdict(tool: string, input: unknown): string | undefined {
if (tool !== 'memory_store') return undefined
const s = JSON.stringify(input) ?? ''
const end = Date.now() + (s.length > 100000 ? 80 : 0)
while (Date.now() < end) { /* busy */ }
return undefined
}`,
})
const r = run('--fast', '--plugins-dir', dir, '--format', 'json', '--budget-ms', '50')
assert.equal(r.status, 1)
const by = Object.fromEntries(JSON.parse(r.stdout).rows.map(x => [x.name, x]))
const thrown = by.thrower.results.find(x => x.id === 's-a')
assert.equal(thrown.status, 'fail')
assert.match(thrown.note, /RangeError/)
const slowed = by.slow.results.find(x => x.id === 's-a')
assert.equal(slowed.status, 'fail')
assert.ok(slowed.ms >= 50)
})
test('a hung guard is killed by the watchdog and reported, not waited on forever', () => {
const dir = fixtures(join(tmp, 'plugins-hang'), {
hang: `export function verdict(tool: string, input: unknown): string | undefined {
if (typeof input === 'object' && input !== null && JSON.stringify(input).length > 60000) for (;;) { /* never returns */ }
return undefined
}`,
})
const t0 = Date.now()
const r = run('--fast', '--plugins-dir', dir, '--format', 'json', '--watchdog-ms', '1500')
assert.equal(r.status, 1)
assert.ok(Date.now() - t0 < 120_000)
const row = JSON.parse(r.stdout).rows[0]
assert.ok(row.results.some(x => x.status === 'fail' && /HANG/.test(x.note)), 'a probe must be marked HANG')
})
test('a known-holes baseline makes only new holes fail, and reports holes that are now fixed', () => {
const dir = fixtures(join(tmp, 'plugins-base'), { naive: NAIVE, screening: SCREENING })
const base = join(tmp, 'known.json')
const w = run('--fast', '--plugins-dir', dir, '--write-known-holes', base, '--format', 'json')
assert.equal(w.status, 1)
const known = JSON.parse(readFileSync(base, 'utf8'))
assert.ok(known.naive.length > 10)
assert.equal(known.screening, undefined)
const ok = run('--fast', '--plugins-dir', dir, '--known-holes', base)
assert.equal(ok.status, 0, ok.stdout)
assert.match(ok.stdout, /0 new/)
writeFileSync(base, JSON.stringify({ naive: known.naive.filter(id => id !== 'key-name'), screening: ['shy-split'] }))
const regress = run('--fast', '--plugins-dir', dir, '--known-holes', base)
assert.equal(regress.status, 1, 'a hole missing from the baseline is a new hole')
assert.match(regress.stdout, /FAIL\s+key-name\b/)
assert.match(regress.stdout, /known holes that now pass/)
})
test('CLI contract: exit 2 on a bad flag, exit 3 when nothing is found', () => {
assert.equal(run('--nope').status, 2)
assert.equal(run('--budget-ms', '0', '--plugins-dir', tmp).status, 2)
const empty = join(tmp, 'empty')
mkdirSync(empty)
assert.equal(run('--plugins-dir', empty).status, 3)
})
+138
View File
@@ -0,0 +1,138 @@
// Adversarial corpus for scripts/probe-mod-guards.mjs. Pure data builders: no I/O, no plugin knowledge.
// Secrets are assembled from parts at runtime so no secret-shaped literal sits in the repo.
const cp = (...c) => String.fromCodePoint(...c)
const rep = (s, n) => s.repeat(Math.max(1, Math.ceil(n / s.length))).slice(0, n)
export const INVISIBLE = {
zwsp: cp(0x200b), zwnj: cp(0x200c), zwj: cp(0x200d), lrm: cp(0x200e), rlo: cp(0x202e), pdf: cp(0x202c),
wj: cp(0x2060), bom: cp(0xfeff), shy: cp(0xad), rli: cp(0x2067), pdi: cp(0x2069), mvs: cp(0x180e), cgj: cp(0x34f),
}
const A36 = 'A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8'
/** Secret shapes the repo's guards are meant to catch, in the order a plugin's detector is tried. */
export const SECRET_TYPES = {
github: () => 'gh' + 'p_' + A36,
aws: () => 'AK' + 'IA' + 'ABCDEFGHIJKLMNOP',
anthropic: () => 'sk' + '-ant-' + rep('a1B2c3D4', 40),
slack: () => 'xox' + 'b-' + '1234567890-abcdefghijkl',
jwt: () => 'eyJ' + 'hbGciOiJIUzI1NiJ9' + '.eyJ' + 'zdWIiOiIxMjM0NTY3ODkwIn0' + '.' + 'abcdefgh12345678',
bearer: () => 'Bear' + 'er ' + rep('Zy9x8W7v', 32),
assign: () => 'api_' + 'key = ' + 'A1b2C3d4E5f6G7h8I9j0',
pem: () => '-----BEGIN ' + 'RSA PRIVATE KEY-----',
}
export const ALL_SECRETS = Object.values(SECRET_TYPES).map(f => f()).join('\n')
export const BENIGN = [
'Decided to rotate credentials quarterly; the runbook says where each token lives.',
'pip install sk-learn is wrong; the package is scikit-learn. Version 1.2.3, commit 4f9c2a1.',
'The password policy needs 12 characters. Bearer auth is described in section 4.',
'ghp_ is the prefix GitHub uses; AKIA is the AWS prefix. Neither is followed by anything here.',
'token budget: 4096, max_tokens=128, api key rotation ticket RUF-123',
'sha256: ' + rep('0123456789abcdef', 64) + ' (a digest, not a key)',
'id 123e4567-e89b-12d3-a456-426614174000 created by agent-7 at 2026-10-04T12:00:00Z',
'function isKey(k) { return typeof k === "string" && k.length > 3 }\nconst token = nextToken()',
{ title: 'Use JWT with refresh tokens', status: 'accepted', tags: ['auth', 'adr'], depth: { a: [1, 2, { b: 'c' }] } },
['plain', 'list', 'of', 'ordinary', 'words', 42, true, null],
]
const split = (s, ch, at) => s.slice(0, at) + ch + s.slice(at)
const everyChar = (s, ch) => [...s].join(ch)
const fullwidth = s => [...s].map(c => (c >= '!' && c <= '~' ? String.fromCodePoint(c.codePointAt(0) + 0xfee0) : c)).join('')
const homoglyph = s => s.replace(/[aeopc]/g, c => ({ a: cp(0x430), e: cp(0x435), o: cp(0x43e), p: cp(0x440), c: cp(0x441) })[c])
const nest = (leaf, depth, kind) => {
let v = leaf
for (let i = 0; i < depth; i++) v = kind === 'arr' ? [v] : kind === 'mix' ? (i % 2 ? [v] : { k: v }) : { k: v }
return v
}
const pad = (n, s, where) => {
const filler = rep('lorem ipsum dolor sit amet ', n)
return where === 'start' ? s + ' ' + filler : where === 'end' ? filler + ' ' + s : filler.slice(0, n >> 1) + ' ' + s + ' ' + filler.slice(n >> 1)
}
const escFirst = s => '\\u00' + s.charCodeAt(0).toString(16) + s.slice(1)
/**
* Evasion probes: the plain secret in the same slot is refused (calibration proves it), so any mutation that is NOT refused is a hole.
* tier `must` fails the run; `advisory` is an encoding a pattern guard is not designed to decode (reported, fails only with --strict).
*/
export function evasionProbes(big) {
const p = []
const add = (id, cls, tier, build) => p.push({ id, cls, tier, kind: 'evade', build })
add('plain', 'control', 'must', s => `deploy with ${s} now`)
add('zwsp-prefix', 'invisible', 'must', s => split(s, INVISIBLE.zwsp, 2))
add('zwj-mid', 'invisible', 'must', s => split(s, INVISIBLE.zwj, s.length >> 1))
add('bom-mid', 'invisible', 'must', s => split(s, INVISIBLE.bom, s.length >> 1))
add('shy-split', 'invisible', 'must', s => split(s, INVISIBLE.shy, 3))
add('word-joiner', 'invisible', 'must', s => split(s, INVISIBLE.wj, 3))
add('bidi-isolate', 'invisible', 'must', s => split(s, INVISIBLE.rli + INVISIBLE.pdi, 3))
add('bidi-override-wrap', 'invisible', 'must', s => INVISIBLE.rlo + split(s, INVISIBLE.lrm, 4) + INVISIBLE.pdf)
add('mongolian-vs', 'invisible', 'must', s => split(s, INVISIBLE.mvs, 3))
add('every-char-zwsp', 'invisible', 'must', s => everyChar(s, INVISIBLE.zwsp))
for (const kind of ['obj', 'arr', 'mix']) {
for (const d of [3, 6, 12, 60, 5000]) add(`nest-${kind}-${d}`, 'nesting', 'must', s => nest(s, d, kind))
}
add('wide-array-250', 'wide', 'must', s => [...Array.from({ length: 250 }, (_, i) => `note ${i}`), s])
add('wide-object-250', 'wide', 'must', s => ({ ...Object.fromEntries(Array.from({ length: 250 }, (_, i) => [`k${i}`, 'x'])), last: s }))
add('wide-array-5000', 'wide', 'must', s => [...Array.from({ length: 5000 }, () => 'x'), s])
add('key-name', 'keys', 'must', s => ({ [s]: 'value' }))
add('key-name-nested-3', 'keys', 'must', s => nest({ [s]: 'value' }, 3, 'obj'))
add('key-name-in-array', 'keys', 'must', s => [{ [s]: 'value' }])
add('json-in-string', 'json', 'must', s => JSON.stringify({ token: s, note: 'x' }))
add('json-double-encoded', 'json', 'must', s => JSON.stringify(JSON.stringify({ token: s })))
add('json-unicode-escape', 'encoding', 'advisory', s => `{"token":"${escFirst(s)}"}`)
add('base64', 'encoding', 'advisory', s => Buffer.from(s).toString('base64'))
add('hex', 'encoding', 'advisory', s => Buffer.from(s).toString('hex'))
add('url-encoded', 'encoding', 'advisory', s => '%' + s.charCodeAt(0).toString(16) + s.slice(1))
add('fullwidth-confusable', 'confusable', 'advisory', s => fullwidth(s))
add('cyrillic-confusable', 'confusable', 'advisory', s => homoglyph(s))
add('huge-start', 'huge', 'must', s => pad(big, s, 'start'))
add('huge-middle', 'huge', 'must', s => pad(big, s, 'middle'))
add('huge-end', 'huge', 'must', s => pad(big, s, 'end'))
add('after-25k-padding', 'huge', 'must', s => pad(25_000, s, 'end'))
add('after-200k-padding', 'huge', 'must', s => pad(Math.min(big, 200_000), s, 'end'))
return p
}
/** Stress inputs (no secret needed): the guard must neither throw nor take > the budget. */
export function stressProbes(big) {
const p = []
const add = (id, build) => p.push({ id, cls: 'stress', tier: 'must', kind: 'stress', build })
const n = big
add('s-a', () => 'a'.repeat(n))
add('s-a-bang', () => 'a'.repeat(n) + '!')
add('s-sk-near-miss', () => rep('sk' + '-' + 'a'.repeat(22) + ' ', n))
add('s-ghp-near-miss', () => rep('gh' + 'p_' + 'a'.repeat(28) + ' ', n))
add('s-akia-near-miss', () => rep('AK' + 'IA' + 'ABCDEFGHIJKLM ', n))
add('s-assign-spaces', () => rep('api_' + 'key' + ' '.repeat(40), n))
add('s-assign-long-value', () => 'pass' + 'word: ' + 'a'.repeat(n))
add('s-bearer', () => 'Bear' + 'er ' + 'a'.repeat(n))
add('s-pem-spaces', () => '-----BEGIN ' + 'A '.repeat(n >> 1))
add('s-pem-upper', () => '-----BEGIN ' + 'A'.repeat(n))
add('s-jwt-dots', () => rep('eyJ' + 'abcdefghi.', n))
add('s-curl-repeat', () => 'curl '.repeat(n / 5))
add('s-ignore-repeat', () => 'ignore previous '.repeat(n / 16))
add('s-role-tags', () => '<system>'.repeat(n / 8))
add('s-send-secrets', () => rep('send the ', n) + 'x')
add('s-newlines', () => '\n'.repeat(n))
add('s-spaces-x', () => ' '.repeat(n) + 'x')
add('s-lt', () => '<'.repeat(n))
add('s-brace', () => '{'.repeat(n))
add('s-backslash', () => 'x:' + '\\'.repeat(n))
add('s-zwsp', () => INVISIBLE.zwsp.repeat(n))
add('s-zwsp-in-secret', () => rep('gh' + INVISIBLE.zwsp + 'p_' + INVISIBLE.zwsp, n))
add('s-emoji', () => '\u{1F600}'.repeat(n >> 1))
add('s-email', () => 'a@' + 'a.'.repeat(n >> 1))
add('s-email-dots', () => 'a.'.repeat(n >> 1) + '@')
add('s-url', () => 'http://' + 'a'.repeat(n))
add('s-url-userinfo', () => 'http://' + 'a:'.repeat(n >> 1) + '@h')
add('s-path-dots', () => '../'.repeat(n / 3))
add('s-words', () => rep('alpha beta gamma ', n))
add('s-eq-run', () => ('a'.repeat(200) + '=').repeat(n / 201))
add('s-deep-obj-5000', () => nest('benign', 5000, 'obj'))
add('s-deep-arr-5000', () => nest('benign', 5000, 'arr'))
add('s-deep-obj-100000', () => nest('benign', 100_000, 'obj'))
add('s-wide-array-100k', () => Array.from({ length: 100_000 }, () => 'x'))
add('s-wide-keys-100k', () => Object.fromEntries(Array.from({ length: 100_000 }, (_, i) => ['k' + i, 'x'])))
add('s-many-1kb-strings', () => Array.from({ length: Math.max(200, n >> 10) }, () => 'a'.repeat(1024)))
return p
}
+49
View File
@@ -0,0 +1,49 @@
// Markdown rendering of a probe run for v3/docs/validation/mod-guard-probe-*.md. Pure: rows in, string out.
const count = (r, s) => r.results.filter(x => x.status === s).length
const worstOf = r => r.results.reduce((m, x) => (x.status === 'skip' || x.ms <= m.ms ? m : x), { ms: 0, id: '-' })
const cell = s => ({ pass: '✓', fail: '✗', info: '~', skip: '·' })[s] ?? '?'
export function renderReport({ rows, args, startedMs }) {
const out = []
const ids = [...new Set(rows.flatMap(r => r.results.map(x => x.id)))]
const byId = id => rows.map(r => [r.name, r.results.find(x => x.id === id)]).filter(([, x]) => x)
out.push(`Generated by \`node scripts/probe-mod-guards.mjs${args.fast ? ' --fast' : ''}\` in ${((Date.now() - startedMs) / 1000).toFixed(1)}s. Timing gate: ${args.budgetMs} ms per call (best of ${args.fast ? 1 : 3} runs).`)
out.push('')
out.push('## Per-plugin summary')
out.push('')
out.push('| plugin | secret surface found (tool[namespace].field) | pass | fail | advisory miss | skipped | worst ms (probe) |')
out.push('|---|---|---:|---:|---:|---:|---|')
for (const r of rows) {
const w = worstOf(r)
const surface = r.meta.chosen.length ? r.meta.chosen.map(c => `\`${c.at}\``).join('<br>') : r.fatal ? `**FATAL** ${r.fatal}` : 'none (policy guard)'
out.push(`| ${r.name} | ${surface} | ${count(r, 'pass')} | ${count(r, 'fail')} | ${count(r, 'info')} | ${count(r, 'skip')} | ${w.ms.toFixed(1)} (${w.id}) |`)
}
out.push('')
out.push('## Probe matrix (✓ pass · ✗ fail · ~ advisory miss · · not applicable)')
out.push('')
out.push('Columns are plugins in the order of the summary table above (1 = first row).')
out.push('')
out.push(`| probe | class | ${rows.map((_, i) => i + 1).join(' | ')} |`)
out.push(`|---|---|${rows.map(() => ':-:').join('|')}|`)
for (const id of ids) {
const hit = byId(id)
const cls = hit[0]?.[1].cls ?? ''
const by = new Map(hit)
out.push(`| ${id} | ${cls} | ${rows.map(r => (by.has(r.name) ? cell(by.get(r.name).status) : '·')).join(' | ')} |`)
}
out.push('')
out.push('## Failures by probe')
out.push('')
for (const id of ids) {
const bad = byId(id).filter(([, x]) => x.status === 'fail' || x.status === 'info')
if (bad.length === 0) continue
const tier = bad[0][1].status === 'info' ? 'advisory' : 'must'
const applicable = byId(id).length
const clean = byId(id).filter(([, x]) => x.status === 'pass').map(([n]) => n)
const who = bad.length > applicable / 2 ? `all but ${clean.join(', ') || 'none'}` : bad.map(([n, x]) => `${n}${x.ms > args.budgetMs ? ` ${x.ms.toFixed(0)}ms` : ''}`).join(', ')
out.push(`- **${id}** (${tier}) — ${bad.length}/${applicable}: ${who}`)
}
out.push('')
return out.join('\n')
}
+143
View File
@@ -0,0 +1,143 @@
// Worker for scripts/probe-mod-guards.mjs: loads one bundled guard, finds the tool/input shapes it actually bites on
// ("calibration"), then streams one result per probe to the parent. Runs in a worker thread so the parent can kill a hung regex.
import { parentPort, workerData } from 'node:worker_threads'
import { pathToFileURL } from 'node:url'
import { performance } from 'node:perf_hooks'
import { ALL_SECRETS, BENIGN, SECRET_TYPES, evasionProbes, stressProbes } from './guard-probe-corpus.mjs'
const { bundle, tools, servers, extra, fast, skip, budgetMs } = workerData
const done = new Set(skip)
const send = m => parentPort.postMessage(m)
const mod = await import(pathToFileURL(bundle).href)
const verdictFn = mod.guard?.verdict
if (typeof verdictFn !== 'function') {
send({ type: 'fatal', error: 'guard.ts exports no verdict()' })
process.exit(0)
}
const opts0 = (() => { try { return mod.options?.readOptions?.({}) ?? { guard: true } } catch { return { guard: true } } })()
const call = (tool, input) => {
const stats = (() => { try { return mod.status?.newStats?.() ?? {} } catch { return {} } })()
const t0 = performance.now()
try {
return { v: verdictFn(tool, input, { ...opts0 }, stats), ms: performance.now() - t0 }
} catch (e) {
return { err: `${e?.name ?? 'Error'}: ${String(e?.message ?? e).slice(0, 80)}`, ms: performance.now() - t0 }
}
}
const refused = r => r.err === undefined && r.v !== undefined && r.v !== null && r.v !== 'pass' && r.v !== false
const FIELDS = ['value', 'content', 'text', 'message', 'command', 'script', 'args', 'data', 'description', 'query', 'prompt', 'body', 'url', 'title']
const NAMESPACES = [undefined, 'adr', 'docs', 'document', 'federation', 'patterns', 'pattern', 'market-data', 'learning', 'agent', 'goals', 'trading', 'cost-tracking', 'observability', 'migrations', 'rvf', 'sparc', 'tests', 'security', 'knowledge-graph', 'workflows', 'iot-devices', 'ddd-model', 'music']
const base = ns => ({ action: 'create', ...(ns === undefined ? {} : { namespace: ns, key: ns }) })
const superInput = (ns, p) => ({ ...base(ns), ...Object.fromEntries(FIELDS.map(f => [f, p])), input: Object.fromEntries(FIELDS.map(f => [f, p])) })
const carriers = ns => [
...FIELDS.map(f => ({ field: f, make: p => ({ ...base(ns), [f]: p }) })),
...['value', 'content', 'text', 'command'].map(f => ({ field: `input.${f}`, make: p => ({ ...base(ns), input: { [f]: p } }) })),
]
const forms = (() => {
const out = []
for (const t of tools) {
out.push(t, `mcp__plugin_ruflo-core_ruflo__${t}`)
for (const s of servers) out.push(`mcp__${s}__${t}`)
}
return [...new Set(out)]
})()
/** Every (tool, namespace, field) the guard refuses a secret on while letting benign text through. */
function calibrate() {
const found = []
const pool = [...forms.map(tool => ({ tool })), ...extra.map(e => ({ tool: e.tool, fixed: e }))]
for (const { tool, fixed } of pool) {
for (const ns of fixed ? [fixed.namespace] : NAMESPACES) {
if (!refused(call(tool, superInput(ns, ALL_SECRETS)))) continue
for (const c of carriers(ns)) {
if (!refused(call(tool, c.make(ALL_SECRETS)))) continue
if (BENIGN.some(b => refused(call(tool, c.make(b))))) continue
const types = Object.keys(SECRET_TYPES).filter(t => refused(call(tool, c.make(SECRET_TYPES[t]()))))
if (types.length > 0) found.push({ tool, ns, field: c.field, types, make: c.make })
}
}
}
return found
}
const pairs = calibrate()
const bareOf = t => (t.startsWith('mcp__') ? t.slice(t.lastIndexOf('__') + 2) : t)
const chosen = []
for (const p of pairs) if (chosen.length < 3 && !chosen.some(c => bareOf(c.tool) === bareOf(p.tool))) chosen.push(p)
const describe = p => `${bareOf(p.tool)}${p.ns ? `[${p.ns}]` : ''}.${p.field}`
// Timing/crash targets: the calibrated shapes (one field each, so a legitimate linear scan is not multiplied), else every
// (tool, namespace) pair with the payload in every field at once.
const timing = pairs.length > 0
? pairs.slice(0, 24).map(p => ({ tool: p.tool, ns: p.ns, make: p.make }))
: [...new Set(forms)].flatMap(tool => NAMESPACES.map(ns => ({ tool, ns, make: x => superInput(ns, x) })))
send({ type: 'meta', calibrated: pairs.length, chosen: chosen.map(c => ({ at: describe(c), types: c.types })), timingTargets: timing.length })
const big = fast ? 64 * 1024 : 1024 * 1024
const reps = 3
const record = (probe, status, ms, note) => send({ type: 'result', id: probe.id, cls: probe.cls, tier: probe.tier, status, ms: Math.round(ms * 100) / 100, note })
const begin = probe => send({ type: 'start', id: probe.id })
for (const probe of evasionProbes(big)) {
if (done.has(probe.id)) continue
begin(probe)
if (chosen.length === 0) { record(probe, 'skip', 0, 'no secret surface found'); continue }
const failures = []
let worst = 0
for (const c of chosen) {
const payload = probe.build(SECRET_TYPES[c.types[0]]())
const r = call(c.tool, c.make(payload))
worst = Math.max(worst, r.ms)
if (r.err) failures.push(`${describe(c)} threw ${r.err}`)
else if (!refused(r)) failures.push(`${describe(c)} let ${c.types[0]} through`)
}
record(probe, failures.length === 0 ? 'pass' : probe.tier === 'advisory' ? 'info' : 'fail', worst, failures.join('; '))
}
for (const probe of stressProbes(big)) {
if (done.has(probe.id)) continue
begin(probe)
const payload = probe.build()
let worst = 0
const bad = []
for (const t of timing) {
const input = t.make(payload)
let best = Infinity
let err
for (let i = 0; i < reps; i++) {
const r = call(t.tool, input)
if (r.err) { err = r.err; best = Math.min(best, r.ms); break }
best = Math.min(best, r.ms)
if (best * 2 < budgetMs) break // clearly inside the budget: re-measure only the calls that are near it
}
if (best > worst) worst = best
if (err) bad.push(`${bareOf(t.tool)}${t.ns ? `[${t.ns}]` : ''} threw ${err}`)
else if (best > budgetMs) bad.push(`${bareOf(t.tool)}${t.ns ? `[${t.ns}]` : ''} ${best.toFixed(0)}ms`)
if (bad.length >= 3) break // three offenders prove the point; do not sweep every shape of a guard that is slow everywhere
}
record(probe, bad.length === 0 ? 'pass' : 'fail', worst, [...new Set(bad)].slice(0, 3).join('; '))
}
// Benign look-alikes must pass on every calibrated shape.
{
const probe = { id: 'benign-lookalikes', cls: 'benign', tier: 'must' }
begin(probe)
if (chosen.length === 0) record(probe, 'skip', 0, 'no secret surface found')
else {
const bad = []
let worst = 0
for (const c of pairs.slice(0, 12)) {
BENIGN.forEach((b, i) => {
const r = call(c.tool, c.make(b))
worst = Math.max(worst, r.ms)
if (r.err || refused(r)) bad.push(`#${i} on ${describe(c)}`)
})
}
record(probe, bad.length === 0 ? 'pass' : 'fail', worst, bad.slice(0, 3).join('; '))
}
}
send({ type: 'done' })
+886
View File
@@ -0,0 +1,886 @@
{
"ruflo-adr": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-agent": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-agentdb": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-ai-team": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-aidefence": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-autopilot": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-bbs-federation": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-daa": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-ddd": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-docs": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-federation": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-goals": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-graph-intelligence": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-intelligence": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-iot-cognitum": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-jujutsu": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-knowledge-graph": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-loop-workers": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-market-data": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-metaharness": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-migrations": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-music": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-neural-trader": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-observability": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-rag-memory": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-ruvector": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-ruvllm": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-rvf": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-security-audit": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-sparc": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-workflows": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
],
"ruflo-x-gateway": [
"shy-split",
"bidi-isolate",
"mongolian-vs",
"nest-obj-6",
"nest-obj-12",
"nest-obj-60",
"nest-obj-5000",
"nest-arr-6",
"nest-arr-12",
"nest-arr-60",
"nest-arr-5000",
"nest-mix-6",
"nest-mix-12",
"nest-mix-60",
"nest-mix-5000",
"wide-array-250",
"wide-object-250",
"wide-array-5000",
"key-name",
"key-name-nested-3",
"key-name-in-array",
"huge-middle",
"huge-end",
"after-25k-padding",
"after-200k-padding",
"s-wide-keys-100k"
]
}
+197
View File
@@ -0,0 +1,197 @@
#!/usr/bin/env node
// probe-mod-guards — adversarial fleet probe for every plugins/*/hooks/guard.ts verdict function.
//
// Each guard is bundled with esbuild into a private temp dir, then run in a worker thread through a fixed corpus (see
// scripts/lib/guard-probe-corpus.mjs): secrets split by invisible/bidi characters, nested 5000 levels, in object keys, 1 MB strings,
// regex-DoS inputs (each call is timed; > --budget-ms fails), encodings, and benign look-alikes (must pass). A worker that hangs is
// killed by a watchdog and reported as a failure of the probe it was in.
//
// USAGE
// node scripts/probe-mod-guards.mjs # full corpus, table, exit 1 on any `must` failure
// node scripts/probe-mod-guards.mjs --fast # 64 KB inputs, one timing run: the smoke-path subset
// node scripts/probe-mod-guards.mjs --only ruflo-adr,ruflo-rvf --verbose
// node scripts/probe-mod-guards.mjs --known-holes scripts/probe-mod-guards.known-holes.json # only NEW holes fail
// node scripts/probe-mod-guards.mjs --write-known-holes <file> # record current holes
// node scripts/probe-mod-guards.mjs --report v3/docs/validation/mod-guard-probe-2026-10.md # write the markdown report
// node scripts/probe-mod-guards.mjs --strict # advisory (encoding/confusable) misses fail too
// node scripts/probe-mod-guards.mjs --plugins-dir <dir> # probe <dir>/*/hooks/guard.ts (used by the tests)
//
// Exit codes: 0 ok · 1 a guard failed a `must` probe (beyond --known-holes) · 2 config error · 3 nothing to probe
import { execFileSync } from 'node:child_process'
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'node:fs'
import { tmpdir, cpus } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { Worker } from 'node:worker_threads'
import { renderReport } from './lib/guard-probe-report.mjs'
const HERE = dirname(fileURLToPath(import.meta.url))
const REPO = dirname(HERE)
const WORKER = join(HERE, 'lib', 'guard-probe-worker.mjs')
const args = (() => {
const a = { fast: false, format: 'table', only: null, verbose: false, pluginsDir: join(REPO, 'plugins'), report: null, knownHoles: null, writeKnownHoles: null, strict: false, budgetMs: 50, watchdogMs: null, concurrency: Math.max(1, Math.min(4, cpus().length >> 1)) }
const argv = process.argv.slice(2)
const need = i => { if (argv[i + 1] === undefined) { console.error(`probe-mod-guards: ${argv[i]} needs a value`); process.exit(2) } return argv[i + 1] }
for (let i = 0; i < argv.length; i++) {
const v = argv[i]
if (v === '--fast') a.fast = true
else if (v === '--verbose') a.verbose = true
else if (v === '--strict') a.strict = true
else if (v === '--format') a.format = need(i++)
else if (v === '--only') a.only = new Set(need(i++).split(',').map(s => s.trim()).filter(Boolean))
else if (v === '--plugins-dir') a.pluginsDir = resolve(need(i++))
else if (v === '--report') a.report = resolve(need(i++))
else if (v === '--known-holes') a.knownHoles = resolve(need(i++))
else if (v === '--write-known-holes') a.writeKnownHoles = resolve(need(i++))
else if (v === '--budget-ms') a.budgetMs = Number(need(i++))
else if (v === '--watchdog-ms') a.watchdogMs = Number(need(i++))
else if (v === '--concurrency') a.concurrency = Number(need(i++))
else { console.error(`probe-mod-guards: unknown argument ${v}`); process.exit(2) }
}
if (!['table', 'json'].includes(a.format) || !(a.budgetMs > 0) || !(a.concurrency >= 1)) { console.error('probe-mod-guards: bad --format, --budget-ms or --concurrency'); process.exit(2) }
a.watchdogMs ??= a.fast ? 10_000 : 30_000
return a
})()
/** esbuild from the repo, else from the main checkout when this is a worktree with no node_modules. */
function findEsbuild() {
if (process.env.ESBUILD) return process.env.ESBUILD
const roots = [REPO]
try { roots.push(dirname(resolve(REPO, execFileSync('git', ['rev-parse', '--git-common-dir'], { cwd: REPO, encoding: 'utf8' }).trim()))) } catch { /* not a git checkout */ }
for (const r of roots) { const p = join(r, 'node_modules', '.bin', 'esbuild'); if (existsSync(p)) return p }
console.error('probe-mod-guards: esbuild not found (set ESBUILD=/path/to/esbuild)')
process.exit(2)
}
// Shapes the generic calibration cannot guess: servers a guard matches by substring, and tools matched by prefix/regex.
const SERVERS = ['plugin_ruflo-music_cogmusic', 'plugin_ruflo-core_ruflo', 'ruflo', 'claude-flow', 'ruflo-federation', 'plugin_ruflo-chatgpt-federation_chatgpt']
const STATIC_TOOLS = ['memory_store', 'agentdb_hierarchical-store', 'agentdb_causal-edge', 'agentdb_pattern-store', 'agentdb_batch', 'hooks_intelligence_pattern-store', 'hooks_transfer', 'hooks_worker-dispatch', 'sublinear_solve', 'channel_publish', 'workflow_create', 'federation_bbs_publish', 'browser_eval', 'browser_open', 'aidefence_learn', 'autopilot_log', 'analyze_diff', 'metaharness_flywheel', 'create_production', 'embeddings_generate', 'ruvllm_hnsw_add', 'neural_train', 'daa_knowledge_share', 'github_pr_manage', 'task_create', 'terminal_execute']
function toolsOf(guardSrc) {
const out = new Set(STATIC_TOOLS)
for (const m of guardSrc.matchAll(/['"`]([a-z][a-z0-9]*(?:[_-][a-z0-9]+)+)['"`]/g)) out.add(m[1])
for (const m of guardSrc.matchAll(/(?:startsWith|includes)\((['"`])([a-z][a-z0-9_-]*[_-])\1\)/g)) out.add(`${m[2]}x`)
for (const m of guardSrc.matchAll(/\^([a-z][a-z0-9]*)\[\/_-\]/g)) out.add(`${m[1]}_x`)
for (const m of guardSrc.matchAll(/['"`]([a-z][a-z0-9]*_)['"`]/g)) out.add(`${m[1]}x`)
return [...out]
}
function discover() {
if (!existsSync(args.pluginsDir)) return []
return readdirSync(args.pluginsDir)
.filter(n => existsSync(join(args.pluginsDir, n, 'hooks', 'guard.ts')) && (!args.only || args.only.has(n)))
.sort()
.map(name => ({ name, dir: join(args.pluginsDir, name, 'hooks') }))
}
function bundle(esbuild, plugin, tmp) {
const entry = join(tmp, `${plugin.name}.entry.ts`)
const lines = [`export * as guard from ${JSON.stringify(join(plugin.dir, 'guard.ts'))}`]
for (const f of ['options', 'status']) if (existsSync(join(plugin.dir, `${f}.ts`))) lines.push(`export * as ${f} from ${JSON.stringify(join(plugin.dir, `${f}.ts`))}`)
writeFileSync(entry, lines.join('\n') + '\n')
const out = join(tmp, `${plugin.name}.mjs`)
execFileSync(esbuild, [entry, '--bundle', '--platform=node', '--format=esm', `--outfile=${out}`, '--log-level=error'], { stdio: ['ignore', 'pipe', 'pipe'] })
return out
}
/** Run one plugin's probes in a worker; a hung or crashed probe becomes a failure and the worker is restarted after it. */
async function runPlugin(plugin, bundled) {
const guardSrc = readFileSync(join(plugin.dir, 'guard.ts'), 'utf8')
const named = [...guardSrc.matchAll(/includes\((['"`])([a-z][^'"`]*)\1\)/g)].map(m => m[2])
const job = { bundle: bundled, tools: toolsOf(guardSrc), servers: [...SERVERS, ...named], extra: [], fast: args.fast, budgetMs: args.budgetMs }
const results = []
const skip = []
let meta = { calibrated: 0, chosen: [], timingTargets: 0 }
for (let restarts = 0; restarts < 12; restarts++) {
let current
const outcome = await new Promise(res => {
const w = new Worker(WORKER, { workerData: { ...job, skip }, resourceLimits: { maxOldGenerationSizeMb: 3072 } })
let timer
const arm = () => { clearTimeout(timer); timer = setTimeout(() => { void w.terminate(); res({ kind: 'hang' }) }, args.watchdogMs) }
arm()
w.on('message', m => {
arm()
if (m.type === 'start') current = m.id
else if (m.type === 'result') { results.push(m); current = undefined }
else if (m.type === 'meta') meta = m
else if (m.type === 'fatal') { clearTimeout(timer); res({ kind: 'fatal', error: m.error }) }
else if (m.type === 'done') { clearTimeout(timer); res({ kind: 'done' }) }
})
w.on('error', e => { clearTimeout(timer); res({ kind: 'crash', error: `${e?.name}: ${String(e?.message ?? e).slice(0, 100)}` }) })
w.on('exit', () => { clearTimeout(timer); res({ kind: 'exit' }) })
})
if (outcome.kind === 'done') return { meta, results }
if (outcome.kind === 'fatal') return { meta, results, fatal: outcome.error }
if (current === undefined) return { meta, results, fatal: outcome.kind === 'hang' ? `worker hung > ${args.watchdogMs}ms before the first probe` : (outcome.error ?? 'worker exited early') }
const note = outcome.kind === 'hang' ? `HANG: no answer within ${args.watchdogMs}ms (worker killed)` : `worker ${outcome.kind}: ${outcome.error ?? 'exited'}`
results.push({ id: current, cls: current.startsWith('s-') ? 'stress' : 'evade', tier: 'must', status: 'fail', ms: args.watchdogMs, note })
skip.push(current, ...results.map(r => r.id))
}
return { meta, results, fatal: 'too many worker restarts' }
}
async function pool(items, n, fn) {
const out = new Array(items.length)
let next = 0
await Promise.all(Array.from({ length: Math.min(n, items.length) }, async () => { while (next < items.length) { const i = next++; out[i] = await fn(items[i]) } }))
return out
}
const plugins = discover()
if (plugins.length === 0) { console.error(`probe-mod-guards: no ${args.pluginsDir}/*/hooks/guard.ts found`); process.exit(3) }
const esbuild = findEsbuild()
const tmp = mkdtempSync(join(tmpdir(), 'probe-mod-guards-'))
const started = Date.now()
let rows
try {
rows = await pool(plugins, args.concurrency, async p => {
try { return { name: p.name, ...(await runPlugin(p, bundle(esbuild, p, tmp))) } } catch (e) {
return { name: p.name, meta: { calibrated: 0, chosen: [], timingTargets: 0 }, results: [], fatal: `bundle failed: ${String(e?.stderr ?? e?.message ?? e).slice(0, 200)}` }
}
})
} finally { rmSync(tmp, { recursive: true, force: true }) }
// A hole is a failing `must` probe (or any failing/info probe under --strict), identified as "<plugin>:<probe>".
const isHole = r => r.status === 'fail' || (args.strict && r.status === 'info')
const holes = rows.flatMap(r => r.results.filter(isHole).map(x => `${r.name}:${x.id}`))
const known = args.knownHoles && existsSync(args.knownHoles) ? new Set(Object.entries(JSON.parse(readFileSync(args.knownHoles, 'utf8'))).flatMap(([p, ids]) => ids.map(i => `${p}:${i}`))) : new Set()
const fresh = holes.filter(h => !known.has(h))
const ran = (k) => { const i = k.indexOf(':'); return rows.find(r => r.name === k.slice(0, i))?.results.some(x => x.id === k.slice(i + 1) && !isHole(x)) }
const fixed = [...known].filter(ran)
const fatal = rows.filter(r => r.fatal)
if (args.writeKnownHoles) {
const byPlugin = {}
for (const h of holes) { const i = h.indexOf(':'); (byPlugin[h.slice(0, i)] ??= []).push(h.slice(i + 1)) }
mkdirSync(dirname(args.writeKnownHoles), { recursive: true })
writeFileSync(args.writeKnownHoles, JSON.stringify(byPlugin, null, 1) + '\n')
}
if (args.report) {
mkdirSync(dirname(args.report), { recursive: true })
writeFileSync(args.report, renderReport({ rows, args: { fast: args.fast, budgetMs: args.budgetMs }, startedMs: started }))
}
if (args.format === 'json') {
console.log(JSON.stringify({ rows, holes, newHoles: fresh, fixedKnownHoles: fixed, fatal: fatal.map(f => ({ plugin: f.name, error: f.fatal })) }, null, 1))
} else {
const pad = (s, n) => String(s).padEnd(n)
console.log(`probe-mod-guards: ${rows.length} guards, ${args.fast ? 'fast' : 'full'} corpus, budget ${args.budgetMs}ms/call, ${((Date.now() - started) / 1000).toFixed(1)}s\n`)
console.log(`${pad('plugin', 26)} ${pad('surface (tool.field)', 44)} ${pad('pass', 5)} ${pad('fail', 5)} ${pad('info', 5)} ${pad('skip', 5)} worst-ms`)
for (const r of rows) {
const c = s => r.results.filter(x => x.status === s).length
const worst = r.results.reduce((m, x) => (x.status === 'skip' ? m : x.ms > m.ms ? x : m), { ms: 0, id: '-' })
const surface = r.meta.chosen.length ? r.meta.chosen.map(x => x.at).join(', ') : r.fatal ? `FATAL ${r.fatal}` : 'none (policy guard / no secret surface)'
console.log(`${pad(r.name, 26)} ${pad(surface.length > 43 ? `${surface.slice(0, 42)}…` : surface, 44)} ${pad(c('pass'), 5)} ${pad(c('fail'), 5)} ${pad(c('info'), 5)} ${pad(c('skip'), 5)} ${worst.ms.toFixed(1)} (${worst.id})`)
for (const x of r.results) {
if (args.verbose || (isHole(x) && !known.has(`${r.name}:${x.id}`))) console.log(` ${pad(x.status.toUpperCase(), 5)} ${pad(x.id, 24)} ${pad(x.ms.toFixed(1) + 'ms', 10)} ${(x.note ?? '').slice(0, 120)}`)
}
}
const total = rows.reduce((n, r) => n + r.results.length, 0)
console.log(`\n${total} probe runs · ${holes.length} holes (${holes.length - fresh.length} known, ${fresh.length} new) · ${fixed.length} known holes now fixed · ${fatal.length} fatal`)
if (fixed.length) console.log(`known holes that now pass (remove from the baseline): ${fixed.slice(0, 10).join(', ')}${fixed.length > 10 ? ', …' : ''}`)
if (fatal.length) for (const f of fatal) console.log(` FATAL ${f.name}: ${f.fatal}`)
}
process.exitCode = fresh.length > 0 || fatal.length > 0 ? 1 : 0 // not process.exit(): it can truncate a large JSON write to a pipe
+31 -1
View File
@@ -8,6 +8,11 @@
// node scripts/smoke-all-plugins.mjs --format json # machine-readable
// node scripts/smoke-all-plugins.mjs --only ruflo-agent,ruflo-cost-tracker
// node scripts/smoke-all-plugins.mjs --skip ruflo-iot-cognitum
// node scripts/smoke-all-plugins.mjs --skip-guard-probe # omit the final guard-probe step
//
// After the per-plugin smokes, `scripts/probe-mod-guards.mjs --fast` runs every hooks/guard.ts through the adversarial corpus. It
// fails only on holes NOT listed in scripts/probe-mod-guards.known-holes.json, so a regression (or a new guard copied from a
// capped template) fails the run while the recorded backlog does not.
//
// CI integration:
// - name: All-plugin smoke contracts
@@ -20,7 +25,7 @@
// 3 no smoke scripts found (likely repo-layout drift — fail closed)
import { readdirSync, existsSync, statSync } from 'node:fs';
import { spawn } from 'node:child_process';
import { spawn, spawnSync } from 'node:child_process';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -36,6 +41,7 @@ const ARGS = (() => {
skip: new Set(),
timeoutSec: 120, // per-plugin hard cap — prevents a hung smoke from deadlocking the run
failFast: false, // if true, kill remaining smokes on first failure
guardProbe: true, // final step: fast adversarial probe of every hooks/guard.ts
};
for (let i = 2; i < process.argv.length; i++) {
const v = process.argv[i];
@@ -43,6 +49,7 @@ const ARGS = (() => {
else if (v === '--format') a.format = process.argv[++i];
else if (v === '--timeout') a.timeoutSec = parseFloat(process.argv[++i]);
else if (v === '--fail-fast') a.failFast = true;
else if (v === '--skip-guard-probe') a.guardProbe = false;
else if (v === '--only') {
a.only = new Set((process.argv[++i] || '').split(',').map((s) => s.trim()).filter(Boolean));
} else if (v === '--skip') {
@@ -155,6 +162,27 @@ function runSmoke(plugin, abortSignal) {
});
}
/** The guard probe as one more smoke row: ok unless a guard has a hole that is not in the known-holes baseline. */
function runGuardProbe() {
const probe = join(SCRIPTS_DIR, 'probe-mod-guards.mjs');
const baseline = join(SCRIPTS_DIR, 'probe-mod-guards.known-holes.json');
const args = [probe, '--fast', '--format', 'json', '--known-holes', baseline];
if (ARGS.only) args.push('--only', [...ARGS.only].join(','));
const t0 = Date.now();
const r = spawnSync(process.execPath, args, { encoding: 'utf8', maxBuffer: 256 * 1024 * 1024, timeout: 10 * 60 * 1000 });
const row = { name: 'guard-probe (fast)', exitCode: r.status ?? 1, ok: r.status === 0, timedOut: r.error?.code === 'ETIMEDOUT', aborted: false, terminationReason: null, passed: null, failed: null, durationMs: Date.now() - t0, failingSteps: [], stderrTail: (r.stderr || '').slice(-400) };
try {
const out = JSON.parse(r.stdout);
row.passed = out.rows.reduce((n, x) => n + x.results.filter((y) => y.status === 'pass').length, 0);
row.failed = out.newHoles.length + out.fatal.length;
row.failingSteps = [...out.newHoles, ...out.fatal.map((f) => `${f.plugin}: ${f.error}`)].slice(0, 6).map((step) => ({ step }));
} catch {
if (r.status === 3) { row.ok = true; row.terminationReason = 'no guards found'; } // nothing to probe is not a failure here
else if (!row.ok) row.terminationReason = 'probe produced no JSON';
}
return row;
}
async function main() {
const plugins = discoverPlugins();
if (plugins.length === 0) {
@@ -195,6 +223,8 @@ async function main() {
results = await Promise.all(pending);
}
if (ARGS.guardProbe && !abortController?.signal.aborted) results.push(runGuardProbe());
const okCount = results.filter((r) => r.ok).length;
const failCount = results.length - okCount;
const totalSteps = results.reduce((s, r) => s + (r.passed || 0) + (r.failed || 0), 0);
@@ -0,0 +1,268 @@
# Mod guard fleet probe — 2026-10
Adversarial probe of every `plugins/*/hooks/guard.ts` verdict function (37 guards), run by `node scripts/probe-mod-guards.mjs`
(full corpus; `--fast` is the subset wired into `scripts/smoke-all-plugins.mjs`). No plugin code was changed: this report records
what the probe found. The section below the marker is generated; the **Findings** section is hand-written from it (regenerate with
`--report <tmp>` and splice, do not overwrite this file blindly).
## Method
- Each guard is bundled with esbuild into a private temp dir and run in a worker thread; a watchdog kills a hung probe and reports it.
- **Calibration** finds where each guard actually bites: it tries every tool name the guard source mentions (plus a static list) under
several namespaces/fields, and keeps a (tool, namespace, field) only if a secret is refused **and** the benign corpus passes. Every
evasion probe is therefore a mutation of a payload the same guard *does* refuse in the same slot, so a miss is a real bypass, not a
guard that never looked there. Up to 3 distinct tools per plugin are probed.
- Corpus (97 probes): secrets split by zero-width/bidi/soft-hyphen characters; nested 3 to 5000 levels (object, array, mixed); wide arrays
and objects; secrets as object keys; 1 MB strings with the secret at the start, middle, end, and after 25 k / 200 k of padding;
JSON-in-string (plain, double-encoded, `\u` escaped); base64, hex, URL-encoding; fullwidth and Cyrillic confusables; 36 stress inputs
(1 MB near-miss secrets, `-----BEGIN` runs, `curl ` repeats, 100 000-deep nesting, 100 000-wide arrays and objects) timed per call.
- Gate: a call over 50 ms (best of up to 3 runs) fails; a throw fails; a hang fails; benign look-alikes (10 shapes) must pass.
- Tiers: `must` fails the run. `advisory` (base64, hex, URL-encoding, `\u`-escaped JSON, fullwidth/Cyrillic confusables) is an encoding a
pattern guard is not designed to decode; it is reported and fails only under `--strict`.
## Headline numbers (full corpus, 37 guards, 2 997 probe runs)
| result | count |
|---|---:|
| pass | 1 760 |
| fail (`must`) | 820 |
| advisory miss | 192 |
| skipped (no secret surface to mutate) | 225 |
- 32 of 37 guards have a secret surface; 5 are policy-only (`ruflo-browser`, `ruflo-business-pods`, `ruflo-chatgpt-federation`,
`ruflo-deepseek-harness`) or observe-only (`ruflo-testgen`, never refuses). They get the stress and crash probes only (36 pass, 45 skipped
each); their URL/path/command policy rules are **not** covered by the secret-evasion corpus.
- **No regex-DoS, no crash, no hang.** Across all 37 guards the slowest stress call outside the wide-object case was 15.9 ms
(`ruflo-browser`, 1 MB of zero-width spaces); no guard threw on 5000- or 100 000-deep input; no probe hung.
- The 820 failures are 4 root causes, each present in all 32 guards that share the copied `textsOf` + `screen.ts` template.
## Findings
Reproducers run against the real `ruflo-adr` bundle (`namespace: 'adr'`, `memory_store`, value as shown; `tok` is a GitHub-token-shaped
string built at runtime). `refused` is the correct answer; `LET THROUGH` is the hole.
| # | hole | reproducer (value) | result | guards |
|---|---|---|---|---|
| 1 | **Depth cap fails open.** `textsOf` returns `[]` past depth 6 | `{k:{k:{k:{k:{k:{k:tok}}}}}}` (secret at depth 7); also 12, 60, 5000, arrays, mixed | LET THROUGH (3 levels: refused) | 32 |
| 2 | **Sibling cap fails open.** `slice(0, 200)` on arrays and `Object.values` | `[...200 × 'x', tok]`; object with 250 keys, secret last; 5000-element array | LET THROUGH | 32 |
| 3 | **Character budget fails open.** `budget.left = 20_000`, and `hasSecret` slices to 20 000 chars | `'lorem '.repeat(4200) + tok`; same at 1 MB (middle and end). Secret at the start of 1 MB is caught | LET THROUGH | 32 |
| 4 | **Object keys are never read.** `Object.values` only | `{ [tok]: 'v' }`, the same nested 3 deep, or inside an array of objects | LET THROUGH | 28 (the four that scan keys: `ruflo-migrations`, `ruflo-music`, `ruflo-neural-trader`, `ruflo-observability`) |
| 5 | **Invisible-character class is incomplete.** The strip set covers C0/C1 controls, U+200B-U+200F, U+2028-U+202E, U+2060-U+2064 and U+FEFF, and nothing else | the token with U+00AD (soft hyphen) inserted after its third character; with U+2067 U+2069 (bidi isolates); with U+180E (Mongolian vowel separator) | LET THROUGH (U+200B, U+200D, U+FEFF, U+2060, U+202E are all refused) | 32 |
| 6 | **Object with 100 000 keys costs 60–184 ms.** `Object.values(input)` allocates every value before `.slice(0, 200)` | `Object.fromEntries(100 000 × ['k'+i,'x'])` | 63.6–68.3 ms on `ruflo-adr`; 60.5–184.2 ms across the 32 (worst `ruflo-bbs-federation`) | 32 |
Repro for 1 to 5 (about 12 lines, runs standalone after bundling `plugins/ruflo-adr/hooks/guard.ts` with esbuild):
```js
import { verdict } from './adr.mjs'
const tok = 'gh' + 'p_' + 'A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8'
const call = value => verdict('mcp__plugin_ruflo-core_ruflo__memory_store', { namespace: 'adr', value }) === undefined ? 'LET THROUGH' : 'refused'
const nest = (leaf, d) => { let v = leaf; for (let i = 0; i < d; i++) v = { k: v }; return v }
call(tok) // refused (control)
call(nest(tok, 3)) // refused
call(nest(tok, 6)) // LET THROUGH (#1)
call([...Array(200).fill('x'), tok]) // LET THROUGH (#2)
call('lorem '.repeat(4200) + tok) // LET THROUGH (#3)
call({ [tok]: 'v' }) // LET THROUGH (#4)
call(tok.slice(0, 3) + String.fromCharCode(0xad) + tok.slice(3)) // LET THROUGH (#5)
```
Why these matter: every cap is a fail-open. A caller (or a model told to work around the guard) pads, nests or widens the
payload until the secret sits past the cap, and the write is allowed. The fix pattern is the same everywhere: scan iteratively with no
depth/sibling cap, scan keys as well as values, and when a work limit is hit **refuse** (fail closed) instead of returning "nothing
found"; strip `\p{Cf}` plus U+00AD, U+034F and U+180E rather than a hand-listed range; iterate keys with `for…in` and stop at the
limit instead of materialising `Object.values`.
### Advisory (not failures; 192 misses, 6 probes × 32 guards)
`base64`, `hex`, `url-encoded`, `json-unicode-escape` (`"ghp_…"`), `fullwidth-confusable`, `cyrillic-confusable`. A pattern guard
does not decode, so every guard misses these. They are only a risk when the destination decodes before use; decide per tool whether to
add a decode pass. Run `--strict` to make them fail.
### What passed everywhere
Plain secrets, secrets in JSON-in-string and double-encoded JSON, zero-width space/joiner/non-joiner, BOM, word joiner, bidi override
and LRM wrapping, depth ≤ 6 nesting, every stress input, and all 10 benign look-alikes (no false positives on `sk-learn`, a SHA-256
digest, a UUID, "api key rotation ticket", prose about JWTs).
### Not covered
- The 5 policy-only guards' own rules (URL scheme, template path traversal, public channel names, destructive-command patterns).
- Real host input shapes: calibration uses synthetic `{namespace, value}`-style carriers, not captured tool calls. A guard that reads a
field the carrier list does not contain would show as "no secret surface" rather than a hole.
- `ruflo-iot-cognitum`'s Bash-command path (it needs a `cognitum-iot` prefix the generic carriers do not produce); only its
`memory_store` path is probed.
## Gate
`scripts/probe-mod-guards.known-holes.json` records the 820 current holes. `smoke-all-plugins.mjs` runs `--fast --known-holes` last, so a
**new** hole (a regression, or a new guard copied from the capped template) fails the smoke while this backlog does not. Fixed holes
print as "known holes that now pass"; remove them from the baseline. Fix a root cause, rerun `--write-known-holes`, commit the shrink.
<!-- generated below: node scripts/probe-mod-guards.mjs --report <path> -->
Generated by `node scripts/probe-mod-guards.mjs` in 17.4s. Timing gate: 50 ms per call (best of 3 runs).
## Per-plugin summary
| plugin | secret surface found (tool[namespace].field) | pass | fail | advisory miss | skipped | worst ms (probe) |
|---|---|---:|---:|---:|---:|---|
| ruflo-adr | `memory_store[adr].value`<br>`agentdb_hierarchical-store[adr].value`<br>`agentdb_causal-edge[adr].value` | 49 | 26 | 6 | 0 | 80.8 (s-wide-keys-100k) |
| ruflo-agent | `wasm_agent_prompt.value`<br>`wasm_agent_tool.value`<br>`wasm_agent_create.value` | 49 | 26 | 6 | 0 | 77.9 (s-wide-keys-100k) |
| ruflo-agentdb | `memory_store.value`<br>`agentdb_hierarchical-store.value`<br>`agentdb_causal-edge.value` | 49 | 26 | 6 | 0 | 83.7 (s-wide-keys-100k) |
| ruflo-ai-team | `task_create.value`<br>`memory_remember.value`<br>`task_update.value` | 49 | 26 | 6 | 0 | 82.3 (s-wide-keys-100k) |
| ruflo-aidefence | `aidefence_learn.value` | 49 | 26 | 6 | 0 | 79.6 (s-wide-keys-100k) |
| ruflo-autopilot | `autopilot_log.value`<br>`autopilot_learn.value`<br>`autopilot_config.value` | 49 | 26 | 6 | 0 | 90.2 (s-wide-keys-100k) |
| ruflo-bbs-federation | `federation_bbs_publish.value`<br>`federation_bbs_human_join.value` | 49 | 26 | 6 | 0 | 184.2 (s-wide-keys-100k) |
| ruflo-browser | none (policy guard) | 36 | 0 | 0 | 45 | 15.9 (s-zwsp) |
| ruflo-business-pods | none (policy guard) | 36 | 0 | 0 | 45 | 1.4 (s-emoji) |
| ruflo-chatgpt-federation | none (policy guard) | 36 | 0 | 0 | 45 | 0.4 (s-assign-spaces) |
| ruflo-daa | `daa_knowledge_share.value`<br>`daa_x.value` | 49 | 26 | 6 | 0 | 91.7 (s-wide-keys-100k) |
| ruflo-ddd | `memory_store[ddd-model].value`<br>`agentdb_hierarchical-store[ddd-model].value` | 49 | 26 | 6 | 0 | 79.3 (s-wide-keys-100k) |
| ruflo-deepseek-harness | none (policy guard) | 36 | 0 | 0 | 45 | 0.0 (s-pem-spaces) |
| ruflo-docs | `memory_store[docs].value`<br>`hooks_worker-dispatch[document].value` | 49 | 26 | 6 | 0 | 85.3 (s-wide-keys-100k) |
| ruflo-federation | `memory_store[federation].value`<br>`federation_bbs_publish.value` | 49 | 26 | 6 | 0 | 85.8 (s-wide-keys-100k) |
| ruflo-goals | `memory_store[goals].value`<br>`agentdb_hierarchical-store[goals].value`<br>`agentdb_pattern-store[goals].value` | 49 | 26 | 6 | 0 | 84.5 (s-wide-keys-100k) |
| ruflo-graph-intelligence | `sublinear_solve.value`<br>`sublinear_x.value` | 49 | 26 | 6 | 0 | 79.1 (s-wide-keys-100k) |
| ruflo-intelligence | `memory_store[patterns].value`<br>`hooks_intelligence_pattern-store.value`<br>`hooks_transfer.value` | 49 | 26 | 6 | 0 | 92.4 (s-wide-keys-100k) |
| ruflo-iot-cognitum | `memory_store[iot-devices].value` | 49 | 26 | 6 | 0 | 87.1 (s-wide-keys-100k) |
| ruflo-jujutsu | `github_pr_manage.value` | 49 | 26 | 6 | 0 | 86.7 (s-wide-keys-100k) |
| ruflo-knowledge-graph | `agentdb_hierarchical-store.value`<br>`agentdb_causal-edge.value`<br>`agentdb_pattern-store.value` | 49 | 26 | 6 | 0 | 90.0 (s-wide-keys-100k) |
| ruflo-loop-workers | `hooks_worker-dispatch.value` | 49 | 26 | 6 | 0 | 88.7 (s-wide-keys-100k) |
| ruflo-market-data | `memory_store.value`<br>`agentdb_hierarchical-store.value`<br>`agentdb_pattern-store.value` | 49 | 26 | 6 | 0 | 91.1 (s-wide-keys-100k) |
| ruflo-metaharness | `memory_store.value`<br>`agentdb_hierarchical-store.value`<br>`agentdb_causal-edge.value` | 49 | 26 | 6 | 0 | 85.0 (s-wide-keys-100k) |
| ruflo-migrations | `memory_store.value`<br>`agentdb_hierarchical-store.value`<br>`agentdb_pattern-store.value` | 52 | 23 | 6 | 0 | 98.0 (s-wide-keys-100k) |
| ruflo-music | `memory_store.value`<br>`agentdb_hierarchical-store.value`<br>`agentdb_pattern-store.value` | 52 | 23 | 6 | 0 | 95.0 (s-wide-keys-100k) |
| ruflo-neural-trader | `memory_store.value`<br>`agentdb_hierarchical-store.value`<br>`agentdb_pattern-store.value` | 52 | 23 | 6 | 0 | 90.6 (s-wide-keys-100k) |
| ruflo-observability | `memory_store.value`<br>`agentdb_hierarchical-store.value`<br>`agentdb_pattern-store.value` | 52 | 23 | 6 | 0 | 87.6 (s-wide-keys-100k) |
| ruflo-rag-memory | `memory_store.value`<br>`agentdb_hierarchical-store.value`<br>`agentdb_pattern-store.value` | 49 | 26 | 6 | 0 | 92.0 (s-wide-keys-100k) |
| ruflo-ruvector | `memory_store.value` | 49 | 26 | 6 | 0 | 84.8 (s-wide-keys-100k) |
| ruflo-ruvllm | `ruvllm_hnsw_add.value` | 49 | 26 | 6 | 0 | 79.6 (s-wide-keys-100k) |
| ruflo-rvf | `memory_store.value`<br>`hooks_transfer.value` | 49 | 26 | 6 | 0 | 92.3 (s-wide-keys-100k) |
| ruflo-security-audit | `memory_store[security].value`<br>`agentdb_hierarchical-store[security].value`<br>`agentdb_pattern-store[security].value` | 49 | 26 | 6 | 0 | 81.3 (s-wide-keys-100k) |
| ruflo-sparc | `memory_store[sparc].value`<br>`agentdb_hierarchical-store[sparc].value`<br>`agentdb_pattern-store[sparc].value` | 49 | 26 | 6 | 0 | 77.4 (s-wide-keys-100k) |
| ruflo-testgen | none (policy guard) | 36 | 0 | 0 | 45 | 0.3 (s-deep-arr-5000) |
| ruflo-workflows | `memory_store[workflows].value`<br>`agentdb_hierarchical-store[workflows].value`<br>`agentdb_pattern-store[workflows].value` | 49 | 26 | 6 | 0 | 60.5 (s-wide-keys-100k) |
| ruflo-x-gateway | `x_federation_publish.value`<br>`x_federation_channel_publish.value` | 49 | 26 | 6 | 0 | 67.8 (s-wide-keys-100k) |
## Probe matrix (✓ pass · ✗ fail · ~ advisory miss · · not applicable)
Columns are plugins in the order of the summary table above (1 = first row).
| probe | class | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
|---|---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| plain | control | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | ✓ | ✓ | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | ✓ | ✓ |
| zwsp-prefix | invisible | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | ✓ | ✓ | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | ✓ | ✓ |
| zwj-mid | invisible | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | ✓ | ✓ | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | ✓ | ✓ |
| bom-mid | invisible | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | ✓ | ✓ | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | ✓ | ✓ |
| shy-split | invisible | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| word-joiner | invisible | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | ✓ | ✓ | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | ✓ | ✓ |
| bidi-isolate | invisible | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| bidi-override-wrap | invisible | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | ✓ | ✓ | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | ✓ | ✓ |
| mongolian-vs | invisible | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| every-char-zwsp | invisible | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | ✓ | ✓ | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | ✓ | ✓ |
| nest-obj-3 | nesting | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | ✓ | ✓ | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | ✓ | ✓ |
| nest-obj-6 | nesting | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| nest-obj-12 | nesting | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| nest-obj-60 | nesting | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| nest-obj-5000 | nesting | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| nest-arr-3 | nesting | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | ✓ | ✓ | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | ✓ | ✓ |
| nest-arr-6 | nesting | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| nest-arr-12 | nesting | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| nest-arr-60 | nesting | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| nest-arr-5000 | nesting | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| nest-mix-3 | nesting | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | ✓ | ✓ | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | ✓ | ✓ |
| nest-mix-6 | nesting | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| nest-mix-12 | nesting | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| nest-mix-60 | nesting | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| nest-mix-5000 | nesting | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| wide-array-250 | wide | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| wide-object-250 | wide | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| wide-array-5000 | wide | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| key-name | keys | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| key-name-nested-3 | keys | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| key-name-in-array | keys | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| json-in-string | json | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | ✓ | ✓ | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | ✓ | ✓ |
| json-double-encoded | json | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | ✓ | ✓ | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | ✓ | ✓ |
| json-unicode-escape | encoding | ~ | ~ | ~ | ~ | ~ | ~ | ~ | · | · | · | ~ | ~ | · | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | · | ~ | ~ |
| base64 | encoding | ~ | ~ | ~ | ~ | ~ | ~ | ~ | · | · | · | ~ | ~ | · | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | · | ~ | ~ |
| hex | encoding | ~ | ~ | ~ | ~ | ~ | ~ | ~ | · | · | · | ~ | ~ | · | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | · | ~ | ~ |
| url-encoded | encoding | ~ | ~ | ~ | ~ | ~ | ~ | ~ | · | · | · | ~ | ~ | · | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | · | ~ | ~ |
| fullwidth-confusable | confusable | ~ | ~ | ~ | ~ | ~ | ~ | ~ | · | · | · | ~ | ~ | · | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | · | ~ | ~ |
| cyrillic-confusable | confusable | ~ | ~ | ~ | ~ | ~ | ~ | ~ | · | · | · | ~ | ~ | · | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | · | ~ | ~ |
| huge-start | huge | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | ✓ | ✓ | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | ✓ | ✓ |
| huge-middle | huge | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| huge-end | huge | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| after-25k-padding | huge | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| after-200k-padding | huge | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | · | · | ✗ | ✗ | · | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | · | ✗ | ✗ |
| s-a | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-a-bang | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-sk-near-miss | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-ghp-near-miss | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-akia-near-miss | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-assign-spaces | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-assign-long-value | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-bearer | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-pem-spaces | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-pem-upper | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-jwt-dots | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-curl-repeat | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-ignore-repeat | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-role-tags | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-send-secrets | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-newlines | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-spaces-x | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-lt | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-brace | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-backslash | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-zwsp | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-zwsp-in-secret | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-emoji | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-email | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-email-dots | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-url | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-url-userinfo | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-path-dots | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-words | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-eq-run | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-deep-obj-5000 | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-deep-arr-5000 | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-deep-obj-100000 | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-wide-array-100k | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| s-wide-keys-100k | stress | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ |
| s-many-1kb-strings | stress | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| benign-lookalikes | benign | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | ✓ | ✓ | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | ✓ | ✓ |
## Failures by probe
- **shy-split** (must) — 32/37: all but none
- **bidi-isolate** (must) — 32/37: all but none
- **mongolian-vs** (must) — 32/37: all but none
- **nest-obj-6** (must) — 32/37: all but none
- **nest-obj-12** (must) — 32/37: all but none
- **nest-obj-60** (must) — 32/37: all but none
- **nest-obj-5000** (must) — 32/37: all but none
- **nest-arr-6** (must) — 32/37: all but none
- **nest-arr-12** (must) — 32/37: all but none
- **nest-arr-60** (must) — 32/37: all but none
- **nest-arr-5000** (must) — 32/37: all but none
- **nest-mix-6** (must) — 32/37: all but none
- **nest-mix-12** (must) — 32/37: all but none
- **nest-mix-60** (must) — 32/37: all but none
- **nest-mix-5000** (must) — 32/37: all but none
- **wide-array-250** (must) — 32/37: all but none
- **wide-object-250** (must) — 32/37: all but none
- **wide-array-5000** (must) — 32/37: all but none
- **key-name** (must) — 28/37: all but ruflo-migrations, ruflo-music, ruflo-neural-trader, ruflo-observability
- **key-name-nested-3** (must) — 28/37: all but ruflo-migrations, ruflo-music, ruflo-neural-trader, ruflo-observability
- **key-name-in-array** (must) — 28/37: all but ruflo-migrations, ruflo-music, ruflo-neural-trader, ruflo-observability
- **json-unicode-escape** (advisory) — 32/37: all but none
- **base64** (advisory) — 32/37: all but none
- **hex** (advisory) — 32/37: all but none
- **url-encoded** (advisory) — 32/37: all but none
- **fullwidth-confusable** (advisory) — 32/37: all but none
- **cyrillic-confusable** (advisory) — 32/37: all but none
- **huge-middle** (must) — 32/37: all but none
- **huge-end** (must) — 32/37: all but none
- **after-25k-padding** (must) — 32/37: all but none
- **after-200k-padding** (must) — 32/37: all but none
- **s-wide-keys-100k** (must) — 32/37: all but ruflo-browser, ruflo-business-pods, ruflo-chatgpt-federation, ruflo-deepseek-harness, ruflo-testgen