Problem
Rule files (.rules.ts) must be fully self-contained — the scanner (src/engine/rule-scanner.ts, ALLOWED_MODULES) permits only node:path|url|util|crypto. This is a sound default, and I understand the rationale in the scanner's comment and ASSURANCE-CASE.md: a .rules.ts runs in-process, so a relative import to a file the scanner never sees is an ACE vector.
The cost is that any helper shared across multiple ADRs' rule files (frontmatter parsing, config resolution, glob helpers, kebab-case validation, …) must be copy-pasted into every rule file, along with its tests. Past a handful of ADRs this is real code and test duplication, with no single source of truth and silent drift between copies.
Proposal (security-preserving, opt-in)
Keep the default exactly as-is: relative imports stay blocked. Add an opt-in field in .archgate/config.json by which a project may declare allowed relative import paths — constrained to within .archgate/ itself (e.g. .archgate/lib). This is a hard boundary, not merely a default: any specifier that canonicalizes to a path outside .archgate/ (including .. escapes) is rejected even when configured. Bare specifiers and non-relative paths remain blocked (unchanged — the node_modules/path shadowing concern is unaffected).
Rationale: .archgate/ is already the reviewed governance tree. Containing shared helpers there keeps all governance code — rules and their helpers — in one audited boundary, and the containment rule means the config can never reach arbitrary project code or node_modules. The runtime already supports this: loader.ts imports rule files in place via import(pathToFileURL(...)) on Bun, which transpiles .ts on import — so .archgate-local relative imports resolve today; only the scanner blocks them.
Optional additional hardening (maintainer's choice)
If even governance-local helpers should stay within the capability sandbox, imported .archgate/-local files could be run transitively through the same scanRuleSource allowlist, so a helper still can't reach child_process/fetch/eval. rule-scanner.ts:521–533 suggests imported-file scanning already existed and converged with first-party scanning, so the primitive is largely present.
Scope
The change looks localized to rule-scanner.ts (+ threading config through scanRuleSource, which has a single call site in loader.ts) plus a canonicalize-and-contain path check. Happy to open a PR if the direction is welcome — wanted to align on the security model first.
Problem
Rule files (
.rules.ts) must be fully self-contained — the scanner (src/engine/rule-scanner.ts,ALLOWED_MODULES) permits onlynode:path|url|util|crypto. This is a sound default, and I understand the rationale in the scanner's comment andASSURANCE-CASE.md: a.rules.tsruns in-process, so a relative import to a file the scanner never sees is an ACE vector.The cost is that any helper shared across multiple ADRs' rule files (frontmatter parsing, config resolution, glob helpers, kebab-case validation, …) must be copy-pasted into every rule file, along with its tests. Past a handful of ADRs this is real code and test duplication, with no single source of truth and silent drift between copies.
Proposal (security-preserving, opt-in)
Keep the default exactly as-is: relative imports stay blocked. Add an opt-in field in
.archgate/config.jsonby which a project may declare allowed relative import paths — constrained to within.archgate/itself (e.g..archgate/lib). This is a hard boundary, not merely a default: any specifier that canonicalizes to a path outside.archgate/(including..escapes) is rejected even when configured. Bare specifiers and non-relative paths remain blocked (unchanged — thenode_modules/pathshadowing concern is unaffected).Rationale:
.archgate/is already the reviewed governance tree. Containing shared helpers there keeps all governance code — rules and their helpers — in one audited boundary, and the containment rule means the config can never reach arbitrary project code ornode_modules. The runtime already supports this:loader.tsimports rule files in place viaimport(pathToFileURL(...))on Bun, which transpiles.tson import — so.archgate-local relative imports resolve today; only the scanner blocks them.Optional additional hardening (maintainer's choice)
If even governance-local helpers should stay within the capability sandbox, imported
.archgate/-local files could be run transitively through the samescanRuleSourceallowlist, so a helper still can't reachchild_process/fetch/eval.rule-scanner.ts:521–533suggests imported-file scanning already existed and converged with first-party scanning, so the primitive is largely present.Scope
The change looks localized to
rule-scanner.ts(+ threading config throughscanRuleSource, which has a single call site inloader.ts) plus a canonicalize-and-contain path check. Happy to open a PR if the direction is welcome — wanted to align on the security model first.