fix(engine): scan top-level export declarations with a null source - #493
Conversation
ESTree sets `source: null` on an `export` declaration that has no `from`
clause (`export function`, `export const`, `export { local }`). The AST node
schema typed `source` as optional-only, so Zod validation failed on the node,
`parseNode` dropped it, and the walk skipped its entire subtree.
Anything dangerous inside a top-level export — a banned global, `eval`, a
dynamic `import("node:child_process")` — therefore went unscanned, a silent
false-negative that `archgate check` reported as a pass. The schema now
tolerates the null so the node stays in the walk; `checkModuleSpecifier`
already no-ops on a null source, so re-exports are unaffected.
Adds regression tests: a banned global inside `export function` and
`export const` (previously unscanned), plus `export { x } from "node:fs"`
as a positive control that re-export scanning still fires.
Signed-off-by: hancrafted <hancrafted@users.noreply.github.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📜 Recent review details🧰 Additional context used📓 Path-based instructions (6)**/*.{ts,tsx,js,jsx,mjs,cjs}📄 CodeRabbit inference engine (.archgate/adrs/ARCH-006-dependency-policy.md)
Files:
tests/**/*.ts📄 CodeRabbit inference engine (.archgate/adrs/ARCH-009-platform-detection-helper.md)
Files:
{src,tests}/**/*.ts📄 CodeRabbit inference engine (.archgate/adrs/LEGAL-001-spdx-license-headers.md)
Files:
tests/**/*.test.ts📄 CodeRabbit inference engine (.archgate/adrs/ARCH-005-testing-standards.md)
Files:
tests/engine/rule-scanner.test.ts📄 CodeRabbit inference engine (.archgate/adrs/ARCH-024-rule-file-sandbox-boundary.md)
Files:
**⚙️ CodeRabbit configuration file
Files:
🔇 Additional comments (1)
📝 WalkthroughWalkthroughUpdated AST validation to accept 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/engine/rule-scanner.test.ts`:
- Around line 97-122: Add a test in the “top-level export declarations are
scanned” suite covering a local export without a from clause, such as exporting
fetch under an alias, and assert that the scan reports the banned-global
violation. Keep the existing declaration-export and re-export cases unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: d4e5ab97-8eaa-41b5-ac43-bd4f4622dbd4
📒 Files selected for processing (2)
src/engine/rule-scanner.tstests/engine/rule-scanner.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx,js,jsx,mjs,cjs}
📄 CodeRabbit inference engine (.archgate/adrs/ARCH-006-dependency-policy.md)
**/*.{ts,tsx,js,jsx,mjs,cjs}: Use Bun built-ins for file I/O (Bun.file,Bun.write), HTTP, subprocess execution (Bun.spawn), globbing (Bun.Glob), and testing (bun:test).
Do not use Node.js-specific APIs when Bun alternatives exist; for example, useBun.file()instead offs.readFile()for simple reads.
Prefernode:built-in modules such asnode:util,node:path, andnode:fsover npm alternatives.
Do not use utility libraries for single functions, such as importing lodash for one helper likepick.
Do not use path aliases (tsconfig paths); use relative imports with Bun's native module resolution.
Do not install packages globally during development; usebunxfor one-off tools.
Files:
tests/engine/rule-scanner.test.tssrc/engine/rule-scanner.ts
tests/**/*.ts
📄 CodeRabbit inference engine (.archgate/adrs/ARCH-009-platform-detection-helper.md)
In test files, use
_resetPlatformCache()to simulate different platforms instead of mocking or mutatingprocess.platformdirectly.
tests/**/*.ts: Use Bun's built-inbun:testrunner for all tests; do not import test utilities fromnode:test.
Shared test helpers must also userestoreEnvwhen restoring environment variables.
Files:
tests/engine/rule-scanner.test.ts
{src,tests}/**/*.ts
📄 CodeRabbit inference engine (.archgate/adrs/LEGAL-001-spdx-license-headers.md)
{src,tests}/**/*.ts: Every TypeScript source file insrc/andtests/must begin with// SPDX-License-Identifier: Apache-2.0followed by// Copyright 2026 Archgate.
If a TypeScript file has a shebang line (for example#!/usr/bin/env buninsrc/cli.ts), the SPDX license header must appear immediately after the shebang.
Use single-line//comments for the SPDX header; do not use block comments (/* */) or alternate license identifiers.
Files:
tests/engine/rule-scanner.test.tssrc/engine/rule-scanner.ts
tests/**/*.test.ts
📄 CodeRabbit inference engine (.archgate/adrs/ARCH-005-testing-standards.md)
tests/**/*.test.ts: Place tests intests/mirroring thesrc/directory structure, and name test files with the.test.tssuffix.
Test public module interfaces rather than private implementation details, use descriptive test names, and do not depend on network access.
Use isolated temporary directories created withmkdtempfor filesystem tests, and clean them up inafterEachorafterAll. Do not use hardcoded user or system paths.
Close external SDK instances such as servers, clients, and transports inafterEachorafterAll; manage their lifecycle in test hooks rather than individual test bodies.
When a temporary git repository performs commits, configure localuser.emailanduser.nameimmediately aftergit init; do not rely on global git identity.
Every runnabletest()orit()must contain at least oneexpect()assertion. Make implicit no-throw contracts explicit withnot.toThrow()orresolves.toBeUndefined(). Usetest.skiportest.todofor intentional placeholders.
Usetest.skipIf(condition),test.skip, ortest.todofor conditional or intentionally disabled tests; do not use bare early returns or empty callbacks to skip tests, and do not skip without a tracking issue.
When adding assertions to an older test file, importexpectfrombun:test.
Mock HTTP requests by assigning directly toglobalThis.fetch, and restore the original fetch implementation or mock inafterEach; do not mocknode:fetch.
Mock first-party modules withimport * as modandspyOn(mod, "fn"), restoring spies after each test; do not use process-globalmock.module()for first-party modules.
Wrap inlinespyOnormockImplementationusage intry/finallysomockRestore()always runs, or manage spies inbeforeEachandafterEach.
When redirecting user-scope paths, mocknode:os'shomedir()rather than relying on runtimeHOMEoverrides; use environment overrides only for code that reads environment variables directly at call time.
Re...
Files:
tests/engine/rule-scanner.test.ts
tests/engine/rule-scanner.test.ts
📄 CodeRabbit inference engine (.archgate/adrs/ARCH-024-rule-file-sandbox-boundary.md)
Preserve scanner diagnostic and source-position coverage for blocked identifiers and properties.
Files:
tests/engine/rule-scanner.test.ts
**
⚙️ CodeRabbit configuration file
**: This project uses Archgate — an AI governance framework based on
Architecture Decision Records (ADRs). The ADRs in.archgate/adrs/
are the authoritative rules for this codebase. Each ADR has a companion
.rules.tsfile with automated checks that run viaarchgate check.When reviewing, you must:
- Treat ADR violations as blocking issues, not suggestions.
- Cite the specific ADR ID when flagging a violation (e.g., "Violates ARCH-006").
- Focus on semantic and contextual violations that automated rules cannot catch —
the.rules.tsfiles already cover syntactic/structural patterns.- If you are unsure whether something violates an ADR, flag it as a question
rather than approving it.
Files:
tests/engine/rule-scanner.test.tssrc/engine/rule-scanner.ts
src/**/*.ts
📄 CodeRabbit inference engine (.archgate/adrs/ARCH-004-no-barrel-files.md)
src/**/*.ts: Do not re-export symbols from another module in any source file; statements likeexport { X } from "./other"andexport type { X } from "./other"are forbidden.
Import symbols directly from the module that defines them; do not import from a directory path such as../formatsexpecting implicitindex.tsresolution.
src/**/*.ts: Useawait Bun.file(path).json()when reading JSON files in Bun TypeScript source code; do not useJSON.parse(await Bun.file(path).text())orJSON.parse(fs.readFileSync(path, "utf-8"))for file reads.
UseBun.JSONC.parse()when reading files that may contain comments, such astsconfig.json, instead of plainJSON.parse()on file contents.
ReserveJSON.parse()for parsing JSON strings from non-file sources such as API responses or string variables; do not use it as the default for reading JSON files.
src/**/*.ts: In TypeScript source files undersrc/, useBun.envinstead ofprocess.envfor all environment variable reads and writes;process.envmust not be used.
In TypeScript source files undersrc/, use nullish coalescing for environment-variable defaults, e.g.Bun.env.NODE_ENV ?? "production".
In TypeScript source files undersrc/, useBoolean(Bun.env.CI)for truthy checks on environment flags.
In TypeScript source files undersrc/, do not destructureBun.env(for example,const { HOME } = Bun.env); access properties individually instead.
In TypeScript source files undersrc/, do not referenceprocess.enveven in comments that suggest using it.
src/**/*.ts: Heavy dependencies such asinquirer,posthog-node,@sentry/*, and any similarly expensive runtime-only dependency MUST be loaded with dynamicimport()at the point of use, never as a static value import at module top level.
Type-only references to heavy modules must useimport type(for example,import type { PostHog } from "posthog-node"orimport type * as SentryNs from "@sentry/node-core/light"); type-only...
Files:
src/engine/rule-scanner.ts
src/**/!(*platform).ts
📄 CodeRabbit inference engine (.archgate/adrs/ARCH-009-platform-detection-helper.md)
src/**/!(*platform).ts: Insrc/TypeScript source files, do not readprocess.platformdirectly; usesrc/helpers/platform.ts(isWindows(),isMacOS(),isLinux(),isWSL(),getPlatformInfo()) for all platform detection.
Use the centralized platform helper instead of duplicating OS/WSL detection logic inline anywhere insrc/TypeScript source.
When behavior differs between Linux and Windows, account for WSL by usingisWSL()rather than assuming `
Files:
src/engine/rule-scanner.ts
src/{helpers,engine}/**/*.ts
📄 CodeRabbit inference engine (.archgate/adrs/ARCH-002-error-handling.md)
Do not use
console.log(),console.warn(), orconsole.info()directly in helper or engine files; uselogInfo()orlogWarn()instead.
Files:
src/engine/rule-scanner.ts
src/engine/**/*.ts
📄 CodeRabbit inference engine (.archgate/adrs/ARCH-023-engine-file-listing-via-in-memory-git-tracked-matching.md)
src/engine/**/*.ts: In the rules engine, list project files by matching glob patterns in memory against the git-tracked file set rather than walking the filesystem.
Route new engine file listings throughlistMatchingFilesfor rule-facing inputs ormatchTrackedFilesfor trusted ADR frontmatter patterns.
When the target is a Git repository andrespectGitignoreis not false, pass the tracked set fromgetGitTrackedFilesto matching operations.
Per-runRunCachesmust share glob results keyed by pattern and tracked mode and file text keyed by absolute path; cached promises should share in-flight work, and returned glob arrays must be copied before exposing them to rules.
Do not cachereadJSONresults because rules receive mutable objects whose shared mutation could leak between rules.
Do not filter filesystem scan results against the tracked set as a substitute for in-memory matching, and do not hardcode ignore directories; use the Git-derived tracked set instead.
Files:
src/engine/rule-scanner.ts
src/engine/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.archgate/adrs/ARCH-022-ast-aware-rule-context.md)
src/engine/**/*.{ts,tsx}: For TypeScript and JavaScript,ctx.ast()must reuse the in-processmeriyahparser and must not spawn a subprocess.
Python and Ruby AST parsing must use only their interpreters' standard-library facilities through guardedBun.spawninvocations; no third-party parser, native binding, or WASM grammar may be introduced.
Before any Python or Ruby subprocess is spawned, execute guardrails in exactly this order: path safety viasafePath(), language plausibility validation, interpreter availability probing, then guarded invocation.
Use array-basedBun.spawnarguments only for Python/Ruby AST execution; never shell-interpolate paths or file contents.
Run Python AST subprocesses withpython -I -c ...isolation, and strip a leading UTF-8 BOM before parsing Python and Ruby source.
Cache Python/Ruby interpreter availability once percheckinvocation rather than probing once per file.
ctx.ast()must throw on missing interpreters and parse failures, with distinguishable error messages; it must never returnnullor another silent-failure sentinel.
Supportast(path, language, { rev: "base" })andfileAtBase(path)using the merge base of--baseand HEAD. Base AST access must throw for an unresolved base or missing base file, whilefileAtBase()returnsnullfor those cases.
When{ comments: true }is requested, attach structured comments withtype, delimiter-strippedvalue, and sourceloc; omit comments unless explicitly requested. Ruby comment extraction must currently throw as unsupported.
Extract TypeScript/JavaScript comments from the original source, not transpiled output, and do not treat regex literals as comment-aware; Python comments must usetokenize.
Do not trustnode.locfor TypeScript AST results because parsing transpiled output changes positions; re-locate constructs in the original source before reporting lines. JavaScript locations are source-accurate.
Do not invokeBun.spawn,child_process, git, ...
Files:
src/engine/rule-scanner.ts
src/engine/rule-scanner.ts
📄 CodeRabbit inference engine (.archgate/adrs/ARCH-022-ast-aware-rule-context.md)
Factor duplicated
parseModule()calls into a shared exported parse helper used by bothrule-scanner.tsand the TypeScript/JavaScriptctx.ast()implementation.
src/engine/rule-scanner.ts: Statically scan every.rules.tssource withscanRuleSource()and require zero violations before execution.
Use an allowlist, never a denylist, for module specifiers; only explicitly permitted modules may be imported.
Allow only thenode:path,node:url,node:util, andnode:cryptospecifiers; never allow their bare equivalents ornode:module.
Route static imports, literal and non-literal dynamic imports, named re-exports, and star re-exports through the same module-specifier check; reject non-literal dynamic imports.
Block code references to dangerous runtime globals, includingBun,process,globalThis,global,self,Reflect,eval,Function,fetch,WebSocket,XMLHttpRequest, andrequire, except when used only as property keys.
Reject.constructoraccess on any receiver in dotted, computed-literal, and destructuring binding-pattern forms, including renamed, computed-string, and shorthand keys.
UsestaticPropName()or equivalent logic to match both dotted properties (o.name) and computed literal properties (o["name"]).
Keep first-party and imported rule scanning converged:scanImportedRuleSource()should delegate toscanRuleSource()unless a genuinely imported-only restriction is required.
Run a raw-source integrity scan before transpilation, rejecting bidi controls, directional marks, and zero-width or invisible characters while permitting a leading BOM.
Do not use raw-text searches to detect dangerous identifiers or module names; use AST analysis for semantic checks.
Spell blocked code points numerically, such as0x202e, rather than as literal characters or\uescapes.
Files:
src/engine/rule-scanner.ts
🔇 Additional comments (1)
src/engine/rule-scanner.ts (1)
188-188: LGTM!Also applies to: 210-219
|
@hancrafted thanks for the PR. Please fix the changes pointed by CodeRabbit |
CodeRabbit suggested a test asserting a banned-global violation for
`export { fetch as local };`, but that assertion is provably false:
Bun.Transpiler erases an export specifier that names no local binding
(the source becomes `export {}`), so no reference reaches the AST walk,
and naming the global in a specifier is not valid ESM.
Add the local-export case asserting the correct outcome (no violation)
with a comment noting the declaration-form tests are what guard the
`source: null` subtree against a schema regression.
Signed-off-by: hancrafted <hancrafted@users.noreply.github.com>
The exotic-literal fix and the earlier null-`source` fix (#493) are the same class: a node `AstNodeSchema` rejects is dropped by `parseNode` together with its whole subtree, so a payload underneath is never walked and every blocking rule in clauses 1-6 runs on a tree it is no longer in. ARCH-024 governed what the scanner blocks but never stated that the walk must reach the node first. Adds Decision clause 7 (the walk visits every node; the schema must not reject a node over a field it does not read), a matching DO/DON'T, a Risks entry with mitigation, and Manual Enforcement item 10 treating any `AstNodeSchema` narrowing as security-relevant. Records the operational gotchas (node-drop = silent scan gap, `z.unknown()` non-optional in Zod v4, transpiler dead-code stripping) in agent memory. Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
rhuanbarreto
left a comment
There was a problem hiding this comment.
Thanks a lot for the contribution! LGTM!
### The bug An AST node that fails `AstNodeSchema.safeParse` is dropped by `parseNode` **together with its entire subtree**, because the walk skips any node that fails to parse (`if (child) walk(child)`). Anything dangerous underneath that node therefore goes unscanned — a silent false-negative that `archgate check` reports as a pass. This is the same class of bug as the null-`source` fix in #493, which patched one trigger. A `Literal`'s `value` is the schema's only *other* leaf that can fail validation: `type` is always present on an ESTree node, and every other typed field (`source`, `object`, `property`, `callee`, `left`) recurses back into the schema, bottoming out at `value`. Meriyah emits two value shapes the narrow union rejected: - a plain **object** for a `RegExpLiteral` (`value: {}`) - a **`bigint`** for `123n` So a payload reached *off* such a literal escaped the walk entirely: - `/x/.constructor.constructor` — the Function constructor (= `eval`) off a RegExp receiver - `(123n).constructor.constructor` — same, off a bigint receiver - a banned global or dynamic import sitting beside the literal (`/x/ + fetch("…")`, `[/x/, import("node:child_process")]`) Naming `Function`/`eval` directly is already blocked, but the `.constructor` chain is the property-route around that — and it was slipping through whenever its receiver was one of these literals. ### The fix Widen `value` to `z.unknown().optional()` (and `AstNode.value?: unknown`). `value` is only ever *read* through `typeof … === "string"` guards (`staticPropName`, `checkModuleSpecifier`), so the scanner never consumes its shape — there is no reason to reject a node over it. No ESTree node can now fail validation, so none is silently dropped from the walk. ### Tests New escape regressions in `rule-scanner-escapes.test.ts`: - Function-constructor chain off a RegExp literal — unscanned before, caught now - Function-constructor chain off a bigint literal — unscanned before, caught now - banned global / dynamic import / `import.meta.require` beside a RegExp or bigint literal - positive controls: a clean RegExp literal and a clean bigint literal still pass (the fix keeps the node in the walk, it does not start flagging the literal) One case is deliberately *not* tested as a hole: `/x/ || import(…)` is stripped by the transpiler as provably-dead code (a RegExp literal is always truthy), so the import genuinely never runs and correctly is not scanned. The tests use reachable positions only, with that reasoning documented inline. `bun run validate` passes locally: oxlint, `tsc --build`, `oxfmt --check`, full test suite, `archgate check` 44/44, compile. The commit is DCO signed-off. ### Governance Extends **ARCH-024 (Rule File Sandbox Boundary)** with a new Decision clause (7) codifying the root class both this fix and #493 belong to: a node rejected by `AstNodeSchema` is dropped by `parseNode` *with its entire subtree*, so the (correct) blocking rules in clauses 1–6 never run on the payload. The clause requires the walk to reach every node and the schema to reject nothing a valid ESTree node can contain. Adds a matching DO/DON'T, a Risk + mitigation, and a Manual Enforcement item treating any `AstNodeSchema` narrowing as security-relevant. --------- Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
# archgate ## [0.51.0](v0.50.0...v0.51.0) (2026-07-26) ### Features * **adrs:** enforce concise, forward-only code comments (GEN-004) ([#496](#496)) ([9a114b3](9a114b3)), references [#2123](https://github.com/archgate/cli/issues/2123) * **adrs:** flag stray files at the repository root (GEN-005) ([#535](#535)) ([6a6e765](6a6e765)), closes [#514](#514), references [#500](#500) * **engine:** add ctx.readYAML and ctx.checkCase rule helpers ([#497](#497)) ([c5d82c5](c5d82c5)), closes [#490](#490), references [#490](#490) [#491](#491) [#499](#499) [#499](#499) [#499](#499) [#499](#499) * report truncated ADR briefings, trim the ADR corpus 15.6%, add GEN-005 briefing budget ([#501](#501)) ([a9dab40](a9dab40)) ### Bug Fixes * **docs:** relocate ADR content to clear briefing-budget warnings ([#531](#531)) ([c7419b3](c7419b3)) * **docs:** restore pt-br diacritics and enforce locale content integrity ([#523](#523)) ([db39104](db39104)), closes [#516](#516), references [#231](#231) * **engine:** allow symlinks that resolve inside the project root ([#500](#500)) ([387bf15](387bf15)) * **engine:** reject rule-file reads through a symlinked ancestor directory ([#499](#499)) ([a555f9d](a555f9d)), references [#497](#497) [#491](#491) [#497](#497) [#497](#497) [#497](#497) [#497](#497) * **engine:** scan top-level export declarations with a null source ([#493](#493)) ([d07db03](d07db03)), closes [#491](#491) * **engine:** stop dropping AST nodes with exotic literal values ([#494](#494)) ([0015542](0015542)), closes [#493](#493) [#493](#493) * **lint:** resolve no-bare-env-restore by captured key and lexical scope ([#524](#524)) ([7094a3a](7094a3a)), closes [#498](#498) * **rules:** make ARCH-020 and ARCH-023 match ctx.ast() instead of raw text ([#533](#533)) ([ad5529b](ad5529b)), closes [#513](#513), references [#486](#486) * **tests:** replace bun:test anti-patterns with idiomatic patterns ([#512](#512)) ([bcb086f](bcb086f)) --- This PR was generated with [simple-release](https://github.com/TrigenSoftware/simple-release). <details> <summary>📄 Cheatsheet</summary> <br> You can configure the bot's behavior through a pull request comment using the `!simple-release/set-options` command. ### Command Format ````md !simple-release/set-options ```json { "bump": {}, "publish": {} } ``` ```` ### Useful Parameters #### Bump | Parameter | Type | Description | |-----------|------|-------------| | `version` | `string` | Force set specific version | | `as` | `'major' \| 'minor' \| 'patch' \| 'prerelease'` | Release type | | `prerelease` | `string` | Pre-release identifier (e.g., "alpha", "beta") | | `firstRelease` | `boolean` | Whether this is the first release | | `skip` | `boolean` | Skip version bump | | `byProject` | `Record<string, object>` | Per-project bump options for monorepos | #### Publish | Parameter | Type | Description | |-----------|------|-------------| | `skip` | `boolean` | Skip publishing | | `access` | `'public' \| 'restricted'` | Package access level | | `tag` | `string` | Tag for npm publication | ### Usage Examples #### Force specific version ````md !simple-release/set-options ```json { "bump": { "version": "2.0.0" } } ``` ```` #### Force major bump ````md !simple-release/set-options ```json { "bump": { "as": "major" } } ``` ```` #### Create alpha pre-release ````md !simple-release/set-options ```json { "bump": { "prerelease": "alpha" } } ``` ```` #### Publish with specific access and tag ````md !simple-release/set-options ```json { "bump": { "prerelease": "beta" }, "publish": { "access": "public", "tag": "beta" } } ``` ```` ### Custom Changelog Preamble You can add custom markdown to the top of the changelog (right after the version header) using the `!simple-release/set-preamble` command. The markdown after the command line becomes the preamble. ```md !simple-release/set-preamble ## What's new? - The website was completely redesigned - The new API gives you awesome possibilities ``` In a monorepo, pass the full package name after the command to target a single package's changelog. Wrap the name in backticks so GitHub keeps it as text instead of a mention: ```md !simple-release/set-preamble `@your-org/core` ## Core changes - New plugin system ``` Use one comment per package, plus one without a name for the whole release. ### Access Restrictions The commands can only be used by users with permissions: - repository owner - organization member - collaborator ### Notes - The last comment with `!simple-release/set-options` command takes priority - The last `!simple-release/set-preamble` comment per package takes priority - JSON must be valid, otherwise the `set-options` command will be ignored - Parameters apply only to the current release execution - The commands can be updated by editing the comment or adding a new one </details> <!-- Please do not edit this comment. simple-release-pull-request: true simple-release-branch-from: release simple-release-branch-to: main --> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Splits the scanner fix out of #491 (now closed) into a standalone PR, as suggested there.
The bug
ESTree sets
source: nullon anyexportdeclaration with nofromclause —export function,export const,export { local }.AstNodeSchematypedsourceas optional-only, sosafeParsefailed on the node,parseNodereturnednull, and the walk (if (child) walk(child)) skipped the node and its entire subtree.A banned global,
eval, or a dynamicimport("node:child_process")nested inside a top-levelexportwas therefore never scanned — a silent false-negative thatarchgate checkreports as a pass.The fix
Make
source.nullable().optional()in the schema (andAstNode.source?: AstNode | null) so the node stays in the walk.checkModuleSpecifieralready no-ops on a null source, so re-exports (export { x } from "…") are unaffected. No behavior change beyond no longer dropping these nodes.Tests
Three regression tests in
rule-scanner.test.ts:export function— unscanned before, caught nowexport const— unscanned before, caught nowexport { x } from "node:fs"— positive control, confirms re-export scanning still firesbun run validatepasses locally: oxlint,tsc --build,oxfmt --check, 1579 tests,archgate check44/44, compile.The commit is DCO signed-off.