Last active
June 22, 2022 01:34
-
-
Save dfkaye/d6b52877fc2316d705eec3965e27e7d9 to your computer and use it in GitHub Desktop.
protect against hostile worker and channel messages with addressing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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