Skip to content

feat(compiler): SYN027 — warn on window.postMessage/postMessage (net bypass, like SYN007 fetch) #180

@marcelofarias

Description

@marcelofarias

Problem

SYN007 warns on `fetch()` because it makes HTTP requests while bypassing `uses { net }`. The same class of bypass applies to `postMessage`:

  • `window.postMessage(data, targetOrigin)` — sends data to another window/iframe/worker at a different origin. This is a cross-origin network-equivalent operation.
  • bare `postMessage(data, origin)` — same call on the implicit `window` receiver.

A fn that calls `postMessage` has an undeclared cross-origin communication dependency. The call can exfiltrate data or trigger remote behavior in other browsing contexts — no `uses { net }` declaration, no `writes {}` label, nothing visible in the fn header.

Why postMessage is a net bypass

`fetch` bypasses `uses { net }` because it initiates an HTTP request outside the stdlib namespace. `postMessage` bypasses the same boundary by sending structured data to another origin:

```bs
?bs 0.7
fn notifyParent(userId: string) -> void {
// SYN027: postMessage sends data cross-origin, invisible to the capability model
postMessage({ type: "user-ready", id: userId }, "https://parent.example.com")
}
```

The fn header has no `uses { net }` declaration. The compiler cannot see the outbound cross-origin communication.

Additional attack surface

In the bot/agent context, `postMessage` is a known prompt-injection vector: injected content in a page can call `postMessage` to exfiltrate data to an attacker-controlled origin. A SYN check makes this surface visible in code review.

Proposed diagnostic

Code Form Trigger
SYN027 `postMessage(data, origin)` Bare call not preceded by `./\?.` at `?bs 0.7+`

Detection mirrors SYN007 (fetch):

  • Fires on `postMessage(data, origin)`, `postMessage?.(data, origin)`
  • Excluded: member calls (`worker.postMessage`, `iframe.contentWindow.postMessage`) — these are operations on an already-declared handle, analogous to `http.post`
  • Excluded: `fn`/`function`/`function*` declarations named `postMessage`
  • Suppressed inside `unsafe {}` and `unsafe "reason" fn` bodies

Fix

Pass the messaging function as an explicit parameter so the dependency is declared:

```bs
fn notifyParent(post: (msg: object, origin: string) -> void, userId: string) -> void {
post({ type: "user-ready", id: userId }, "https://parent.example.com")
}
```

Or wrap in unsafe when direct cross-origin messaging is required:
```bs
fn notifyParent(userId: string) -> void {
unsafe "posts user-ready event to parent frame" {
postMessage({ type: "user-ready", id: userId }, "https://parent.example.com")
}
}
```

Relation to existing checks

  • SYN007 (fetch): same capability bypass class, HTTP
  • SYN014 (BroadcastChannel): same bypass class, same-origin broadcast
  • SYN008 (WebSocket): persistent bidirectional connection

`postMessage` is the missing cross-origin messaging entry in this family.

Metadata

Metadata

Assignees

No one assigned

    Labels

    proposalRFC or design proposal

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions