fix(db): close round-8 review evasions in writer-coverage assertion
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
- Dressed quoted computed keys (as/satisfies/!) fail closed anywhere: the static key survives the dressing but breaks every ['name'] matcher, and matcher tolerance cannot span types containing ']' (as Foo['x']), so the shape itself is the trigger (with an !(?!=) guard for ordinary comparisons). - Escape-built quoted keys (\u/\x/octal) fail closed: statically resolvable, so outside the non-literal computed-member residual. - Reflect verb indirection fails closed: any Reflect.* call naming a write/exec verb in its argument text (Reflect.apply(db.insert, ...), Reflect.get(db, 'insert')). - CODE_SHAPE_REGISTER: enumerated disposition path for the fail-closed code-shape rules (template key, dressed/escaped key, apply/call/bind, Reflect) — a reviewed legitimate hit is registered, never resolved by weakening the shape. Empty today; eval/new Function stays unconditional. - KNOWN RESIDUALS: test-file/out-of-src modules named as import-graph conduit blind spots; TYPE_ANN/DECL_LIST single-line limit documented. - Controls E93-E102 (dressed/escaped keys at write target, conduit export, default export, single-file factory extraction; Reflect.apply/get) plus a clean control pinning the !== guard.
This commit is contained in:
@@ -82,15 +82,20 @@
|
||||
* residual is accepted and reviews of DI provider modules carry it.
|
||||
* - Computed member access with a NON-literal name (obj[verb]()) is not
|
||||
* statically resolvable; literal computed access (obj['insert']()) is
|
||||
* flagged, and a computed member — access or call, in any position —
|
||||
* whose key is a text-only template literal (obj[`insert`](),
|
||||
* ns[`companies`]) fails closed anywhere — template text never reaches
|
||||
* the lexer's code output, so such a member is indistinguishable from a
|
||||
* runtime-constructed one (a template key WITH interpolation is a
|
||||
* non-literal computed member, above). Invoking a write/exec verb via
|
||||
* `.apply`/`.call`/`.bind` likewise fails closed anywhere. Constructing
|
||||
* the member at runtime is adjacent to eval and is expected to be caught
|
||||
* in review.
|
||||
* flagged, and the statically-resolvable DISGUISES of a literal key fail
|
||||
* closed — access or call, in any position: a text-only template-literal
|
||||
* key (obj[`insert`](), ns[`companies`] — template text never reaches
|
||||
* the lexer's code output), a quoted key carrying expression dressing
|
||||
* (`ns['companies' as const]`, `ns['companies'!]`), and a quoted key
|
||||
* built from string escapes (`\u`/`\x`/octal). (A template key WITH
|
||||
* interpolation is a non-literal computed member, above.) Invoking a
|
||||
* write/exec verb via `.apply`/`.call`/`.bind`, or through any
|
||||
* `Reflect.*` call naming a verb, fails closed the same way. These
|
||||
* code-shape rules match ordinary syntax over ordinary method names, so
|
||||
* they carry their own enumerated disposition (CODE_SHAPE_REGISTER,
|
||||
* empty today): a reviewed legitimate hit is registered, never resolved
|
||||
* by weakening the shape. Constructing the member at runtime is adjacent
|
||||
* to eval and is expected to be caught in review.
|
||||
* - The DB_FACTORY_IMPORTERS enumeration counts the import edges
|
||||
* hasSymbolImportEdge can see (named import, literal dynamic package
|
||||
* import with symbol use, namespace member use). Destructuring a factory
|
||||
@@ -115,6 +120,12 @@
|
||||
* - The scan perimeter is <root>/<pkg>/src for the three roots; production
|
||||
* TS outside a src/ directory (e.g. packages/mosaic/framework/**) is not
|
||||
* scanned (verified free of db/driver/execute references at review time).
|
||||
* Files excluded from the scan — test files and out-of-src modules — are
|
||||
* also invisible as import-graph CONDUITS: test files are emitted to
|
||||
* dist, so a production module could launder a symbol or capability
|
||||
* through a re-export in one. Importing a test module from production
|
||||
* code is anomalous and review-visible; the blind spot is accepted as a
|
||||
* residual, not closed.
|
||||
*
|
||||
* The writer allowlist names hierarchy command/repository modules ONLY. It is
|
||||
* empty today: the hierarchy command family (M4-1b) has not landed, so no
|
||||
@@ -247,6 +258,20 @@ const DB_FACTORY_IMPORTERS: string[] = [
|
||||
'packages/storage/src/cli.ts',
|
||||
'packages/storage/src/migrate-tier.ts',
|
||||
];
|
||||
/**
|
||||
* Enumerated disposition for the fail-closed CODE-SHAPE rules (text-only
|
||||
* template keys, dressed/escape-built quoted keys, verb
|
||||
* `.apply`/`.call`/`.bind`, `Reflect.*` verb indirection). Those shapes use
|
||||
* ordinary syntax over ordinary method names (`query`, `delete`), so a
|
||||
* legitimate hit is possible — e.g. a non-SQL `.query.bind(this)` on a
|
||||
* log-shaped service. Such a hit is registered here with a justification,
|
||||
* reviewed under §5.1, and is never resolved by weakening the shape. Empty
|
||||
* today: the production tree has zero occurrences of any of these shapes
|
||||
* (calibrated by the full-tree test). Exemption covers the shape rules ONLY —
|
||||
* every prong still applies in full. eval/new Function stays unconditional:
|
||||
* constructed code has no disposition path.
|
||||
*/
|
||||
const CODE_SHAPE_REGISTER: string[] = [];
|
||||
|
||||
const SCAN_ROOTS = ['apps', 'packages', 'plugins'];
|
||||
const EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts']);
|
||||
@@ -506,7 +531,9 @@ const CALL_OPEN = `\\s*(?:\\?\\.)?\\s*\\(`;
|
||||
* initializer); DECL_LIST skips prior declarators whose initializers are
|
||||
* comma-free. Both are approximations of the declarator grammar — exotic
|
||||
* prior initializers (an array or call containing a comma) fall to the
|
||||
* value-flow residual.
|
||||
* value-flow residual. Both are also SINGLE-LINE shapes: a multiline type
|
||||
* annotation (prettier keeps one only past the print width) falls to the
|
||||
* value-flow residual too.
|
||||
*/
|
||||
const TYPE_ANN = `(?:\\s*:\\s*(?:[^=;\\n]|=>)*?)?`;
|
||||
const DECL_LIST = `(?:[\\w$]+${TYPE_ANN}\\s*=\\s*[^,;\\n]*,\\s*)*`;
|
||||
@@ -987,42 +1014,93 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
||||
) {
|
||||
violations.push({ file: rel, prong: 'code-construction', detail: 'eval/new Function' });
|
||||
}
|
||||
// A computed member — access or call — whose key is a text-only template
|
||||
// literal is invisible to every member and verb matcher (the lexer routes
|
||||
// template text to spans, so the key survives in code as two ADJACENT
|
||||
// backticks — the discriminator against backticks inside quoted-string
|
||||
// prose, whose text stays in code, and against interpolated keys, whose
|
||||
// `${…}` expression stays in code between the backticks). Runtime code
|
||||
// construction's sibling: fail-closed anywhere, in any position (write
|
||||
// target, export expression, receiver, call). A template key WITH
|
||||
// interpolation is a non-literal computed member (documented residual
|
||||
// above).
|
||||
if (new RegExp(`\\[\\s*\`\`\\s*\\]`).test(code)) {
|
||||
violations.push({
|
||||
file: rel,
|
||||
prong: 'code-construction',
|
||||
detail: 'template-literal computed member',
|
||||
});
|
||||
}
|
||||
// Invoking a write/exec verb through Function.prototype indirection
|
||||
// (`db.insert.apply(db, [companies])`, `d.execute.call(d, s)`,
|
||||
// `db.insert.bind(db)`) hides the argument shape from every verb matcher
|
||||
// while both member names stay statically visible — unlike the
|
||||
// method-EXTRACTION residual, where the verb never appears as a member.
|
||||
// Fail-closed anywhere: no legitimate site invokes a builder verb this way
|
||||
// (calibrated clean over the production tree).
|
||||
if (
|
||||
new RegExp(
|
||||
`(?:${DOT}(?:insert|update|delete|execute|query|unsafe|raw)\\b|` +
|
||||
`\\[\\s*['"](?:insert|update|delete|execute|query|unsafe|raw)['"]\\s*\\])` +
|
||||
`${DOT}(?:apply|call|bind)${CALL_OPEN}`,
|
||||
).test(code)
|
||||
) {
|
||||
violations.push({
|
||||
file: rel,
|
||||
prong: 'code-construction',
|
||||
detail: 'verb apply/call/bind indirection',
|
||||
});
|
||||
// Fail-closed CODE-SHAPE rules: statically-resolvable disguises of a
|
||||
// literal member key or verb invocation. They match ordinary syntax, so a
|
||||
// reviewed legitimate hit is dispositioned through CODE_SHAPE_REGISTER —
|
||||
// the shapes themselves are never weakened.
|
||||
if (!CODE_SHAPE_REGISTER.includes(rel)) {
|
||||
// A computed member — access or call — whose key is a text-only template
|
||||
// literal is invisible to every member and verb matcher (the lexer routes
|
||||
// template text to spans, so the key survives in code as two ADJACENT
|
||||
// backticks — the discriminator against backticks inside quoted-string
|
||||
// prose, whose text stays in code, and against interpolated keys, whose
|
||||
// `${…}` expression stays in code between the backticks). Runtime code
|
||||
// construction's sibling: fail-closed in any position (write target,
|
||||
// export expression, receiver, call). A template key WITH interpolation
|
||||
// is a non-literal computed member (documented residual above).
|
||||
if (new RegExp(`\\[\\s*\`\`\\s*\\]`).test(code)) {
|
||||
violations.push({
|
||||
file: rel,
|
||||
prong: 'code-construction',
|
||||
detail: 'template-literal computed member',
|
||||
});
|
||||
}
|
||||
// A quoted computed key carrying expression dressing
|
||||
// (`ns['companies' as const]`, `ns['companies' satisfies 'companies']`,
|
||||
// `ns['companies'!]`) keeps its static value while breaking every
|
||||
// `['name']` matcher — the bracket alternatives require the closing
|
||||
// quote to touch the `]`. Tolerance inside the matchers cannot span the
|
||||
// class (the dressing's type text may itself contain a `]`, e.g.
|
||||
// `as Foo['x']`), so the SHAPE fails closed: a quote-close followed by
|
||||
// `!`/`as`/`satisfies` inside a bracket. The `!(?!=)` guard keeps
|
||||
// ordinary comparisons (`o['k'] !== x` — dressing AFTER the bracket)
|
||||
// clean.
|
||||
if (
|
||||
new RegExp(`\\[\\s*(['"])(?:(?!\\1)[^\\n])*\\1\\s*(?:!(?!=)|as\\s|satisfies\\s)`).test(code)
|
||||
) {
|
||||
violations.push({
|
||||
file: rel,
|
||||
prong: 'code-construction',
|
||||
detail: 'dressed computed-member key',
|
||||
});
|
||||
}
|
||||
// A quoted key built from string escapes (a `\` + `u`/`x`/octal-digit
|
||||
// sequence whose decoded text is a plain identifier, e.g. a key spelling
|
||||
// `companies` with its first letter unicode-escaped) stays invisible to
|
||||
// every `\w+`-keyed matcher. The key IS statically
|
||||
// resolvable, so it is not under the non-literal residual: any bracketed
|
||||
// quoted key containing an identifier-capable escape fails closed.
|
||||
if (new RegExp(`\\[\\s*(['"])[^'"\\n]*\\\\[ux0-7][^'"\\n]*\\1\\s*\\]`).test(code)) {
|
||||
violations.push({
|
||||
file: rel,
|
||||
prong: 'code-construction',
|
||||
detail: 'escape-built computed-member key',
|
||||
});
|
||||
}
|
||||
// Invoking a write/exec verb through Function.prototype indirection
|
||||
// (`db.insert.apply(db, [companies])`, `d.execute.call(d, s)`,
|
||||
// `db.insert.bind(db)`) hides the argument shape from every verb matcher
|
||||
// while both member names stay statically visible — unlike the
|
||||
// method-EXTRACTION residual, where the verb never appears as a member.
|
||||
if (
|
||||
new RegExp(
|
||||
`(?:${DOT}(?:insert|update|delete|execute|query|unsafe|raw)\\b|` +
|
||||
`\\[\\s*['"](?:insert|update|delete|execute|query|unsafe|raw)['"]\\s*\\])` +
|
||||
`${DOT}(?:apply|call|bind)${CALL_OPEN}`,
|
||||
).test(code)
|
||||
) {
|
||||
violations.push({
|
||||
file: rel,
|
||||
prong: 'code-construction',
|
||||
detail: 'verb apply/call/bind indirection',
|
||||
});
|
||||
}
|
||||
// `Reflect` reaches the same members without member syntax:
|
||||
// `Reflect.apply(db.insert, db, [companies])`, `Reflect.get(db,
|
||||
// 'insert')`, `Reflect.getOwnPropertyDescriptor(db, 'execute')`. Any
|
||||
// Reflect call whose argument text (up to the first `)`) names a verb
|
||||
// fails closed — the tree has zero Reflect call sites (measured).
|
||||
if (
|
||||
new RegExp(
|
||||
`\\bReflect${DOT}[\\w$]+${CALL_OPEN}[^)\\n]*\\b(?:insert|update|delete|execute|query|unsafe|raw)\\b`,
|
||||
).test(code)
|
||||
) {
|
||||
violations.push({
|
||||
file: rel,
|
||||
prong: 'code-construction',
|
||||
detail: 'Reflect verb indirection',
|
||||
});
|
||||
}
|
||||
}
|
||||
// Dynamic import whose specifier is not a single string literal: the
|
||||
// import graph becomes unanalyzable. Checked per call site, so a literal
|
||||
@@ -2032,6 +2110,58 @@ describe('hierarchy writer coverage (contract 1 §6.3b)', () => {
|
||||
name: 'E92 template-keyed receiver verb in capability-free file',
|
||||
src: `export class R { constructor(private pool: { execute(s: string): Promise<unknown> }) {}\n async f(t: string) { await this.pool[\`execute\`].apply(this.pool, ['TRUNCATE ' + t]); } }`,
|
||||
},
|
||||
{
|
||||
name: 'E93 as-dressed computed key as write target',
|
||||
src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns['companies' as const]).values({}); }`,
|
||||
},
|
||||
{
|
||||
name: 'E94 satisfies-dressed computed key as write target',
|
||||
src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns['companies' satisfies 'companies']).values({}); }`,
|
||||
},
|
||||
{
|
||||
name: 'E95 non-null-dressed computed key as write target',
|
||||
src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns['companies'!]).values({}); }`,
|
||||
},
|
||||
{
|
||||
name: 'E96 escape-built computed key as write target',
|
||||
src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns['\\u0063ompanies']).values({}); }`,
|
||||
},
|
||||
{
|
||||
name: 'E97 dressed computed key in schema conduit export',
|
||||
src: `import { co } from './evasion-mid41.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(co).values({}); }`,
|
||||
extras: [
|
||||
{
|
||||
rel: 'packages/db/src/evasion-mid41.ts',
|
||||
src: `import * as ns from '@mosaicstack/db';\nexport const co = ns['companies' as const];`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'E98 dressed computed key in schema default export',
|
||||
src: `import c from './evasion-mid42.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`,
|
||||
extras: [
|
||||
{
|
||||
rel: 'packages/db/src/evasion-mid42.ts',
|
||||
src: `import * as ns from '@mosaicstack/db';\nexport default ns['companies' as const];`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'E99 dressed computed key in single-file factory extraction',
|
||||
src: `import * as ns from '@mosaicstack/db';\nexport async function f(t: string) { const mk = ns['createDb' as const]; const d = mk('u'); await d.execute('DELETE FROM ' + t); }`,
|
||||
},
|
||||
{
|
||||
name: 'E100 Reflect.apply of a write verb',
|
||||
src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await Reflect.apply(db.insert, db, [companies]); }`,
|
||||
},
|
||||
{
|
||||
name: 'E101 Reflect.apply of an execution verb in capability-free file',
|
||||
src: `export class R { constructor(private pool: { execute(s: string): Promise<unknown> }) {}\n async f(t: string) { await Reflect.apply(this.pool.execute, this.pool, ['TRUNCATE ' + t]); } }`,
|
||||
},
|
||||
{
|
||||
name: 'E102 Reflect.get extraction of an execution verb',
|
||||
src: `import { db } from './x.js';\nexport async function f(t: string) { const fn = Reflect.get(db, 'execute') as (s: string) => Promise<unknown>; await fn.call(db, 'DELETE FROM ' + t); }`,
|
||||
},
|
||||
];
|
||||
const CLEAN_CONTROLS: Array<{ name: string; src: string }> = [
|
||||
{
|
||||
@@ -2046,6 +2176,10 @@ describe('hierarchy writer coverage (contract 1 §6.3b)', () => {
|
||||
name: 'clean: interpolated template key is a non-literal computed member, not a text-only key',
|
||||
src: `export function g(o: Record<string, () => void>, k: string) { o[\`\${k}\`](); }`,
|
||||
},
|
||||
{
|
||||
name: 'clean: strict-inequality after a bracket member is not key dressing',
|
||||
src: `export function h(o: Record<string, string>) { return o['kind'] !== 'x'; }`,
|
||||
},
|
||||
];
|
||||
|
||||
it('analyzer flags every known evasion form', () => {
|
||||
|
||||
Reference in New Issue
Block a user