Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
coincident
Advanced tools
An Atomics based Proxy to simplify, and synchronize, Worker related tasks
Social Media Photo by bady abbas on Unsplash
An Atomics based Proxy to simplify, and synchronize, Worker related tasks.
Following the description of all different imports to use either on the main or the worker thread.
This is the import that provides the ability to expose main thread's callbacks to the worker thread and to also await callbacks exposed via the worker code.
import coincident from 'coincident/main';
const {
// the Worker to be used (this extends the global one)
Worker,
// a boolean indicating if sabayon is being used
polyfill,
// a utility to transfer values directly via `postMessage`
// (...args: Transferable[]) => Transferable[]
transfer,
} = coincident({
// a utility to parse text, default: JSON.parse
parse: JSON.parse,
// a utility to stringify values, default: JSON.stringify
stringify: JSON.stringify,
// an optional utility to transform values (FFI / Proxy related)
transform: value => value,
});
The Worker
class returned by coincident()
has these features:
{ type: "module" }
( mostly because the worker needs to await coincident()
on bootstrap ){ serviceWorker: "../sw.js" }
to help sabayon falling back to synchronous behavior, which is mandatory to use any DOM or window
related functionalityproxy
reference where utilities, as callbacks, can be assigned or asynchronously awaited if exposed within worker's codeconst { proxy } = new Worker('./worker.js');
// can be invoked from the worker
proxy.location = () => location.href;
// exposed via worker code
await proxy.compute();
This is the import that provides the ability to expose worker thread's callbacks to the main thread and to also directly invoke callbacks exposed via the main proxied reference.
import coincident from 'coincident/worker';
const {
// the counter-part of the main worker.proxy reference
proxy,
// a boolean indicating if sabayon is being used
polyfill,
// a boolean indicating if it's possible to do synchronous operations
sync,
// a utility to transfer values directly via `postMessage`
// (...args: Transferable[]) => Transferable[]
transfer,
} = await coincident({
// a utility to parse text, default: JSON.parse
parse: JSON.parse,
// a utility to stringify values, default: JSON.stringify
stringify: JSON.stringify,
// an optional utility to transform values (FFI / Proxy related)
transform: value => value,
// an optional interrupt reference that is used via `Atomic.wait(sb)`
interrupt: { handler() {}, timeout: 42 },
});
// exposed to the main thread
proxy.compute = async () => {
// super expensive task ...
return 'result';
};
// consumed from the main thread
// synchronous if COI is enabled or
// the Service Worker was passed
console.log(proxy.location());
These exports and their coincident/dist/...
pre-optimized counter-parts allow coincident to drive, from a Worker the main thread and operate directly on it.
When the worker code expects the main window
reference, this import is needed to allow just that.
import coincident from 'coincident/window/main';
// ^^^^^^
const { Worker, polyfill, transfer } = coincident();
The signature, on the main thread, is identical.
On the worker side, this import is also identical to the non-window variant but it's returned namespace, after bootstrap, contains two extra utilities:
import coincident from 'coincident/window/worker';
// ^^^^^^
const {
proxy, polyfill, sync, transfer,
// it's a synchronous, Atomic.wait based, Proxy
// to the actual globalThis reference on the main
window,
// it's an introspection helper that returns `true`
// only when a reference points at the main thread
// (value: any) => boolean
isWindowProxy,
} = await coincident();
// direct synchronous access to the main `window`
console.log(window.location.href);
window.document.body.textContent = 'Hello World 👋';
These exports and their coincident/dist/...
pre-optimized counter-parts allow coincident to drive, from a Worker both the main thread and operate directly on the running server too.
This feature exists mostly to enable Kiosk or IoT related projects and it should not be publicly available as any malicious worker code could fully take over the server or harm the service.
This is what node or bun or others should import to instrument connected WebSockets.
import coincident from 'coincident/server';
// Bun example
serve({
port,
fetch,
// here coincident options should have
// a "truthy" bun 🐰
websocket: coincident({ bun: true })
});
// NodeJS ⬡ or any other with `ws` module as example
import { WebSocketServer } from 'ws';
const server = ...;
coincident({
// the `wss` property must be there
wss: new WebSocketServer({ server })
});
The coincident
utility here simply instruments every connected WebSocket to react on message
and close
events.
When the worker code expects both the main window
and the server
references, this import is needed to allow just that.
import coincident from 'coincident/server/main';
// ^^^^^^
const { Worker, polyfill, transfer } = coincident({
ws: 'ws://localhotst:8080/'
// ^^^^^^^^^^^^^^^^^^^^^
});
The signature, on the main thread, is identical except the WebSocket url must be provided during initialization.
On the worker side, this import is also identical to the window variant but it's returned namespace, after bootstrap, contains two extra utilities:
import coincident from 'coincident/server/worker';
// ^^^^^^
const {
proxy, polyfill, sync, transfer,
window, isWindowProxy,
// it's a synchronous, Atomic.wait based, Proxy
// to the actual globalThis reference on the server
server,
// it's an introspection helper that returns `true`
// only when a reference points at the server
// (value: any) => boolean
isServerProxy,
} = await coincident();
// direct synchronous access to the main `server`
server.console.log('Hello World 👋');
// example of module import
const os = await server.import('os');
console.log(os.platform());
Every single property retrieved via the window
reference is a whole worker ↔ main roundtrip and this is inevitable. There is no "smart caching" ability backed in the project, because everytrhing could be suddenly different at any point in time due side effects that both the worker, or the main thread, could have around previously retrieved references.
Especially when SharedArrayBuffer is polyfilled, and the serviceWorker
provided as option, an average PC would perform up to ~1000 roundtrips per second. That seems like a lot but operations can easily pile up and make the program feel unnecessary slower than it could be (if run on the main thread directly, as comparison).
When native SharedArrayBuffer is enabled though, an average PC would be able to do ~50000 (50x) roundtrips per second .... and yet that could also easily degrade with more complex logic involved.
An easy way to prevent repeated roundtrips, when we already assume a reference will not change by any mean over time, we can take over that "smart caching" explicit operation:
const { window } = await coincident();
const { document } = window;
const { head, body } = document;
// any time we need to change the content
body.textContent = 'Hello World 👋';
Please note that because those references won't likely ever change on the main thread, there are also no memory leaks hazard, and that's true with every other reference that might live forefer on the main thread.
This module allows different worlds to expose utilities that can be invoked elsewhere and there are two ways this can work:
Atomics.wait
is usable, from a worker, and it will be preferred over Atomics.waitAsync
for the simple reason that it unlocks much more than trivial async exchanges between the two worlds. In this case, if the worker is invoking a foreign exposed utility, it will be fully unresponsive until that utility returned a value and there's no possible workaround. When this happens, the module understands that the requested utility comes from a worker that is paused until such invoke returns, and if this invoke relies on a synchronous worker utility there won't be any chance to complete that request: the worker is stuck and the main can't use it until is not stuck anymore. In this case, an error is thrown with details around which worker utility was invoked while the main utility was executing, and the program won't just block itself forever. This is the most meaningful and reasonable deadlock case to throw
errors unconditionally ... but ...Atomics.wait
ability, meaning no COI headers are enabled and no serviceWorker
fallback has been used, it is possible for a main exported utility to query a worker exported utility one once executed, assuming there is no recursion in doing so (i.e. the worker calls main()
that internally calls worker()
which in turns calls main()
again). These cases are rather infinite loops/recursions than deadlocks but if you are sure your main utility is invoking something in the worker that won't cause such infinite recursion, no deadlock error would be shown, as that would not be the case, strictly speaking, but also recursions won't be tracked so ... be careful with your logic!As rule of thumb, do not ever invoke other world utilities while one of your exported utility is executing, so that code will be guaranteed to work in both Atomics.wait
and Atomics.waitAsync
scenarios without ever worrying about future deadlock, once all headers are available or the serviceWorker
helper will be used.
It is, however, always possible to execute foreign utilities on the next tick, micro-task, timeout, idle state or listener, so that if a main exposed utility needs to invoke a worker utility right after, there are ways to do that.
Unfortunately not enabled by default on the Web, the SharedArrayBuffer primitive requires either special headers permissions to be trusted or a polyfill that can always enable the async abilities of the Atomics specifications and eventually grant the sync abilities too, as long as a ServiceWorker able to handle those requests is installed.
This primitive is needed to enable notifications about data cross realms, notifications that are expected to be sync, in the best case scenario, or async as least possible fallback.
This is the preferred way to use this module or any module depending on it, meaning all headers to enable SAB are in place. To do so:
npx mini-coi ./
is all you need to enable these headers locally, but the utility doesn't do much more than serving files with those headers enabled<script src="./mini-coi.js"></script>
on top of your <head>
node in your HTML templates to use automatically a ServiceWorker that force-enable those headers for any request made frm any client. This woks on GitHub pages too, and every other static files handler for local projectsnpx sabayon ./public/sw.js
to Worker constructors, so that such SW can be used to polyfill the sync case{ serviceWorker: '../sw.js' }
extra option, as long as it imports utilities from sabayon, as explained in its ServiceWorker related detailsThe latter 2 points will inevitably fallback to a polyfilled version of the native possible performance but it should be good enough to enable your logic around workers invoking, or reaching, synchronous main thread related tasks.
This module by default does fallback to a SAB polyfill, meaning async notification of any buffer are still granted to be executed or succeed, thanks to sabayon underlying module.
This scenario is ideal when:
As long as these enabled use cases are clear, here the caveats:
If all of this is clear, it's possible to use coincident module as bridge between worker exported features / utilities consumed asynchronously by the main thread any time it needs to.
This still unlocks tons of use cases out there, but it's definitively a constrained and limited experience.
FAQs
An Atomics based Proxy to simplify, and synchronize, Worker related tasks
The npm package coincident receives a total of 8,127 weekly downloads. As such, coincident popularity was classified as popular.
We found that coincident demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.