Skip to content

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
mainfrom
botkowski/syn009-xhr-bypass
Open

feat(compiler): SYN009 — warn on XMLHttpRequest() calls that bypass the net capability model (?bs 0.7+)#150
marcelofarias wants to merge 7 commits into
mainfrom
botkowski/syn009-xhr-bypass

Conversation

@marcelofarias

Copy link
Copy Markdown
Owner

Summary

  • Adds SYN009: a new warning that fires when a fn body constructs an XMLHttpRequest via new XMLHttpRequest(), bare XMLHttpRequest(), or TypeScript instantiation form new XMLHttpRequest<T>() at ?bs 0.7+
  • XMLHttpRequest opens an HTTP connection at runtime but is invisible to CAP001 (which only checks http.* member calls). A fn that constructs an XHR has an undeclared net dependency: no uses { net } in the header, no signal to callers, no capability manifest entry
  • Same bypass class as fetch (SYN007) and WebSocket (SYN008): real network effects, invisible to the declared capability surface
  • Unlike fetch (SYN007), there is no stdlib XHR wrapper — the idiomatic fix is unsafe "reason" { new XMLHttpRequest() } or migrating to http.get() / http.post()
  • Suppressed inside unsafe "reason" { } blocks and unsafe "reason" fn bodies
  • Note: should be merged after PR feat(compiler): SYN008 — warn on WebSocket() calls that bypass the net capability model (?bs 0.7+) #149 (SYN008) lands; SYN009 is the next available code in the bypass series

Changes

File Change
packages/compiler/src/error-codes.ts Add SYN009 entry (rule, idiom, rewrite, example)
packages/compiler/src/passes/syn-check.ts Add SYN006 to header comment; detection: XMLHttpRequest not preceded by ./?., followed by (, ?.(, or <T>( (with >>/>>> depth scan); unsafe suppression
packages/compiler/tests/syn009-check.test.ts 11 tests: fires/not-fires cases, severity, unsafe suppression, member-call exclusion, generic forms, nested-generic form, bare reference, prototype access
packages/compiler/tests/error-codes.test.ts Add SYN009 to exhaustive allowlist
packages/mcp/src/explanations.ts Add SYN009 long-form explanation with fails/passes examples
packages/mcp/tests/server.test.ts Add SYN009 to KNOWN_CODES list
AGENTS.md Add SYN009 row to diagnostic table
README.md Add SYN009 to explain tool code list

Test plan

  • pnpm -r build passes
  • npx vitest run — 11/11 SYN009 tests pass, 1160 total tests pass
  • SYN009 fires on new XMLHttpRequest()
  • SYN009 fires on bare XMLHttpRequest() (without new)
  • SYN009 fires on TypeScript instantiation form new XMLHttpRequest<Event>()
  • SYN009 fires on nested generic form new XMLHttpRequest<EventSource<Event>>()
  • SYN009 does NOT fire below ?bs 0.7
  • SYN009 does NOT fire inside unsafe {} or unsafe fn
  • SYN009 does NOT fire on obj.XMLHttpRequest(...) (member call on a local)
  • SYN009 does NOT fire on bare XMLHttpRequest reference (not called)
  • SYN009 does NOT fire on XMLHttpRequest.prototype.open (property access, not call)
  • MCP explain tool returns long-form for SYN009

🤖 Generated with Claude Code

…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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/compiler/src/passes/syn-check.ts Outdated
Comment thread packages/compiler/src/passes/syn-check.ts
Comment thread packages/compiler/tests/syn009-check.test.ts
- 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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread packages/compiler/src/passes/syn-check.ts
Comment thread packages/compiler/src/passes/syn-check.ts Outdated
… 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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comment thread packages/compiler/src/passes/syn-check.ts Outdated
…s are skipped

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Comment thread packages/mcp/src/explanations.ts Outdated
Comment thread packages/compiler/src/passes/syn-check.ts Outdated
Comment thread packages/compiler/src/passes/syn-check.ts
Comment thread packages/compiler/src/passes/syn-check.ts Outdated
…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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.

Comment thread packages/compiler/src/error-codes.ts Outdated
Comment thread packages/compiler/src/error-codes.ts Outdated
Comment thread packages/mcp/src/explanations.ts Outdated
Comment thread packages/mcp/src/explanations.ts Outdated
Comment thread packages/mcp/src/explanations.ts
Comment thread AGENTS.md Outdated
- 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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

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>
@marcelofarias marcelofarias requested a review from Copilot June 12, 2026 06:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

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" +
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants