Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfkaye/d6b52877fc2316d705eec3965e27e7d9 to your computer and use it in GitHub Desktop.
Save dfkaye/d6b52877fc2316d705eec3965e27e7d9 to your computer and use it in GitHub Desktop.
protect against hostile worker and channel messages with addressing
// 8 June 2022
// protect against hostile worker and channel messages with addressing
// 18-21 June 2022
// refactored onmessage to handle: {
// sender,
// token,
// action,
// value
// }]
// ...continues from "communicate with workers through message channels" gist
// https://gist.github.com/dfkaye/8e10996623246d4b178f1d4a7939673d
// Note: The following example won't run in browser pages with Content Security
// Policy that contains a `worker-src` directive that does not specify `blob:`
// as a value, such as the `console` in a GitHub gist (where I do lot of
// experimenting). If there is *no* `worker-src` directive, then this should
// *just work*...
// How can we prevent hostile messages from infecting a worker, either on a
// message channel port or on its (possibly several) message handlers?
// In short, by treating messages with adddressing (e.g., timestamps, UUIDs).
// Create all messages with a structure containing a sender, a recipient,
// an action, and a value.
// The example demonstrates a worker that initializes itself with a token URL,
// treats the first incoming request as an initialization command, and expects a
// sender name and an action value "init". If either is missing or incorrect, it
// throws an error.
// If all goes well, the worker replaces its message handler, and returns the
// sender, and the token for re-use by the client. This is, in effect, the
// *become* concern of the Actor model. Read more about Actors in JavaScript at
// Dale Schumacher's site, http://www.dalnefre.com/wp/tag/actor/.
var sender = "test-sender-thread";
var script = `
self.senders = new Set([ "${sender}" ]);
self.token = URL.createObjectURL(new Blob()).split("/").at(-1);
self.response = "Worker says:";
self.addEventListener("message", init);
// Handle initial message as a request.
function init(request) {
if (request.data.action !== "init") {
throw new Error("failed to initialize worker");
}
if (!self.senders.has(request.data.sender)) {
throw new Error("failed to initialize worker");
}
self.removeEventListener("message", init);
self.addEventListener("message", handleMessage);
// Send response.
postMessage({
sender: request.data.sender,
token: self.token
});
}
// Handle subsequent message as a request.
function handleMessage(request) {
var { ok, error } = credentials(request.data, self);
var message = self.response + (ok ? "OK" : "Error");
// Send response.
postMessage({ok, error, message, data: request.data})
}
// Verify sender and token.
function credentials(data, self) {
var sender = data.sender;
var token = data.token;
var ok = (
sender && self.senders.has(sender) && token && self.token === token
);
return ok
? { ok }
: { error: "bad credentials!" };
}
`;
var blob = new Blob([script], {type: 'text/javascript'});
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
var token;
var init = function(response) {
worker.removeEventListener("message", init);
worker.addEventListener("message", handle);
/* defensive check that sender returned is original sender */
if (response.data.sender !== sender) {
throw new Error("bad sender response")
}
token = response.data.token;
}
var handle = function(response) {
var { ok, error, message, data } = response.data;
var method = ok ? "warn" : "error";
console[method]('Received from worker: ', { ok, error, message, data });
}
// Handle messages received from the worker.
worker.addEventListener("message", init);
// Send invalid command to initialize worker with sender.
console.log("**bad init request**");
worker.postMessage({ });
console.log("**missing sender request**");
worker.postMessage({ action: "init" });
// Send valid command to initialize worker with sender.
console.log("**good init**");
worker.postMessage({
sender,
action: 'init'
});
/** send messages after timeout **/
setTimeout(function () {
console.log("**eventual messages**")
worker.postMessage("**zoinks**");
worker.postMessage({ "value": "ok", sender, token });
worker.postMessage({ action: 'update', value: 'ok', sender, token });
}, 200);
/** send messages eagerly **/
console.log("**eager messages**");
worker.postMessage("**eager**");
worker.postMessage({ "value": "eager", sender, token });
worker.postMessage({ action: 'update', value: 'eager', sender, token });
/*
**bad init request**
**missing sender request**
**good init**
**eager messages**
Error: failed to initialize worker
Error: failed to initialize worker
Received from worker: {ok: undefined, error: 'bad credentials!', message: "Worker says:ERROR", data: '**eager**'}
Received from worker: {ok: undefined, error: 'bad credentials!', message: "Worker says:ERROR", data: {
sender: "test-sender-thread",
token: "08f5d859-02e6-455b-b3ee-b87f0e28a4c5",
value: "ok"
}}
Received from worker: {ok: undefined, error: 'bad credentials!', message: "Worker says:ERROR", data: {
action: "update",
sender: "test-sender-thread",
token: "08f5d859-02e6-455b-b3ee-b87f0e28a4c5",
value: "ok"
}}
**eventual messages**
Received from worker: {ok: undefined, error: 'bad credentials!', message: "Worker says:OK", data: '**zoinks**'}
Received from worker: {ok: true, error: undefined, data: {
sender: "test-sender-thread",
token: "08f5d859-02e6-455b-b3ee-b87f0e28a4c5",
value: "ok"
}}
Received from worker: {ok: true, error: undefined, message: "Worker says:OK", data: {
action: "update",
sender: "test-sender-thread",
token: "08f5d859-02e6-455b-b3ee-b87f0e28a4c5",
value: "ok"
}}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment