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.
|
* residual is accepted and reviews of DI provider modules carry it.
|
||||||
* - Computed member access with a NON-literal name (obj[verb]()) is not
|
* - Computed member access with a NON-literal name (obj[verb]()) is not
|
||||||
* statically resolvable; literal computed access (obj['insert']()) is
|
* statically resolvable; literal computed access (obj['insert']()) is
|
||||||
* flagged, and a computed member — access or call, in any position —
|
* flagged, and the statically-resolvable DISGUISES of a literal key fail
|
||||||
* whose key is a text-only template literal (obj[`insert`](),
|
* closed — access or call, in any position: a text-only template-literal
|
||||||
* ns[`companies`]) fails closed anywhere — template text never reaches
|
* key (obj[`insert`](), ns[`companies`] — template text never reaches
|
||||||
* the lexer's code output, so such a member is indistinguishable from a
|
* the lexer's code output), a quoted key carrying expression dressing
|
||||||
* runtime-constructed one (a template key WITH interpolation is a
|
* (`ns['companies' as const]`, `ns['companies'!]`), and a quoted key
|
||||||
* non-literal computed member, above). Invoking a write/exec verb via
|
* built from string escapes (`\u`/`\x`/octal). (A template key WITH
|
||||||
* `.apply`/`.call`/`.bind` likewise fails closed anywhere. Constructing
|
* interpolation is a non-literal computed member, above.) Invoking a
|
||||||
* the member at runtime is adjacent to eval and is expected to be caught
|
* write/exec verb via `.apply`/`.call`/`.bind`, or through any
|
||||||
* in review.
|
* `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
|
* - The DB_FACTORY_IMPORTERS enumeration counts the import edges
|
||||||
* hasSymbolImportEdge can see (named import, literal dynamic package
|
* hasSymbolImportEdge can see (named import, literal dynamic package
|
||||||
* import with symbol use, namespace member use). Destructuring a factory
|
* 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
|
* - The scan perimeter is <root>/<pkg>/src for the three roots; production
|
||||||
* TS outside a src/ directory (e.g. packages/mosaic/framework/**) is not
|
* TS outside a src/ directory (e.g. packages/mosaic/framework/**) is not
|
||||||
* scanned (verified free of db/driver/execute references at review time).
|
* 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
|
* The writer allowlist names hierarchy command/repository modules ONLY. It is
|
||||||
* empty today: the hierarchy command family (M4-1b) has not landed, so no
|
* 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/cli.ts',
|
||||||
'packages/storage/src/migrate-tier.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 SCAN_ROOTS = ['apps', 'packages', 'plugins'];
|
||||||
const EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts']);
|
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
|
* initializer); DECL_LIST skips prior declarators whose initializers are
|
||||||
* comma-free. Both are approximations of the declarator grammar — exotic
|
* comma-free. Both are approximations of the declarator grammar — exotic
|
||||||
* prior initializers (an array or call containing a comma) fall to the
|
* 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 TYPE_ANN = `(?:\\s*:\\s*(?:[^=;\\n]|=>)*?)?`;
|
||||||
const DECL_LIST = `(?:[\\w$]+${TYPE_ANN}\\s*=\\s*[^,;\\n]*,\\s*)*`;
|
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' });
|
violations.push({ file: rel, prong: 'code-construction', detail: 'eval/new Function' });
|
||||||
}
|
}
|
||||||
// A computed member — access or call — whose key is a text-only template
|
// Fail-closed CODE-SHAPE rules: statically-resolvable disguises of a
|
||||||
// literal is invisible to every member and verb matcher (the lexer routes
|
// literal member key or verb invocation. They match ordinary syntax, so a
|
||||||
// template text to spans, so the key survives in code as two ADJACENT
|
// reviewed legitimate hit is dispositioned through CODE_SHAPE_REGISTER —
|
||||||
// backticks — the discriminator against backticks inside quoted-string
|
// the shapes themselves are never weakened.
|
||||||
// prose, whose text stays in code, and against interpolated keys, whose
|
if (!CODE_SHAPE_REGISTER.includes(rel)) {
|
||||||
// `${…}` expression stays in code between the backticks). Runtime code
|
// A computed member — access or call — whose key is a text-only template
|
||||||
// construction's sibling: fail-closed anywhere, in any position (write
|
// literal is invisible to every member and verb matcher (the lexer routes
|
||||||
// target, export expression, receiver, call). A template key WITH
|
// template text to spans, so the key survives in code as two ADJACENT
|
||||||
// interpolation is a non-literal computed member (documented residual
|
// backticks — the discriminator against backticks inside quoted-string
|
||||||
// above).
|
// prose, whose text stays in code, and against interpolated keys, whose
|
||||||
if (new RegExp(`\\[\\s*\`\`\\s*\\]`).test(code)) {
|
// `${…}` expression stays in code between the backticks). Runtime code
|
||||||
violations.push({
|
// construction's sibling: fail-closed in any position (write target,
|
||||||
file: rel,
|
// export expression, receiver, call). A template key WITH interpolation
|
||||||
prong: 'code-construction',
|
// is a non-literal computed member (documented residual above).
|
||||||
detail: 'template-literal computed member',
|
if (new RegExp(`\\[\\s*\`\`\\s*\\]`).test(code)) {
|
||||||
});
|
violations.push({
|
||||||
}
|
file: rel,
|
||||||
// Invoking a write/exec verb through Function.prototype indirection
|
prong: 'code-construction',
|
||||||
// (`db.insert.apply(db, [companies])`, `d.execute.call(d, s)`,
|
detail: 'template-literal computed member',
|
||||||
// `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.
|
// A quoted computed key carrying expression dressing
|
||||||
// Fail-closed anywhere: no legitimate site invokes a builder verb this way
|
// (`ns['companies' as const]`, `ns['companies' satisfies 'companies']`,
|
||||||
// (calibrated clean over the production tree).
|
// `ns['companies'!]`) keeps its static value while breaking every
|
||||||
if (
|
// `['name']` matcher — the bracket alternatives require the closing
|
||||||
new RegExp(
|
// quote to touch the `]`. Tolerance inside the matchers cannot span the
|
||||||
`(?:${DOT}(?:insert|update|delete|execute|query|unsafe|raw)\\b|` +
|
// class (the dressing's type text may itself contain a `]`, e.g.
|
||||||
`\\[\\s*['"](?:insert|update|delete|execute|query|unsafe|raw)['"]\\s*\\])` +
|
// `as Foo['x']`), so the SHAPE fails closed: a quote-close followed by
|
||||||
`${DOT}(?:apply|call|bind)${CALL_OPEN}`,
|
// `!`/`as`/`satisfies` inside a bracket. The `!(?!=)` guard keeps
|
||||||
).test(code)
|
// ordinary comparisons (`o['k'] !== x` — dressing AFTER the bracket)
|
||||||
) {
|
// clean.
|
||||||
violations.push({
|
if (
|
||||||
file: rel,
|
new RegExp(`\\[\\s*(['"])(?:(?!\\1)[^\\n])*\\1\\s*(?:!(?!=)|as\\s|satisfies\\s)`).test(code)
|
||||||
prong: 'code-construction',
|
) {
|
||||||
detail: 'verb apply/call/bind indirection',
|
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
|
// Dynamic import whose specifier is not a single string literal: the
|
||||||
// import graph becomes unanalyzable. Checked per call site, so a literal
|
// 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',
|
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]); } }`,
|
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 }> = [
|
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',
|
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}\`](); }`,
|
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', () => {
|
it('analyzer flags every known evasion form', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user