feat(compiler): SYN009 — warn on XMLHttpRequest() calls that bypass the net capability model (?bs 0.7+)#150
Open
marcelofarias wants to merge 7 commits into
Open
feat(compiler): SYN009 — warn on XMLHttpRequest() calls that bypass the net capability model (?bs 0.7+)#150marcelofarias wants to merge 7 commits into
marcelofarias wants to merge 7 commits into
Conversation
…he net capability model (?bs 0.7+)
`new XMLHttpRequest()` opens an HTTP connection at runtime that is invisible
to CAP001 (which checks http.* member calls). Same bypass class as fetch
(SYN007) and WebSocket (SYN008) — no `uses { net }` will reflect it in the
fn header, callers cannot see the network dependency.
Detection: `XMLHttpRequest` not preceded by `.`/`?.`, followed by `(`,
`?.(`, or `<T>(` (including nested generics via >>/>>>> depth scan).
`obj.XMLHttpRequest(...)` and bare `XMLHttpRequest` references are excluded.
Suppressed inside `unsafe {}` blocks and `unsafe "reason" fn` bodies.
Note: should be merged after PR #149 (SYN008) lands; SYN009 is the next
available code in the bypass-detection series.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a new compiler warning SYN009 to detect XMLHttpRequest constructions/calls inside function bodies (at ?bs 0.7+) because they can perform network I/O while bypassing the declared net capability surface, and wires that diagnostic through the registry, MCP explain output, docs, and tests.
Changes:
- Adds SYN009 to the compiler’s diagnostic registry and the syntax-check pass (
syn-check) with unsafe suppression support. - Adds MCP long-form explanation text for SYN009 and registers it in MCP/server code lists.
- Adds a dedicated SYN009 compiler test suite and updates exhaustive diagnostic allowlists / documentation tables.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Adds SYN009 to the MCP explain tool’s documented stable-code list. |
| packages/mcp/tests/server.test.ts | Adds SYN009 to the MCP server’s known codes list for explanations. |
| packages/mcp/src/explanations.ts | Adds long-form explain content and examples for SYN009. |
| packages/compiler/tests/syn009-check.test.ts | Introduces targeted SYN009 detection/suppression tests. |
| packages/compiler/tests/error-codes.test.ts | Adds SYN009 to the compiler’s exhaustive error-code allowlist. |
| packages/compiler/src/passes/syn-check.ts | Implements SYN009 token-based detection and unsafe suppression. |
| packages/compiler/src/error-codes.ts | Adds the SYN009 registry entry (rule/idiom/rewrite/example). |
| AGENTS.md | Documents SYN009 in the diagnostic table. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Fix diagnostic message: remove trailing prose after closing `}` to match the style of SYN005/SYN006 (message ends cleanly with the unsafe block) - Detect `new XMLHttpRequest` without parens — bare new-expressions are valid JS/TS constructions and bypass the net capability model equally - Add regression test for the no-parens form
8 tasks
… positive and update comment examples - Exclude member-access-on-constructor form (`new XMLHttpRequest.prototype.open()`) from SYN009 by checking for `.` as the next token in the no-parens branch - Add regression test covering that form - Update header comment to drop incorrect `(url)` from constructor examples and mention the no-parens form explicitly
…s are skipped Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nderflow, improve message
- Fire on `new XMLHttpRequest<T>` (no call parens) — valid TS construction form was skipped
- Fix depth scan: use Math.max(0, depth - len) to prevent depth going negative on >> or >>>
- Use syn009.code instead of hardcoded "SYN009" string to avoid drift
- Update diagnostic message to recommend http.get()/http.post() as primary fix
- Update header comment: remove $require("net") guidance (inconsistent with rest of PR);
add no-parens form to documented detection surface
- Add regression test for generic no-paren construction form
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change "call" to "construction" in SYN009 title (error-codes.ts and explanations.ts) - Add `new XMLHttpRequest` (no-parens) and generic no-parens form to rule text in error-codes.ts - Add no-parens forms to opening sentence and detection description in explanations.ts - Update AGENTS.md SYN009 row to mention all detected construction forms Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
marcelofarias
pushed a commit
that referenced
this pull request
Jun 12, 2026
…k calls in fn bodies (?bs 0.7+)
- Adds **SYN010**: a new warning that fires when a fn body calls `setTimeout(...)`,
`setInterval(...)`, or `queueMicrotask(...)` at `?bs 0.7+`
- These globals schedule callbacks that run after the fn returns — any effects inside
those callbacks are invisible to callers: no capability declaration, no `writes {}`
label, and no `throws {}` entry can cover them
- Same bypass class as `fetch` (SYN007) and `WebSocket` (SYN008): real side effects
that sidestep the declared capability surface, but deferred rather than immediate
- Suppressed inside `unsafe "reason" { }` blocks and `unsafe "reason" fn` bodies
- Member calls (`obj.setTimeout(...)`) and bare references (without `(`) are excluded
- Note: should be merged after PR #150 (SYN009) lands; SYN010 is the next available code
Changes:
- error-codes.ts: add SYN010 entry with rule/idiom/rewrite/example
- syn-check.ts: add SYN010 detection; add SYN006 + SYN010 to module header comment;
fix outdated unsafe-fn skip comment
- tests/syn010-check.test.ts: 12 tests covering all three globals, optional-call form,
severity, version gating, unsafe suppression, member-call exclusion, bare reference
- tests/error-codes.test.ts: add SYN010 to exhaustive allowlist
- mcp/src/explanations.ts: add SYN010 long-form explanation with fails/passes examples
- mcp/tests/server.test.ts: add SYN010 to KNOWN_CODES list
- AGENTS.md: add SYN010 row to diagnostic table
- README.md: add SYN010 to explain tool code list
marcelofarias
added a commit
that referenced
this pull request
Jun 12, 2026
…k calls in fn bodies (?bs 0.7+)
* feat(compiler): SYN010 — warn on setTimeout/setInterval/queueMicrotask calls in fn bodies (?bs 0.7+)
- Adds **SYN010**: a new warning that fires when a fn body calls `setTimeout(...)`,
`setInterval(...)`, or `queueMicrotask(...)` at `?bs 0.7+`
- These globals schedule callbacks that run after the fn returns — any effects inside
those callbacks are invisible to callers: no capability declaration, no `writes {}`
label, and no `throws {}` entry can cover them
- Same bypass class as `fetch` (SYN007) and `WebSocket` (SYN008): real side effects
that sidestep the declared capability surface, but deferred rather than immediate
- Suppressed inside `unsafe "reason" { }` blocks and `unsafe "reason" fn` bodies
- Member calls (`obj.setTimeout(...)`) and bare references (without `(`) are excluded
- Note: should be merged after PR #150 (SYN009) lands; SYN010 is the next available code
Changes:
- error-codes.ts: add SYN010 entry with rule/idiom/rewrite/example
- syn-check.ts: add SYN010 detection; add SYN006 + SYN010 to module header comment;
fix outdated unsafe-fn skip comment
- tests/syn010-check.test.ts: 12 tests covering all three globals, optional-call form,
severity, version gating, unsafe suppression, member-call exclusion, bare reference
- tests/error-codes.test.ts: add SYN010 to exhaustive allowlist
- mcp/src/explanations.ts: add SYN010 long-form explanation with fails/passes examples
- mcp/tests/server.test.ts: add SYN010 to KNOWN_CODES list
- AGENTS.md: add SYN010 row to diagnostic table
- README.md: add SYN010 to explain tool code list
* fix(syn010): address Copilot review — hoist TIMER_GLOBALS, fix param name and reason string
- Hoist TIMER_GLOBALS to module scope (was re-created per fn, wasteful in hot pass)
- Rename `fn` parameter to `callback` in unsafe-fn test (fn is a reserved keyword)
- Fix unsafe reason string: "suspends execution" → "schedules deferred effect" (consistent with idiom)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(syn010): address remaining Copilot feedback — false positive on declarations and explanation codes
- syn-check: exclude function declarations named setTimeout/setInterval/queueMicrotask
(prev significant token is `function`) — these are definitions, not calls
- syn-check: exclude object/class method shorthands where after closing `)` is `{` or `:`
- tests: add two tests covering function-declaration and object-method-shorthand exclusions
- explanations: remove SYN007/SYN008 code references in SYN010 body; reference by name instead
to avoid dangling code cross-references until those PRs land
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(compiler): address SYN010 Copilot review feedback — exclude fn keyword declarations
- syn-check.ts: also skip `fn setTimeout(...) -> void {}` botscript-style nested
function declarations (prev token is `keyword`/`fn`), not just JS `function`
declarations — prevents false positives on nested fn declarations inside fn bodies
- syn010-check.test.ts: add test asserting SYN010 does NOT fire on
`fn setTimeout(delay: number) -> void { }` inside a fn body
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Marcelo Farias <mfarias@Marcelos-MacBook-Pro-4.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…mary fix
Rephrase the SYN009 diagnostic message to make the primary remediation
more actionable: "switch to http.get(url)/http.post(url, { body }) and
declare uses { net } on the fn header" makes clear that the http.* call
and the uses { net } declaration go together (CAP001 enforces the latter
once the former is present). The unsafe secondary path is unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment on lines
+821
to
+823
| "XMLHttpRequest is the predecessor to `fetch` in the browser HTTP API. Like `fetch` (SYN007) " + | ||
| "and `WebSocket` (SYN008), it makes real network calls at runtime but is invisible to " + | ||
| "botscript's capability model: CAP001 checks for `http.*` member calls, not the XHR global. " + |
Comment on lines
+827
to
+829
| "The risk class is the same as SYN007 and SYN008: static analysis cannot reason about the " + | ||
| "blast radius of a fn that quietly opens HTTP connections. Code-generation models still " + | ||
| "produce XHR patterns, especially in browser-targeting code.\n\n" + |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
XMLHttpRequestvianew XMLHttpRequest(), bareXMLHttpRequest(), or TypeScript instantiation formnew XMLHttpRequest<T>()at?bs 0.7+XMLHttpRequestopens an HTTP connection at runtime but is invisible to CAP001 (which only checkshttp.*member calls). A fn that constructs an XHR has an undeclarednetdependency: nouses { net }in the header, no signal to callers, no capability manifest entryfetch(SYN007) andWebSocket(SYN008): real network effects, invisible to the declared capability surfacefetch(SYN007), there is no stdlib XHR wrapper — the idiomatic fix isunsafe "reason" { new XMLHttpRequest() }or migrating tohttp.get()/http.post()unsafe "reason" { }blocks andunsafe "reason" fnbodiesChanges
packages/compiler/src/error-codes.tspackages/compiler/src/passes/syn-check.tsXMLHttpRequestnot preceded by./?., followed by(,?.(, or<T>((with>>/>>>depth scan); unsafe suppressionpackages/compiler/tests/syn009-check.test.tspackages/compiler/tests/error-codes.test.tspackages/mcp/src/explanations.tspackages/mcp/tests/server.test.tsAGENTS.mdREADME.mdexplaintool code listTest plan
pnpm -r buildpassesnpx vitest run— 11/11 SYN009 tests pass, 1160 total tests passnew XMLHttpRequest()XMLHttpRequest()(withoutnew)new XMLHttpRequest<Event>()new XMLHttpRequest<EventSource<Event>>()?bs 0.7unsafe {}orunsafe fnobj.XMLHttpRequest(...)(member call on a local)XMLHttpRequestreference (not called)XMLHttpRequest.prototype.open(property access, not call)explaintool returns long-form for SYN009🤖 Generated with Claude Code