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.
Problem
SYN007 warns on `fetch()` because it makes HTTP requests while bypassing `uses { net }`. The same class of bypass applies to `postMessage`:
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
/\?.` at `?bs 0.7+`Detection mirrors SYN007 (fetch):
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
`postMessage` is the missing cross-origin messaging entry in this family.