feat(compiler): SYN008 — warn on WebSocket() calls that bypass the net capability model (?bs 0.7+)#149
Open
marcelofarias wants to merge 8 commits into
Open
feat(compiler): SYN008 — warn on WebSocket() calls that bypass the net capability model (?bs 0.7+)#149marcelofarias wants to merge 8 commits into
marcelofarias wants to merge 8 commits into
Conversation
…t capability model (?bs 0.7+)
- `new WebSocket(url)`, `WebSocket(url)`, and TypeScript instantiation
forms like `new WebSocket<T>(url)` open persistent bidirectional
connections that are invisible to CAP001 (which only checks `http.*`
member calls). A fn that constructs a WebSocket has an undeclared net
dependency — no `uses { net }` reflects it.
- Same bypass class as fetch (SYN007) and console.* (SYN003): real
network effects, invisible to the declared capability surface.
- Unlike SYN007, there is no stdlib WebSocket wrapper, so the idiomatic
fix is to wrap in `unsafe "reason" { new WebSocket(url) }` rather than
replacing with an http.* call.
- Suppressed inside `unsafe {}` blocks and `unsafe "reason" fn` bodies.
- Detection: `WebSocket` not preceded by `.`/`?.`, followed by `(`,
`?.(`, or `<T>(`. Nested generics (e.g. `WebSocket<A<B>>`) handled
correctly via `>> / >>>` depth scan.
Files:
packages/compiler/src/error-codes.ts — SYN008 registry entry
packages/compiler/src/passes/syn-check.ts — detection pass
packages/compiler/tests/syn008-check.test.ts — 11 tests
packages/compiler/tests/error-codes.test.ts — add SYN008 to allowlist
packages/mcp/src/explanations.ts — long-form MCP explanation
packages/mcp/tests/server.test.ts — add SYN008 to KNOWN_CODES
AGENTS.md / README.md — document SYN008
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds compiler + MCP support for SYN008, a new ?bs 0.7+ warning that detects new WebSocket(...) / WebSocket(...) (including optional-call and TS generic-instantiation forms) as a capability-model bypass, with suppression inside unsafe {} and unsafe fn bodies.
Changes:
- Register SYN008 in the compiler diagnostic registry and add token-based detection in
syn-check. - Add a dedicated SYN008 test suite plus updates to “exhaustive allowlist” tests.
- Extend MCP/Docs surfaces (
explain, long-form explanation, AGENTS/README) to include SYN008.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Adds SYN008 to the explain tool’s supported diagnostic code list. |
| AGENTS.md | Documents SYN008 in the diagnostics table. |
| packages/compiler/src/error-codes.ts | Adds SYN008 registry entry (rule/idiom/rewrite/example). |
| packages/compiler/src/passes/syn-check.ts | Implements SYN008 detection + suppression behavior. |
| packages/compiler/tests/syn008-check.test.ts | Adds SYN008 behavior tests (fires / not-fires / suppression / forms). |
| packages/compiler/tests/error-codes.test.ts | Adds SYN008 to the exhaustive compiler diagnostic allowlist. |
| packages/mcp/src/explanations.ts | Adds long-form MCP explanation for SYN008 with fails/passes examples. |
| packages/mcp/tests/server.test.ts | Adds SYN008 to the stable KNOWN_CODES expectation list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…fires without http.* calls
Copilot review: the doc comment and warning message both suggested
`uses { net }` as part of the fix, but `uses { net }` without an
underlying `http.*` call triggers CAP002 (over-declared capability).
Fix both to recommend `unsafe { ... }` with an optional
`$require("net")`-checked wrapper instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
12 tasks
- Add SYN006 to header comment (was missing between SYN005 and SYN008) - Remove forward references to SYN007 in explanations.ts — SYN007 is not in this branch; reword to describe the fetch bypass class without the code
…ction for SYN008 The questionDot handler only matched `WebSocket?.(...)` but not `WebSocket?.<T>(...)`. Add generic-arg scanning (matching the existing `<T>(...)` branch) inside the questionDot branch, plus a regression test.
…s are skipped Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot stopped reviewing on behalf of
marcelofarias due to an error
June 11, 2026 14:45
…hod-shorthand exclusion
- Refactor to track parenIdx8 pointing at the actual `(` token for all forms
(direct, generic <T>, optional ?.(), optional-generic ?.<T>())
- Fix depth scan: use Math.max(0, depth - len) to prevent negative depth from
>> or >>> composite tokens causing premature scan exit
- Add method-shorthand exclusion: `{ WebSocket(url) { ... } }` no longer fires
- Shorten the diagnostic message to its actionable core
- Add two regression tests: method-shorthand exclusion and depth-3 generic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- syn-check.ts: update inline comment to list all detection forms
including `WebSocket?.<T>(...)` (optional-call-with-type-args)
- syn-check.ts: change diagnostic message from "constructs" to
"calls or constructs" to cover bare `WebSocket(url)` (no `new`)
- error-codes.ts: rephrase rule text — replace the misleading
"no `uses { net }` reflects it" with "no `http.*` call is present
to justify a `uses { net }` declaration" to clarify the constraint
- explanations.ts: rephrase Fix note so it doesn't imply you can't
add `uses { net }`; clarify that the compiler cannot verify the
net usage without an `http.*` call, and CAP002 will fire
- explanations.ts: add `?.<T>(` to the detection description
- AGENTS.md: update SYN008 table row to include
`WebSocket?.<T>(url)` form and use "calls or constructs"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- syn-check.ts: expand detection comment to list all five forms including
`WebSocket?.(...)` and `WebSocket?.<T>(...)` (was missing optional-call forms)
- error-codes.ts: rephrase SYN008 rule text to avoid implying `uses { net }`
is impossible to declare — say "the capability model cannot see it" instead of
"no uses { net } reflects it"
- explanations.ts: rephrase line 822 from "`uses { net }` is absent from the
fn header" to "the capability model cannot require `uses { net }` for it"
- explanations.ts: expand detection description to enumerate all five detected
surface forms including `WebSocket?.<T>(url)` (optional-call-with-type-args)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment on lines
+830
to
+831
| "This is the same bypass class as bare `fetch(...)` calls and `console.*` (SYN003): real network " + | ||
| "effects that sidestep the declared capability surface.\n\n" + |
Comment on lines
+515
to
+517
| message: | ||
| `fn '${decl.name}' calls or constructs a WebSocket — bypasses the net capability model; ` + | ||
| `wrap in unsafe "wraps WebSocket directly" { new WebSocket(url) } or write a $require("net")-checked wrapper`, |
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
WebSocketvianew WebSocket(url),WebSocket(url), or TypeScript instantiation forms likenew WebSocket<T>(url)at?bs 0.7+WebSocketopens a persistent bidirectional connection at runtime but is invisible to CAP001 (which only checkshttp.*member calls). A fn that constructs a WebSocket has an undeclarednetdependency: nouses { net }in the header, no signal to callers, no capability manifest entryfetch(SYN007), there is no stdlib WebSocket wrapper — the idiomatic fix isunsafe "reason" { new WebSocket(url) }rather than replacing with anhttp.*callfetch(SYN007) andconsole.*(SYN003): real network effects, invisible to the declared capability surfaceunsafe "reason" { }blocks andunsafe "reason" fnbodiesChanges
packages/compiler/src/error-codes.tspackages/compiler/src/passes/syn-check.tsWebSocketnot preceded by./?., followed by(,?.(, or<T>((with correct>>/>>>depth scan); unsafe suppressionpackages/compiler/tests/syn008-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 SYN008 tests pass, 1160 total tests passnew WebSocket(url),new WebSocket(url, protocols)WebSocket(url)(withoutnew)new WebSocket<MessageEvent>(url)new WebSocket<EventSource<MessageEvent>>(url)WebSocket?.(url)optional call?bs 0.7unsafe {}orunsafe fnobj.WebSocket(...)(member call on a local)WebSocketreference (not called)explaintool returns long-form for SYN008🤖 Generated with Claude Code