Status
The original backtick/no-substitution-template bypass reported here was fixed in #646:
import(`@altinity/clickhouse-http`)
is now recognized, and the package-boundary rules that needed stronger syntax handling were moved to the real TypeScript-parser-backed helpers during #646/#653.
The remaining problem is narrower: the generic regex-backed RULES in build/check-boundaries.mjs still do not fail closed on dynamic imports whose argument is not a statically provable string literal.
Problem
extractSpecifiers() only returns module specifiers it can recognize textually. That is sufficient for ordinary static imports/exports and for quoted or no-substitution-template dynamic imports, but it silently skips computed dynamic imports such as:
import(specifier)
import(`../${name}.js`)
import(prefix + '/module.js')
For a source tree guarded only by the generic RULES loop, a computed dynamic import therefore receives no boundary decision at all. The check does not prove that the import is allowed; it simply fails to see a specifier.
This is no longer the broad package-boundary hole described in the original filing: later #630 phases moved Rule C/Rule D and the final package guards to the real parser. The remaining scope is the generic internal-layer rules that still intentionally use extractSpecifiers().
Why it matters
These guards are intended to enforce source-level dependency direction. A rule that silently ignores an import form it cannot classify is fail-open: a future dynamic import can cross a forbidden layer without check:arch noticing.
There is no known production use of computed dynamic imports under the currently guarded trees, so this is architecture-hardening rather than a live product regression.
Intended fix
For directories covered by the generic RULES, make dynamic-import handling fail closed:
- allow a dynamic import only when its argument is statically provable as a string-like module specifier supported by the checker;
- continue to accept single-quoted, double-quoted, and no-substitution-template literals;
- reject computed/non-static
import(...) expressions in guarded source instead of silently skipping them;
- preserve the existing fast path for ordinary static import/export forms unless moving the generic rules to the shared real-parser helper is simpler and measurably acceptable.
The diagnostic should identify the file and explain that the dynamic import cannot be statically checked against the architecture boundary.
Tests
Add sabotage coverage for at least:
- quoted dynamic import crossing a forbidden boundary;
- no-substitution-template dynamic import crossing a forbidden boundary;
- computed template dynamic import, e.g.
import(`../${name}.js`);
- identifier argument, e.g.
import(specifier);
- concatenated argument, e.g.
import('../' + name);
- existing
export * from, named re-export, and namespace re-export coverage remains intact.
The production checker and its policy mirror should prove the same fail-closed behavior.
Acceptance criteria
History
Originally filed during review of #641 after finding that the then-current dynamic-import regex matched only single- and double-quoted arguments. That exact bypass was fixed in #646. This issue now tracks only the remaining fail-open behavior for non-static dynamic imports in the generic regex-backed architecture rules.
Status
The original backtick/no-substitution-template bypass reported here was fixed in #646:
is now recognized, and the package-boundary rules that needed stronger syntax handling were moved to the real TypeScript-parser-backed helpers during #646/#653.
The remaining problem is narrower: the generic regex-backed
RULESinbuild/check-boundaries.mjsstill do not fail closed on dynamic imports whose argument is not a statically provable string literal.Problem
extractSpecifiers()only returns module specifiers it can recognize textually. That is sufficient for ordinary static imports/exports and for quoted or no-substitution-template dynamic imports, but it silently skips computed dynamic imports such as:For a source tree guarded only by the generic
RULESloop, a computed dynamic import therefore receives no boundary decision at all. The check does not prove that the import is allowed; it simply fails to see a specifier.This is no longer the broad package-boundary hole described in the original filing: later #630 phases moved Rule C/Rule D and the final package guards to the real parser. The remaining scope is the generic internal-layer rules that still intentionally use
extractSpecifiers().Why it matters
These guards are intended to enforce source-level dependency direction. A rule that silently ignores an import form it cannot classify is fail-open: a future dynamic import can cross a forbidden layer without
check:archnoticing.There is no known production use of computed dynamic imports under the currently guarded trees, so this is architecture-hardening rather than a live product regression.
Intended fix
For directories covered by the generic
RULES, make dynamic-import handling fail closed:import(...)expressions in guarded source instead of silently skipping them;The diagnostic should identify the file and explain that the dynamic import cannot be statically checked against the architecture boundary.
Tests
Add sabotage coverage for at least:
import(`../${name}.js`);import(specifier);import('../' + name);export * from, named re-export, and namespace re-export coverage remains intact.The production checker and its policy mirror should prove the same fail-closed behavior.
Acceptance criteria
check:archfailing.RULESboundary is rejected.History
Originally filed during review of #641 after finding that the then-current dynamic-import regex matched only single- and double-quoted arguments. That exact bypass was fixed in #646. This issue now tracks only the remaining fail-open behavior for non-static dynamic imports in the generic regex-backed architecture rules.