A common client-side security scenario is finding a postMessage handler but not being able to reach the interesting code path because of origin checks.
This guide shows how to bypass those checks at runtime using Wirebrowser.
Make the handler continue past its checks so we can observe or test the logic behind them.
Before adding a hook, you need the location of the code you want to instrument.
The easiest way is to use the Sources panel in Wirebrowser:
postMessage handlerThis will generate a hook template that you can edit to inject your logic.
Hooks are defined inside Node Scripts, available in the Automation tab.
You can create them in two ways:
Once created, a hook is just part of a Node Script.
Hooks are activated by:
await WB.Node.Instrumentation.startHooks(pageId);
To activate your hooks:
This will register all hooks defined in the script.
A single script can contain multiple hooks.
You can re-run the script anytime to re-register hooks or update their behavior.
Hooks remain active until you explicitly stop them.
There are two ways to stop all active hooks:
In the top-right corner, there is a small bug icon 🐞.
Clicking the icon allows you to stop the debugger.
When the debugger is stopped, all hooks are disarmed.
You can stop all hooks by running a script:
await WB.Node.Instrumentation.stopHooks()
This will remove all active hooks across all sessions.
event parameterwindow.addEventListener("message", function (event) {
if (event.origin !== "https://target.example") return;
handleMessage(event.data);
});
WB.Node.Instrumentation.addHook({ file: "https://example.com/js/index.js", line: 16, col: 2 }, {
onEnter(ctx) {
event = {
...event,
origin: "https://attacker.example"
}
}
});
This works because you replace the local event binding in the current frame.
The handler now sees your modified origin and continues execution past the check.
allowedOriginwindow.addEventListener("message", function (event) {
const allowedOrigin = "https://target.example";
if (event.origin !== allowedOrigin) return;
handleMessage(event.data);
});
WB.Node.Instrumentation.addHook({ file: "https://example.com/js/index.js", line: 16, col: 2 }, {
onEnter(ctx) {
allowedOrigin = "https://attacker.example"
}
});
This works without touching the event object at all.
const allowedOrigins = ["https://origin1.example", "https://origin2.example"];
if (!allowedOrigins.includes(event.origin)) return;
WB.Node.Instrumentation.addHook({ file: "https://example.com/js/index.js", line: 16, col: 2 }, {
onEnter(ctx) {
allowedOrigins.push("https://attacker.example");
}
});
if (!isAllowedOrigin(event.origin)) return;
Here we can place the hook inside isAllowedOrigin and override its return value.
WB.Node.Instrumentation.addHook({ file: "https://example.com/js/index.js", line: 2, col: 2 }, {
onLeave(ctx) {
ctx.return(true);
}
});
This overrides the decision directly.
This is often the cleanest approach when the check is encapsulated in a helper.
In some cases, patching values at function entry is not enough.
For example:
function onMessage(event) {
const parsed = JSON.parse(event.data);
const origin = event.origin;
if (origin !== allowedOrigin) return;
handleMessage(parsed);
}
Here, origin is computed inside the function. While rebinding event at entry may still work, sometimes you want to patch the value exactly where it is used.
WB.Node.Instrumentation.addHook({ file: "https://example.com/js/index.js", line: 2, col: 2 }, {
at: [
{
location: "4:2", // line:col
onHit(ctx){
origin = allowedOrigin
}
}
]
});
This allows you to modify local variables or state at a precise execution point, rather than only at function boundaries.
Use at when:
event includes the fields the handler actually uses (data, source, etc.)event to control handler inputThe key idea: you don’t need to modify the browser event — you only need to control what the handler reads.