Socket
Socket
Sign inDemoInstall

workerboxjs

Package Overview
Dependencies
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

workerboxjs - npm Package Compare versions

Comparing version 3.4.0 to 4.0.0

196

lib/index.js

@@ -1,6 +0,35 @@

let workerBoxCount = 0;
export function createWorkerBox (scriptUrl, options) {
import createCallbackStore from './createCallbackStore.js';
import scopeToString from './scopeToString.js';
import stringToArgs from './stringToArgs.js';
import argsToString from './argsToString.js';
const instances = {
count: 0
};
function createWorkerboxInstance (url, onMessage) {
instances.count = instances.count + 1;
const channel = new MessageChannel();
const iframe = document.createElement('iframe');
iframe.sandbox = 'allow-scripts';
iframe.id = `workerBox-${instances.count}`;
iframe.style =
'position: fixed; height: 0; width: 0; opacity: 0; top: -100px;';
iframe.src = url;
document.body.appendChild(iframe);
channel.port1.onmessage = onMessage;
return new Promise(resolve => {
iframe.addEventListener('load', () => {
iframe.contentWindow.postMessage('OK', '*', [channel.port2]);
resolve({
postMessage: message => channel.port1.postMessage(message),
destroy: () => iframe.remove()
});
});
});
}
export async function createWorkerBox (scriptUrl, options) {
options = {
appendVersion: true,
randomiseSubdomain: false,
...options

@@ -14,3 +43,3 @@ };

if (options.appendVersion) {
scriptUrl = scriptUrl + '/v3.4.0/';
scriptUrl = scriptUrl + '/v4.0.0/';
}

@@ -30,139 +59,46 @@

if (options.randomiseSubdomain) {
const subdomain = [...Array(30)]
.map(() => Math.random().toString(36)[2])
.join('');
scriptUrl.host = `${subdomain}.${scriptUrl.host}`;
}
const callbacks = createCallbackStore();
workerBoxCount = workerBoxCount + 1;
return new Promise((resolve) => {
const iframe = document.createElement('iframe');
iframe.sandbox = 'allow-scripts allow-same-origin';
iframe.id = `workerBox${workerBoxCount}`;
iframe.style =
'position: fixed; height: 0; width: 0; opacity: 0; top: -100px;';
iframe.src = scriptUrl.href;
document.body.appendChild(iframe);
const run = (id, args) =>
new Promise(resolve => {
instance.postMessage(['callback', { id, args, resolve: callbacks.add(resolve) }]);
});
const worker = document.getElementById(
`workerBox${workerBoxCount}`
).contentWindow;
const instance = await createWorkerboxInstance(scriptUrl.href, async message => {
const [action, { id, args, resolve }] = message.data;
const promises = {};
const callbacks = {};
let currentCallbackId = 0;
const parsedArgs = stringToArgs(args, callbacks.add, run);
function prepareArgs (args) {
const newArgs = [];
for (const arg of args) {
if (typeof arg === 'function') {
currentCallbackId = currentCallbackId + 1;
callbacks[currentCallbackId] = arg;
newArgs.push(['callback', currentCallbackId]);
} else if (typeof arg === 'object') {
newArgs.push(['object', prepareArgs(arg)]);
} else {
newArgs.push(['literal', arg]);
}
}
return newArgs;
if (action === 'error') {
callbacks.get(id)?.(new Error(parsedArgs[0]));
return;
}
window.addEventListener('message', async (event) => {
if (event.data.ready) {
let messageNumber = 0;
const run = (code, scope) => {
messageNumber = messageNumber + 1;
const currentMessageNumber = messageNumber;
function prepareScope (scope) {
const newScope = {};
for (const key in scope) {
if (typeof scope[key] === 'function') {
newScope[key] = ['function', key];
} else if (typeof scope[key] === 'object') {
newScope[key] = ['object', prepareScope(scope[key])];
} else {
newScope[key] = ['literal', scope[key]];
}
}
return newScope;
}
if (action === 'return') {
callbacks.get(id)?.(parsedArgs[0]);
return;
}
function parseArgs (args) {
const newArgs = [];
for (const arg of args) {
if (arg[0] === 'callback') {
newArgs.push((...rawArgs) => {
return new Promise((resolve) => {
const args = prepareArgs([...rawArgs, resolve]);
worker.postMessage(
{
messageNumber: currentMessageNumber,
callbackKey: arg[1],
callbackArgs: args
},
'*'
);
});
});
} else if (arg[0] === 'object') {
newArgs.push(parseArgs(arg[1]));
} else {
newArgs.push(arg[1]);
}
}
return newArgs;
}
const fn = callbacks.get(id);
if (!fn) {
return;
}
const callFunction = async (key, args) => {
const [resolve] = parseArgs(args).slice(-1);
const result = await scope[key].call(
null,
...parseArgs(args).slice(0, -1)
);
resolve(result);
};
const result = await fn(...parsedArgs);
instance.postMessage(['callback', { id: resolve, args: argsToString([result]) }]);
});
worker.postMessage(
{
messageNumber: currentMessageNumber,
code,
scope: prepareScope(scope)
},
'*'
);
return new Promise((resolve, reject) => {
promises[currentMessageNumber] = { resolve, reject, callFunction };
});
};
run.destroy = () => {
iframe.remove();
};
resolve(run);
return;
}
const { messageNumber, error, result } = event.data;
if (event.data.functionKey) {
promises[messageNumber].callFunction(
event.data.functionKey,
event.data.functionArgs
);
return;
}
if (error) {
promises[messageNumber]?.reject(error);
return;
}
promises[messageNumber]?.resolve(result);
});
});
return {
run: async (code, originalScope) => {
return new Promise((resolve, reject) => {
const id = callbacks.add(resolve);
const errorId = callbacks.add(reject);
const scope = scopeToString(originalScope, callbacks.add, run);
instance.postMessage(['execute', { id, errorId, code, scope }]);
});
},
destroy: () => instance.destroy()
};
}
export default createWorkerBox;
{
"name": "workerboxjs",
"version": "3.4.0",
"version": "4.0.0",
"type": "module",

@@ -11,3 +11,3 @@ "description": "A secure sandbox to execute untrusted user JavaScript, in a web browser, without any risk to your own domain/site/page.",

"build": "node build.js",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "node test"
},

@@ -38,11 +38,11 @@ "files": [

"devDependencies": {
"@markwylde/ftp-deploy": "^1.2.0",
"@markwylde/ftp-deploy": "^2.0.1",
"basictap": "^3.4.3",
"chokidar": "^3.5.3",
"debounce": "^1.2.1",
"esbuild": "^0.15.12",
"esbuild": "^0.15.15",
"just-tap": "^1.5.1",
"minify": "^9.1.0",
"process": "^0.11.10",
"puppeteer": "^19.2.0",
"puppeteer": "^19.2.2",
"servatron": "^2.4.2"

@@ -49,0 +49,0 @@ },

@@ -18,3 +18,3 @@ # WorkerBox

const run = await createWorkerBox('https://sandbox.workerbox.net/');
const { run, destroy } = await createWorkerBox('https://sandbox.workerbox.net/');

@@ -36,3 +36,3 @@ const scope = {

run.destroy() // Destroys the worker box, terminating any running workers
destroy() // Destroys the worker box, terminating any running workers
```

@@ -46,15 +46,1 @@

Because the only communication between the user code and the workerbox instead is done through messaging, the argument inputs and outputs must all be JSON serializable.
## Caveats
### Storage
Web workers can't use cookies or localStorage, but even if they could they would be isolated to third party domain that is running the code.
However, there are some ways to store data. For example, indexDB.
While your unsafe user code can not access the indexDB of your own site, it can use the instance on the server's site.
But remember, anyone can run untrusted user code on the workerbox site. So if your users store data on the workerbox domain, technically anyone can view that data.
Therefore, you should advise your users not to store any data using the web workers API.
Of course, you could provide an abstraction on the `scope` that would safely allow you to store data on your own domain.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc