feat(compiler): SYN008 — warn on WebSocket() construction that bypasses the net capability model (?bs 0.7+)#157
Merged
Merged
Conversation
12 tasks
There was a problem hiding this comment.
Pull request overview
Adds the new compiler warning SYN008 to flag WebSocket construction/calls inside function bodies (at ?bs 0.7+) because it creates a network effect that bypasses the current static net capability surface (CAP001), and wires that diagnostic through the registry, MCP explain surface, docs, and tests.
Changes:
- Add SYN008 to the compiler’s error-code registry and token-based
syn-checkpass (withnew-gated generic scanning and unsafe suppression). - Add MCP
explainlong-form documentation for SYN008 and update code allowlists/known-code lists. - Add a dedicated test suite for SYN008 detection behavior and edge cases.
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 SYN008 to the list of explainable diagnostic codes. |
| packages/mcp/tests/server.test.ts | Adds SYN008 to the MCP server KNOWN_CODES list. |
| packages/mcp/src/explanations.ts | Adds long-form EXPLANATIONS.SYN008 entry (fix guidance + examples). |
| packages/compiler/tests/syn008-check.test.ts | New test file covering detection forms, suppression, and regressions. |
| packages/compiler/tests/error-codes.test.ts | Updates the exhaustive error-code allowlist to include SYN008. |
| packages/compiler/src/passes/syn-check.ts | Implements SYN008 token-scan detection and warning emission in the syn-check pass. |
| packages/compiler/src/error-codes.ts | Registers SYN008 metadata (rule/idiom/rewrite/example). |
| AGENTS.md | Documents SYN008 in the diagnostic table. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+558
to
+567
| // Exclude method shorthands: { WebSocket(url) { ... } } | ||
| if (callTok8.matchedAt !== undefined) { | ||
| const afterCloseIdx8 = nextSignificant(tokens, callTok8.matchedAt + 1); | ||
| const afterClose8 = tokens[afterCloseIdx8]; | ||
| if (afterClose8 && ( | ||
| (afterClose8.kind === "open" && afterClose8.text === "{") || | ||
| afterClose8.kind === "fatArrow" || | ||
| (afterClose8.kind === "punct" && afterClose8.text === ":") | ||
| )) continue; | ||
| } |
28fdfc0 to
3fe684f
Compare
| message: | ||
| `fn '${decl.name}' ${hasNew8 ? "constructs new " : "calls "}WebSocket${callSep8}() — ` + | ||
| `WebSocket opens a network connection invisible to the capability model; ` + | ||
| `wrap in unsafe "wraps WebSocket for <reason>" { ${hasNew8 ? "new " : ""}WebSocket(url) }`, |
Comment on lines
+679
to
+681
| SYN008: { | ||
| code: "SYN008", | ||
| title: "new WebSocket() / WebSocket() call bypasses the net capability model", |
…t capability model (?bs 0.7+) `WebSocket` opens a persistent bidirectional connection at runtime but is invisible to CAP001, which only checks `http.*` member calls. A fn that constructs a WebSocket has an undeclared network dependency not visible in the fn header. Key improvements over prior attempt: - Generic `<T>` scan gated on `new` — prevents `WebSocket < x > (y)` comparison expressions from false-firing as generic instantiations - Contextual warning message: "constructs new" vs "calls" based on presence of `new` - 14 tests covering all call forms, unsafe suppression, comparison false-positive, member call exclusion, method shorthand exclusion, version gate, severity Closes #153 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…) detection Copilot flagged three issues: 1. Method-shorthand exclusion used `:` without guarding against ternary consequents. `cond ? WebSocket(url) : other` (and the `new` variant) would silently suppress SYN008. Fixed by checking `prev8.kind === "question"` and `prevBeforeNew8.kind === "question"`, mirroring the SYN004 pattern. 2. Member-call test had undeclared `url` variable. Added `url: string` to the fn parameter list so the test isolates the intended exclusion. 3. Comparison regression test was incomplete: only had `WebSocket < x` without the `> (y)` suffix, so it wouldn't catch a future regression where the generic-scan path processes the full expression. Completed the input. 4. Added two ternary regression tests (bare call and `new` form). 5. Updated header comment to document TS method signature exclusion and ternary guard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…registry entry before SYN010 - Warning message for WebSocket?.(url) now suggests WebSocket?.(url) inside unsafe block (not WebSocket(url)), preserving the optional-call semantics - Move SYN008 registry entry to its correct numeric position (before SYN010) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hard-coded reason string ("wraps WebSocket directly") in the idiom is
inconsistent with the rest of the registry that uses explicit <reason>
placeholders to signal where the developer fills in the justification.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
e97885b to
d79fcbc
Compare
Comment on lines
+617
to
+623
| const hasNew8 = prev8 && prev8.kind === "ident" && prev8.text === "new"; | ||
| // For ternary guard: check if token before WebSocket (or before `new`) is `?` | ||
| const prevBeforeNew8 = hasNew8 | ||
| ? tokens[prevSignificant(tokens, prevIdx8 - 1)] | ||
| : undefined; | ||
| const isTernaryConsequent8 = (prev8 !== undefined && prev8 !== null && prev8.kind === "question") || | ||
| (prevBeforeNew8 !== undefined && prevBeforeNew8 !== null && prevBeforeNew8.kind === "question"); |
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),WebSocket?.(url), or TypeScript instantiation formsnew 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 {}declaration covers itfetch(bare global that CAP001 misses): real network effects invisible to the declared capability surfaceKey improvements over PR #149
<T>scan gated onnew: preventsWebSocket < x > (y)comparison expressions from false-firing as generic instantiationsnewprecedes, consistent with the registry idiomWebSocket?.()) does not attempt generic scanning, avoiding the duplicated logic Copilot flaggedWebSocket < x > (y)comparison regressionChanges
packages/compiler/src/error-codes.tspackages/compiler/src/passes/syn-check.tsWebSocketcase in dispatch loop; generic scan gated onnew; 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 build && pnpm test— 1251 tests pass (43 test files)new WebSocket(url)WebSocket(url)(withoutnew)new WebSocket<MessageEvent>(url)new WebSocket<EventSource<MessageEvent>>(url)WebSocket?.(url)?bs 0.7unsafe {}blocks orunsafe fnbodiesobj.WebSocket(...)(member call on a local)WebSocketreference (not called)WebSocketWebSocket < x > (y)comparison expression (regression test)Supersedes PR #149 (original implementation had false-positive risk on comparison expressions and duplicate generic-scan branches).
🤖 Generated with Claude Code