* wip: sendRequest bridge fixes for QuickJS sandbox — known unresolved crash
Not working yet. insomnia.sendRequest() consistently crashes under the
real Electron/Worker/QuickJS environment with:
Aborted(Assertion failed: list_empty(&rt->gc_obj_list), at:
../../vendor/quickjs/quickjs.c,2036,JS_FreeRuntime)
on the FIRST real network round trip (via the
insomnia-templating-worker-database:// fetch bridge to
network.sendRequestWithoutSideEffects). This never reproduces in vitest
(mocked fetch, or a real Node http server) — only under real Electron
fetch() timing inside the dedicated Worker.
This commit includes three targeted fixes attempted so far, none of
which resolved it on their own or together:
1. insomnia.sendRequest('url string') omitted `headers` entirely; the
host handler doesn't default it, crashing
createConfiguredCurlInstance's `headers.find(...)` user-agent lookup.
This one IS a real, confirmed bug (unit-tested regression added) —
just not sufficient by itself to fix the crash above.
2. Deferred sendRequestViaFetch by one Promise.resolve().then() tick so
it doesn't run nested inside the QuickJS C-to-JS callback frame
(matching host-bridge.ts's installHostBridge pattern for the
template-tag sandbox).
3. executePendingJobs() returns a DisposableResult that must itself be
disposed — discarding it leaked a handle. Fixed in both
installSendRequestBridge and driveTaskToCompletion.
4. Added a disposedRef guard so a late-arriving fetch response after
vm.dispose() is a safe no-op instead of touching a dead context.
Also includes the smoke test (quickjs-script-sandbox.test.ts) that
reproduces the crash reliably — the second test in that file (worker
UI-responsiveness) passes; the sendRequest round-trip test does not.
Branched off PR 2 (37d19c1da8) per the rollout plan —
https://gist.github.com/jackkav/3ebf8768bf84be024a3a138874919354 — so
this can be picked up independently without blocking PR 0/PR 1 landing.
Next step for whoever picks this up: bisect further with instrumented
console.error tracing of vm.alive at each step (deferred here, not
committed) to find exactly which call touches vm after disposal — my
three fixes were reasonable hypotheses but didn't close it.
* test: add __sendRequest to the globalThis allowlist after rebase
The merged globalThis-leak test's ALLOWED_EXTRA_GLOBALS predates this
branch's sendRequest bridge landing back on top of develop's rebase target;
without this the merged test fails since __sendRequest is now a real
bridged global.
* fix(quickjs): free bridge promise resolvers before disposing the VM
`vm.newPromise()` allocates three JSValues — the promise plus its `resolve`
and `reject` functions — and quickjs-emscripten frees the two functions only
from inside `resolve()`/`reject()`. `installSendRequestBridge` returned the
promise handle but could skip both (the `disposedRef` guard did exactly that),
so a `sendRequest()` still in flight at teardown left two live function objects
in the runtime and `JS_FreeRuntime` aborted on
`assert(list_empty(&rt->gc_obj_list))` — a native Emscripten abort that takes
down the Worker and the WASM module shared with the template-tag sandbox.
Track every unsettled deferred and dispose it before `vm.dispose()`, which is
the cleanup the library documents for this case. Guard the late-response path
on `vm.alive`/`deferred.alive` instead of a hand-rolled flag.
Also plumb the templating-db auth token from `run-script-quickjs.ts` through
the worker into the engine — it was accepted but never passed, so every bridge
fetch was rejected 401 by the protocol's auth gate.
The crash reproduces in vitest after all: the mocked fetch in the existing
tests settles in the same microtask checkpoint as the call, so the deferred was
always settled before teardown. Settling it on a later macrotask reproduces the
exact production abort, so the smoke test no longer needs `test.fail()`.
* fix(quickjs): return the response value from sendRequestWithoutSideEffects
The handler returned `new Response(JSON.stringify(result))` while every other
handler in `pluginToMainAPI` returns a plain value and lets the caller wrap it.
`resolveDbByKey` does `JSON.stringify(await handler(body))`, so a `Response`
stringified to `"{}"` — the sendRequest bridge received an empty object, and
`response.code` / `response.body` were undefined. The template-tag sandbox's
`network.sendRequestWithoutSideEffects` path was broken the same way.
Also record a second, unrelated `gc_obj_list` abort that the teardown fix does
not address and that predates this branch: allocation-heavy work performed
inside `executePendingJobs()` (anything after the script's first `await`) leaves
live GC objects and aborts at `JS_FreeRuntime`, from ~100k objects up. Reduced
to a reproducer with no bridge, no host functions, no interrupt handler, no
memory limit and no `resolvePromise`; unaffected by every host-side mitigation
tried, and present on both quickjs-emscripten sync variants. Tracked as a
`.fails()` test so it flips red when an engine bump fixes it.
The sendRequest bridge is what makes that latent engine bug routine — it is the
first thing to put multi-megabyte host data into the VM.
Corrects the "later run proves the module survived" comment: a fresh context on
the same WASM module succeeds even after an abort, so that assertion never
detected anything.
* fix(quickjs): address Copilot review feedback on sendRequest bridge
- Normalize request.header (singular, per RequestOptions) as well as
request.headers, and accept {key, value} header entries alongside
{name, value}.
- Coerce request.url to a string, since RequestOptions allows a
Url-like object there and the host handler needs a plain string.
- sendRequestViaFetch no longer unconditionally JSON.parses the
response body — a non-JSON body (empty response, HTML error page,
etc.) now surfaces a clear "non-JSON response"/status-based error
instead of an opaque SyntaxError masking the real failure.
- The callback-style rejection handler no longer assumes err is an
Error instance (guards on err && err.message).
The authToken concern from the same review round was already fixed by
5ad0278188 on this branch (plumbed from run-script-quickjs.ts through
the worker into the engine) before this commit.
Added unit test coverage for all four fixes.
* fix(quickjs): survive the WASM teardown abort instead of losing the run
Allocating ~100k+ objects after a script's first `await` — which a multi-MB
`insomnia.sendRequest()` response body does on its own — leaves QuickJS's
runtime un-freeable, and `JS_FreeRuntime` aborts the WASM module. The script
has already finished by then and everything we return is plain host-side JS, so
the abort was destroying a run whose result was intact.
Catch it at teardown, warn into the script's logs, and report it through a new
`onEngineFault` callback. Catching inside `finally` means a genuine script error
or timeout still wins. The worker forwards the fault as `engineFaulted` and the
client retires that worker, so no later script runs on a module that aborted.
Retirement is drain-aware: `self.onmessage` doesn't serialize, so a second
script can be mid-execution. The worker leaves rotation immediately but is only
terminated once its in-flight runs settle, and the crash handler now rejects
only its own worker's runs rather than every pending call.
Verified before implementing: the abort is catchable, host-collected data
survives it intact, and the module is still *correct* afterwards — arithmetic,
JSON, string and global operations all check out across repeated runs. Retiring
the worker anyway is defensive, since that only samples the behaviour we
happened to test.
This is containment, not a fix. The underlying defect is in quickjs-emscripten's
WASM build, not in QuickJS: the identical vendored engine source built natively
is clean well past the failing size at every optimization level, with and
without -fwrapv, with CONFIG_STACK_CHECK, and with the same raw-context
intrinsic subset the shim uses — while both WASM engines abort at both
optimization levels. Tracked upstream as
https://github.com/justjake/quickjs-emscripten/issues/269.
* fix(quickjs): close three sendRequest bridge findings (#10404)
* fix(quickjs): reject caller-controlled caCertficatePath/authentication in sendRequestWithoutSideEffects
* fix(quickjs): bound the script sandbox's runtime stack size
Unbounded guest recursion overflowed the host WASM stack before QuickJS's own
depth check could fire, masking the real error behind a fatal gc_obj_list
assertion at vm.dispose(). setMaxStackSize(256KB) lets QuickJS's own check win.
* fix(quickjs): deny multipart/file-upload bodies in the sendRequest bridge
network.sendRequestWithoutSideEffects's cacert/authentication fields are already
pinned regardless of caller input, but its `body` was forwarded unvalidated. A
multipart/form-data body's file parts (body.params[].fileName) reach
buildMultipart, which reads each part's fileName off disk with no ownership/
allowlist check -- unlike every other local-file read reachable from a request
definition. A script reaches this by calling the raw __sendRequest bridge global
directly, bypassing insomnia.sendRequest()'s wrapper, which only ever builds a
text/plain body.
Scoped the fix to the QuickJS bridge itself (assertSupportedSendRequestBody,
run before the bridge's fetch() is dispatched) rather than the shared handler,
since that handler is also used by the legacy hidden-window script sandbox and
the template-tag sandbox, each with its own already-privileged execution model
that should keep working as-is.
Two regression tests: one drives the real QuickJS bridge end-to-end and confirms
the request is now denied before it ever reaches curlRequest; the other drives
the legacy (non-QuickJS) sendRequestWithoutSideEffects implementation directly
and confirms it's unaffected -- the same multipart body still goes through.
---------
Co-authored-by: kwburns-kong <kyle.burns@konghq.com>
Insomnia API Client
Insomnia is an open-source, cross-platform API client for GraphQL, REST, WebSockets, Server-Sent Events (SSE), gRPC and any other HTTP compatible protocol.
With Insomnia you can:
- Debug APIs using the most popular protocols and formats.
- Design APIs using the native OpenAPI editor and visual preview.
- Test APIs using native test suites and collection runner.
- Mock APIs using a cloud or self-hosted mocking server.
- Build CI/CD pipelines using the native Insomnia CLI for linting and testing.
- Collaborate with others using the many collaboration features.
- And more including the ability to use 3rd party plugins.
The following storage options are supported for your Insomnia projects, collections, design specs and all other resources:
- Local Vault: for 100% local storage of collections, design specs and every other resource.
- Git Sync: for Git storage using any 3rd party Git repository, without going through the cloud.
- Cloud Sync: for cloud collaboration, optionally end-to-end encrypted (E2EE) in the cloud.
Get started for free
Insomnia is available for Mac, Windows, and Linux and can be downloaded from the website:
Account & Subscriptions
You can use Insomnia without an account with the local Scratch Pad, or you can create an account for free to get access to the full capabilities of the product.
Even with an account, Insomnia only stores your projects and files accordingly to the storage backend that you have selected, which can be Local Vault, Cloud Sync, Git Sync or any combination of them. As such - for example - you have the freedom to choose to store sensitive projects 100% locally or in a Git repository, while still being able to collaborate on others in the cloud. It's the best of both worlds.
For added security, Insomnia also offers a Private Environments feature, where your environments configuration is always stored locally and never in the cloud, independently from the storage option that you have chosen for your project.
Premium features and support
Insomnia has a very generous free plan that will be satisfactory for most users, but if you need to get access to premium capabilities like unlimited collaboration, the Git Sync feature, the ability to create organizations for your projects, using a 3rd party IDP for logins (SAML, OIDC) and many other features, then you can explore the other subscription plans.
You can compare all subscription plans and get started for free.
Why does Insomnia require an account?
Insomnia does not require an account if you decide to use the local Scratch Pad, but to access most capabilities of the product we require an account. Your account data is securely stored in compliance with ISO27001, SOC 2 Type II, ISO27018, Gold CSA STAR regulations and in accordance with our terms of service and privacy policy.
We require an account to sustainably build and improve the product, and to make sure we can continue to offer the many core capabilities in a free and open-source distribution. While open source software is free to use, it is unfortunately not free to build, and our ability to continue working on Insomnia is dependent on our ability to convert a subset of free users (that need premium features) to become paying customers of our product.
If you are a user that cannot share API data like collections and design specifications to the cloud, this is still possible by selecting "Local Vault" as the storage of your Insomnia projects: having an Insomnia account is not tied to how you wish to store your sensitive API data (which can be stored 100% locally via Local Vault, on a 3rd party Git repository without any cloud storage via Git Sync, or in the cloud for ease of collaboration via Cloud Sync).
Bugs and Feature Requests
Have a bug or a feature request? First, read the issue guidelines and search for existing and closed issues. If your problem or idea is not addressed yet, please open a new issue.
For more generic product questions and feedback, join the Slack Team.
Contributing
Please read through our contributing guidelines and code of conduct. Included are directions for opening issues, coding standards, and notes on development.
Documentation
Check out our official Insomnia Documentation.
Develop Insomnia
Development on Insomnia can be done on Mac, Windows, or Linux as long as you have Node.js and Git. See the .nvmrc file located in the project for the correct Node version.
Initial Dev Setup
This repository is structured as a monorepo and contains many Node.JS packages. Each package has its own set of commands, but the most common commands are available from the root package.json and can be accessed using the npm run … command. Here are the only three commands you should need to start developing on the app.
# Install and Link Dependencies
npm i
# Run Lint
npm run lint
# Run type checking
npm run type-check
# Run Tests
npm test
# Start App with Live Reload
npm run dev
# Start App with both renderer process live reload and main process auto restart
npm run dev:autoRestart
Linux
If you are on Linux, you may need to install the following supporting packages:
Ubuntu/Debian
# Update library
sudo apt-get update
# Install font configuration library & support
sudo apt-get install libfontconfig-dev
Fedora
# Install libcurl for node-libcurl
sudo dnf install libcurl-devel
Also on Linux, if Electron is failing during the install process, run the following
# Clear Electron install conflicts
rm -rf ~/.cache/electron
Windows
If you are on Windows and have problems, you may need to install Windows Build Tools
Editor Requirements
You can use any editor you'd like, but make sure to have support/plugins for the following tools:
- ESLint - For catching syntax problems and common errors
- JSX Syntax - For React components
Develop Inso CLI
npm i- Start the compiler in watch mode:
npm run inso-start - Run:
./packages/insomnia-inso/bin/inso -v
Plugins
Search for, discover, and install plugins from the Insomnia Plugin Hub!
Community Projects
- Insomnia Documenter - Generate beautiful API documentation pages using the documenter plugin or your Insomnia export file.
- GitHub API Spec Importer - A complete set of GitHub REST API route specifications that can be imported straight into Insomnia.
- Swaggymnia - Generate Swagger documentation for your existing API in Insomnia.
