Skip to content

feat(compiler): SYN008 — warn on WebSocket() construction that bypasses the net capability model (?bs 0.7+)#157

Merged
marcelofarias merged 4 commits into
mainfrom
botkowski/syn008-v2
Jun 13, 2026
Merged

feat(compiler): SYN008 — warn on WebSocket() construction that bypasses the net capability model (?bs 0.7+)#157
marcelofarias merged 4 commits into
mainfrom
botkowski/syn008-v2

Conversation

@marcelofarias

Copy link
Copy Markdown
Owner

Summary

  • Adds SYN008: warns when a fn body constructs or calls WebSocket via new WebSocket(url), WebSocket(url), WebSocket?.(url), or TypeScript instantiation forms new WebSocket<T>(url) at ?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 net dependency — no uses {} declaration covers it
  • Same bypass class as fetch (bare global that CAP001 misses): real network effects invisible to the declared capability surface

Key improvements over PR #149

  • Generic <T> scan gated on new: prevents WebSocket < x > (y) comparison expressions from false-firing as generic instantiations
  • Contextual warning message: says "constructs new" vs "calls" based on whether new precedes, consistent with the registry idiom
  • No duplicate generic-scan branches: optional-call path (WebSocket?.()) does not attempt generic scanning, avoiding the duplicated logic Copilot flagged
  • Full test coverage: 14 tests including the WebSocket < x > (y) comparison regression

Changes

File Change
packages/compiler/src/error-codes.ts Add SYN008 entry (rule, idiom, rewrite, example)
packages/compiler/src/passes/syn-check.ts Add SYN008 detection: WebSocket case in dispatch loop; generic scan gated on new; unsafe suppression
packages/compiler/tests/syn008-check.test.ts 14 tests: call forms, unsafe suppression, comparison false-positive exclusion, member call exclusion, method shorthand exclusion, version gate, severity, multi-call count
packages/compiler/tests/error-codes.test.ts Add SYN008 to exhaustive allowlist
packages/mcp/src/explanations.ts Add SYN008 long-form explanation
packages/mcp/tests/server.test.ts Add SYN008 to KNOWN_CODES list
AGENTS.md Add SYN008 row to diagnostic table
README.md Add SYN008 to explain tool code list

Test plan

  • pnpm -r build && pnpm test — 1251 tests pass (43 test files)
  • SYN008 fires on new WebSocket(url)
  • SYN008 fires on bare WebSocket(url) (without new)
  • SYN008 fires on TypeScript instantiation form new WebSocket<MessageEvent>(url)
  • SYN008 fires on nested generic form new WebSocket<EventSource<MessageEvent>>(url)
  • SYN008 fires on optional-call form WebSocket?.(url)
  • SYN008 does NOT fire below ?bs 0.7
  • SYN008 does NOT fire inside unsafe {} blocks or unsafe fn bodies
  • SYN008 does NOT fire on obj.WebSocket(...) (member call on a local)
  • SYN008 does NOT fire on bare WebSocket reference (not called)
  • SYN008 does NOT fire on object method shorthands named WebSocket
  • SYN008 does NOT false-fire on WebSocket < 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

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

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-check pass (with new-gated generic scanning and unsafe suppression).
  • Add MCP explain long-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;
}
Comment thread packages/compiler/tests/syn008-check.test.ts
Comment thread packages/compiler/tests/syn008-check.test.ts Outdated

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.

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.

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",

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/error-codes.ts

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.

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.

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.

Marcelo Farias and others added 4 commits June 13, 2026 15:31
…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>
@marcelofarias marcelofarias merged commit 90c8c7a into main Jun 13, 2026
3 checks passed

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 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");
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