* feat(templating): multi-file plugins via in-sandbox module map (M4)
esbuild is dev-only, so instead of bundling plugins at load time this reads the
plugin's own source files into a module map (readPluginModuleMap, host-side)
and teaches the in-sandbox require to resolve RELATIVE specifiers from that map
(./util, ../x, ./dir -> ./dir/index.js) with a CommonJS cache. Bare specifiers
still route through the grant-gated registry.
Security (the poison guarantee): the host walker reads ONLY within the plugin
directory — node_modules and dot-dirs are skipped, symlink-escapes dropped,
file count/size bounded — so a plugin's own node_modules is never consulted and
a bare require('uuid') always binds the vetted registry copy, never a plugin's
substitute. Plugin source now travels as envelope DATA (moduleFiles) compiled by
the loader via new Function; no host-side eval of plugin code. Single-file
callers keep working via a synthesized one-entry map.
Tests: multi-file-plugins.test.ts (7: relative/nested/dir-index resolution,
hybrid bare-uuid, CJS cache, not-found, syntax-error propagation, bare-vs-map
shadow) + readPluginModuleMap tests (map shape, node_modules-excluded poison
guarantee, custom main, entry-escape). E2E: multi-file resolution + poison
(fake node_modules/uuid ignored). Demo converted to multi-file
(require('./lib/greeting')). PERMISSIONS.md documents multi-file + the
require-vetted-libs-inside-run note.
No esbuild runtime dependency added.
* fix(templating): tighten M4 module-map byte guard and relative-path escape
Address review feedback on the multi-file plugin loader:
- readPluginModuleMap counted source.length (UTF-16 code units), which
undercounts multi-byte UTF-8 payloads against MAX_PLUGIN_MODULE_BYTES, and
the custom-"main" entry fallback was added to the map without counting its
bytes at all. Count Buffer.byteLength(...'utf8') via a shared addModule
helper used by both the walk and the entry fallback.
- __normalizeKey clamped above-root '..' traversal back into the plugin root,
so require('../../util') from a shallow dir could masquerade as an in-plugin
module. Return null for above-root traversal (unresolvable), matching Node
semantics; __resolveRelative propagates it as MODULE_NOT_FOUND.
* perf: rewrite command palette search to eliminate UI freezes and stale results
Problems fixed:
- clientLoader blocked navigation on every palette open, freezing the UI during
full sequential DB scan
- no AbortController: stale searches could overwrite newer results mid-type
- no debounce: every keystroke triggered a full DB traversal immediately
- React Aria's default contains filter re-filtered server-side fuzzy results,
silently dropping valid matches and making fuzzy work redundant
- no warm baseline: palette started blank until the entire load completed
Changes:
- move fuzzyMatch/fuzzyMatchAll → insomnia-data/common-src/search.ts (shared,
testable in node)
- move search logic → insomnia-data/node-src/services/helpers/command-search.ts
- per-request AbortController cancellation via abort(requestId)
- recursion depth guard (max 20) on request group traversal
- safeParent() replaces non-null assertions, safe against orphaned documents
- add CommandSearchResult type in insomnia-data/src/command-search-types.ts
- add useCommandSearch hook: 250ms debounce + abort on filter change + warm
baseline on mount
- add defaultFilter={() => true} to ComboBox, delegating all filtering to the
service
- delete routes/commands.tsx
* fix
* fix
* fix: ensure unique blank IDs for environment key-value pairs to prevent stale state
* test: add validation for blank row behavior after deleting committed rows in environment table editor
* chore: emit junit reports per e2e tests shard
* chore: include retries in junit reporter
* chore: test some stuff
* chore: test again
* chore: revert test
* feat(templating): surface profiles + ceiling + manifest-less migration warning (P1)
Add per-surface sandbox profiles (surface-profiles.ts): a profile is the
ceiling of what a plugin on a surface may be granted, effective grant =
floor ∪ (declared ∩ ceiling).
- TEMPLATE_TAG_PROFILE excludes 'credentials' from its capability ceiling —
cloud-credential access is reserved for first-party bundle plugins; a user
template-tag plugin can't obtain it even by declaring it.
- SCRIPTING_PROFILE (broad, enumerated) defined for the later scripting-engine
reuse; unit-tested, not wired to a live surface yet.
- resolveTemplateTag{Modules,Capabilities} move here from module-registry/
host-bridge and delegate to the profile (capabilities enforce the ceiling;
modules pass declared names through so the require gate gives the precise
'not available' vs 'not permitted' message).
- Migration warning: a manifest-less plugin denied a non-baseline module gets a
one-time (per-plugin-per-session) toast via the existing show-toast channel,
naming the module + the insomnia.permissions field to add.
- Docs: PERMISSIONS.md; demo README + capability ceiling note.
Tests: surface-profiles.test.ts (effectiveGrant matrix, floor⊆ceiling, credentials
ceiling exclusion); migration-warning dedup unit test; C2 shape tests decoupled
from the profile (grant caps directly); e2e P1 test (ceiling + one-time warning).
107 unit + 4 smoke green.
* feat(templating): typed module denial (#10254)
* fix(templating): typed module-denial error instead of message regex (P1)
maybeWarnMissingManifest matched the sandbox's thrown error message
("Module 'X' not permitted by manifest") with a regex to decide whether
to show the migration toast. That couples a UI feature to English error
prose crossing the QuickJS VM boundary — any future wrapping or wording
change breaks the toast silently, with no compiler signal.
__require now throws with `code`/`moduleName` own properties
(SANDBOX_MODULE_NOT_PERMITTED vs SANDBOX_MODULE_NOT_AVAILABLE), which
toError() in plugin-tag-sandbox.ts carries through onto the rebuilt
Error. maybeWarnMissingManifest branches on that discriminant instead of
regexing the message text; the message strings remain unchanged and
still asserted verbatim by the existing tests.
* fix(templating): key migration-warning dedup by (plugin, module), add ceiling tripwire
maybeWarnMissingManifest deduped its one-time toast by plugin name alone,
so a manifest-less plugin that trips on two different ungranted modules in
one session only ever saw a hint for the first one it hit — the second,
equally actionable denial was silently suppressed. Key by (plugin, module)
instead so each distinct missing grant still gets its own toast, while a
repeat denial for the same module still doesn't re-toast.
Also added a regression test pinning that a manifest with a malformed axis
(declared:true, per-card warning) is correctly routed away from this toast
path, and a tripwire test on TEMPLATE_TAG_PROFILE.moduleCeiling: resolveTemplateTagModules
deliberately doesn't enforce the ceiling (an unregistered module must still
reach require() for the precise "not available" message), which is only
safe while the ceiling equals the full module registry — this test fails
first if a future change narrows it, as a signal to revisit that function.
---------
Co-authored-by: kwburns-kong <kyle.burns@konghq.com>
__buildContext now builds the full context, then removes branches whose
capability is not granted, so context.network/store/app/util.readFile/
util.openInBrowser/util.models.cloudCredential are undefined (not
present-but-rejecting stubs) unless declared. Driven by __hasCap reading the
captured grantedCapabilities via the pristine __arrIndexOf (unspoofable),
kept in lockstep with C1's BRIDGE_PATH_CAPABILITIES. Baseline caps (render,
models.read, util, crypto) and context/meta/renderPurpose data are never gated.
Plugins can feature-detect (if (context.network)) and degrade gracefully;
touching an ungranted branch fails at the property access in the plugin's own
code, not as a later async rejection. C1's bridge gate remains the backstop.
- unit: context-shape matrix, feature-detect, attach-on-grant, and a C1<->C2
consistency test (branch present iff its bridge-path capability is granted)
- reframed the C1 e2e denial test to prove the bridge backstop (branch granted
to C2 but withheld from the bridge filter); no-manifest denial now covered by
C2's absent-branch test
- smoke: capdenied tag feature-detects (returns 'no-storage-capability'); test
retitled (C1 + C2)
* fix: improve request creation handling and input focus behavior
* fix: simplify user ownership check in project component
* fix: refactor onboarding state loading to use async/await for better readability
* fix: enhance request creation button disable logic to include workspace fetcher state
* Create new methods colors
* Implement first request logic
* feat: enhance first request treatment logic with backend support and caching
* feat: add experiment assignment analytics and related utility functions
* feat: add reportRequestsCreated function to track request creation for first-request experiments
* feat: enhance request creation reporting with session tracking
* feat: add tests for reportRequestsCreated function to validate API interactions
* feat: refactor localStorage methods for better error handling and state management
* feat: enhance accessibility of error message in FirstRequestCreation component
* feat: simplify user ownership check in project component
* feat: update user tests to include onboarding state and related functions
* feat: replace reportRequestCreated function with maybeLatchRequestThreshold for request tracking
* feat: integrate onboarding state into first request treatment logic and refactor related functions
* feat: integrate onboarding state into FirstRequestCreation component and enhance treatment logic
* feat: implement maybeLatchRequestThreshold function for tracking first-request graduation
* feat: refactor onboarding request handling and replace reportRequestsCreated with latchRequestThresholdReached
* feat: simplify first request treatment logic and remove unused functions
* feat: update latchRequestThresholdReached to use PATCH method for idempotent threshold latch
* feat: update onboarding state interface and test to use reached_request_threshold instead of has_reached_request_threshold
* feat: update latchRequestThresholdReached to use PUT method and correct endpoint for idempotent threshold latch
* feat: remove reached_request_threshold from onboarding state and related tests
* feat: integrate request tracking with maybeLatchRequestThreshold in clientAction
* feat: update onboarding state handling and treatment logic in user module
* feat: update @getinsomnia/insomnia-v3-fetch to version 1.0.20 in package-lock and package.json
* feat: add padding to request URL bar and adjust MethodSelector alignment
* feat: enhance request threshold handling by adding per-account request count baseline
* feat: update analytics event emission for treatment assignments in first request flow
* fix: propagate system proxy settings to user requests (INS-2943)
When proxyEnabled is false in Insomnia Preferences, the request engine
(node-libcurl) now falls back to the OS system proxy via Electron's
session.defaultSession.resolveProxy(), matching the behavior of
Insomnia's internal API calls.
Previously, disabling the proxy toggle explicitly set CURLOPT_PROXY to
an empty string, which prevented libcurl from using any proxy including
the system proxy. This caused user requests to bypass the OS proxy while
Insomnia's own network calls (via Chromium's network stack) correctly
respected it.
Changes:
- Add parseResolvedProxy() helper to parse PAC-format proxy strings
- Call resolveProxy() inside createConfiguredCurlInstance() when proxy
is not explicitly configured
- Refactor api.protocol.ts to use the shared helper (removes ~50 lines
of duplicated proxy parsing logic)
* fix: catch resolveProxy errors and fall back to direct connection
* fix: catch resolveProxy errors in api.protocol.ts
* fix: remove redundant comment about proxy mode in updateProxy function
* feat(templating): seed ambient sandbox stdlib globals (M2)
Add always-present, ungated safe-equivalent globals to the QuickJS sandbox:
Buffer (from/alloc/concat/isBuffer + utf8/base64/hex/latin1), a frozen
process stub (platform/arch from the envelope, empty frozen env, microtask
nextTick), Web-Crypto crypto.getRandomValues + crypto.subtle.digest
(host-backed), and URL/URLSearchParams. Lives in sandbox-globals.ts, eval'd
after the bootstrap + host-crypto installs.
Because the sandbox now provides a process stub, the old
'typeof process === undefined' sandbox-detection heuristic no longer holds;
add a non-writable INSOMNIA_TEMPLATE_SANDBOX marker and switch the canary +
demo probe to it.
- +19 unit parity tests vs node: Buffer/subtle/URL/URLSearchParams/getRandomValues + escape + marker
- e2e: stdlibprobe renders each API; flag-off captured and asserted byte-identical
under the flag (parity), subtle pinned to a known hash, process.env '{}' + frozen sandbox-only
- smoke helpers wait for modal close + poll the post-toggle canary (re-open until
the flag propagates), de-flaking the mid-test toggle
- demo plugin gains a stdlibprobe tag
require() module wrappers (url/buffer/util/querystring/string_decoder/assert)
are the M2b follow-up; they mostly re-export these globals.
* test(sandbox): pass M2 global-probe values as tag args, not interpolated code
CodeQL flagged the runGlobal test helper for constructing eval'd plugin
source from interpolated values. Make body a constant literal at every call
site and thread dynamic values through the tag's args (envelope data, read as
arguments[1..]) so no test value is concatenated into sandbox-eval'd code.
* fix(sandbox): enforce WebCrypto 65536-byte quota in getRandomValues + review nits
- getRandomValues now throws past 65536 bytes (matching WebCrypto's
QuotaExceededError) instead of silently zero-filling the tail beyond the
host __cryptoRandomBytes clamp — no predictable output.
- Correct the enableSandbox comment: assertions are expect.soft (mandated by
the smoke ESLint config); the authoritative flag-applied signal is the
downstream polling canary.
- Fix a doc-comment typo: typeof process === "undefined" (quoted).
* fix(sandbox): M2 stdlib parity fixes + bridge hardening (#10224)
* fix(sandbox): plumb host arch into process stub
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sandbox): add crypto.randomUUID to ambient web crypto
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sandbox): URLSearchParams.set keeps first occurrence
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sandbox): support URLSearchParams copy-construct
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sandbox): guard bridge dispatch against inherited keys
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sandbox): hide raw host bridge from plugin code
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(lint): fixed linting issue
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: kwburns-kong <kyle.burns@konghq.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The Edit menu's Undo/Redo used role: 'undo'/'redo', driving only the native
webContents stack. On macOS the menu accelerator swallows Cmd+Z before
CodeMirror's keymap, so CodeMirror surfaces had no working undo — two competing
stacks with the native one always winning and doing nothing useful for CM.
Menu Undo/Redo now send edit:undo/edit:redo to the focused window; a single
renderer handler (editor-undo.ts, wired in renderer-listeners.ts) routes by
focus: CodeMirror surfaces drive cm.undo()/redo(); everything else replays the
native execCommand, preserving prior plain-input behaviour. No double-fire: the
accelerator still keeps CM's own keymap from firing independently.
* tech_design
* tech-doc
* add directory to git repo model
* tech doc
* tech doc
* use directory picker to select an existing repo to clone from/to
* tech doc
* Open git repo
* tech doc
* Implement Git project local storage features and add e2e tests
* tech doc
* Implement folder opening as Git projects with user trust confirmation
* Add Git credential selection to project creation form and enhance repo file watcher for directory availability
* tech doc
* refactor: clean up code and remove references to GIT_LOCAL_REPOS_DESIGN.md
* fix: handle optional author name in Git credential display
* feat: enhance Git credential handling for local repositories
* feat: enhance Git project folder handling and improve test descriptions
* fix: update Git project mode button copy
Rename 'Clone from URL' to 'Clone from Remote' and 'Open existing
folder' to 'Open local folder'. Update the smoke test selector and a
stale comment accordingly.
* fix: align control heights and styling in Git clone form
Standardize the credential select, author email select, and clone
location box to match the repository/branch comboboxes: shared
--line-height-xs height, consistent label spacing, and input-sized
value text and padding.
* feat: show clone target path and remember last clone folder
Default the clone parent directory to the folder the user last cloned
into, and render the resulting target path middle-truncated with a full
path tooltip via a new MiddleTruncate component.
* fix: reorder and align Open local folder input layout
Move the helper text directly below the Folder label, and align the
folder box and Choose folder button to the shared control height with
middle-truncated path display.
* feat: warn when opening a folder already used by a project
Add a git.checkGitRepoDirectory IPC that resolves the project adopting
a folder. The Open local folder flow checks at folder-pick time and
shows a red, no-background warning below the input offering to open the
existing project, and blocks continuing while the warning is present.
* fix: top-align empty organization view and scroll the full page
Replace the vertically centered grid with a top-aligned, page-scrolling
layout so the new project form no longer jumps when switching project
types and the scrollbar spans the whole pane.
* fix: align project modal to top and match folder description color
Top-align the project modal overlay so it no longer jumps as the form
height changes, and drop the dimmer color override on the Open local
folder helper text so it matches the repository URL field description.
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix: always use getRepoBaseDir
* fix comment
* delete duplicate code
* fix: test
* test: add Git repository relocation tests
* fix: ensure selection change handler converts key to string
---------
Co-authored-by: Pavlos Koutoglou <pkoutoglou@gmail.com>
Co-authored-by: Curry Yang <163384738+CurryYangxx@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Curry Yang <1019yanglu@gmail.com>
* feat: adds some progress to fixing issue with invalid spectral rulesets being imported when creating git sync projects
* feat: surfaces ruleset import problem to the renderer in git sync projects
* test: adds additional test
* chore: formatting
* fix: ui
* feat(templating): manifest-gated module registry for the sandbox require (M1)
Replace the hardcoded path/crypto require shim with a curated module
registry (single source of truth in module-registry.ts) resolved by a
default-deny __require against the envelope's grantedModules:
- ungranted name -> "Module 'X' not permitted by manifest"
- granted, no impl -> "Module 'X' not available in sandbox"
Grants are the hardcoded path/crypto baseline until the manifest loader
(C3) lands. node:-prefixed aliases resolve to canonical names; exports
are cached per context. E2E: requireprobe tag asserts the baseline works
through the registry and that npm/builtin names fail with the exact
denial message in the tag Live Preview.
* sec(templating): capture envelope before plugin eval; lock registry; null-proto maps
- Inject __envelopeJSON/__tagName before the bootstrap, capture them into
closure state, and delete the globals — plugin top-level code can no
longer rewrite grantedModules (or any envelope field) before __invoke().
- delete __registerModule once the registry is populated so plugin code
cannot register or replace factories.
- Null-prototype registry/alias/cache maps so __proto__/constructor behave
as absent keys.
- Test helper defaults grants to TEMPLATE_TAG_BASELINE_MODULES; new tests
cover envelope tampering and prototype-chain module names.
* sec(templating): harden module gate against intrinsic tampering
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* sec(templating): lock sandbox-internal globals from plugin reassignment
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* sec(templating): enforce trusted-literal invariant for module factorySource
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Kyle <kyle.burns@konghq.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(templating): PoC run plugin template tags in a QuickJS-WASM sandbox
Behind a new `templateTagSandboxEnabled` setting (default off), route plugin
template-tag execution through a QuickJS-WASM sandbox instead of invoking the
plugin's `run()` directly in the main process.
Approach (per PR #10072): bulk-copy render state into the sandbox as JSON,
rebuild the plugin `context` API in pure JS inside the sandbox, and bridge only
async work back to the host via the existing `pluginToMainAPI` handlers.
`node:crypto` is exposed as synchronous host functions so `require('crypto')`
works without a sync/async mismatch.
- templating/sandbox/: quickjs-runtime, marshal, host-bridge, in-sandbox-bootstrap,
plugin-tag-sandbox (+ parity tests vs in-process tags and node:crypto)
- main/templating-worker-database.ts: route execute handlers through the sandbox
when the flag is on; legacy path unchanged otherwise
- esbuild: keep quickjs-emscripten external so its .wasm resolves at runtime
- settings + scripting-settings UI toggle
- examples/insomnia-plugin-sandbox-demo: manual E2E fixture
Scope: template tags only; sandbox runs in main. require shim covers path + crypto
(other modules throw a clear error — follow-up work).
* test(smoke): e2e canary for the template-tag sandbox flag
Installs an inline probe plugin, renders its tags via the tag editor Live
Preview, and asserts the execution path flips main-process -> sandbox when
templateTagSandboxEnabled is toggled in Preferences > Scripting, with a
require('crypto') sha256 workload staying byte-identical across both paths.
* test(sandbox): suppress hardcoded-hmac-key semgrep finding on parity fixture
The HMAC key is a test vector for sandbox-vs-node:crypto parity, not a
credential; rename it to make that self-evident and add the repo-standard
nosemgrep suppression.
* fix(templating): contain sandbox plugin entry resolution to the plugin directory
Reject a package.json "main" that resolves outside the plugin's own folder
and bundled-plugin names that look like paths, so the sandbox source loader
cannot be steered into reading arbitrary files.
* fix(review): inline nosemgrep placement, plugin-load error context, cross-arch-safe canary
- Move the hardcoded-hmac-key suppression onto the flagged line (line-above
placement was not honored by the scanner).
- Wrap getPluginEntrySource failures with the plugin name for diagnosability.
- Derive the canary's expected arch from the Electron main process instead of
the Playwright runner so cross-arch setups can't flake the assertion.
* sec(templating): QuickJS template-tag sandbox additions (#10209)
* fix(sandbox): enforce timeout on synchronous plugin loops
QuickJS's executePendingJobs() blocks the host thread until a synchronous
call returns, so the wall-clock deadline in drivePromiseToString was never
checked during a tight sync loop in plugin code, hanging the Electron main
process indefinitely. Add a QuickJS interrupt handler, which is polled
during synchronous execution, to enforce the deadline.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(sandbox): clamp crypto.randomBytes size to prevent OOM
hostCrypto.randomBytes(size) passed the sandboxed number straight to
Node's crypto.randomBytes with no upper bound, letting a plugin request
a multi-GB allocation (e.g. crypto.randomBytes(2 ** 31)) and crash the
host process. Clamp to 64KB before the call.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(sandbox): cap QuickJS heap to prevent unbounded allocation
QuickJS.newContext() had no memory limit, so a plugin allocating without
bound could exhaust the WASM heap and crash the host process. Set a 32MB
ceiling via ctx.runtime.setMemoryLimit().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(sandbox): resolve symlinks before validating plugin entry path
getPluginEntrySource's containment check compared raw path strings, so a
plugin directory with a symlinked entry (e.g. index.js -> ../../../etc/secret)
passed the check while fs.readFileSync followed the symlink and read the
out-of-directory target. Re-run the check against fs.realpathSync'd paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(sandbox): close util.render sandbox escape
context.util.render() bridged to the shared render() pipeline, whose Liquid
engine dispatches any registered tag's real run() directly, in-process,
regardless of templateTagSandboxEnabled. A sandboxed plugin could hand it a
string containing "{% anyTag %}" (including its own tag) and have that tag
execute completely unsandboxed. Verified with a PoC that reached
child_process execution from inside a plugin tag with no require() or Node
access.
util.render is now restricted to plain {{ variable }} interpolation (the
only real existing use, confirmed against all built-in tag call sites) via
a second Liquid engine with no tags registered; {% tag %} syntax now fails
to parse instead of dispatching. Default render() behavior is unchanged for
every other caller.
Also drops the dead renderDepth field's misleading doc comment: within one
sandboxed execution the envelope's renderDepth is always 0, so depth could
never exceed 1 regardless of enforcement — it couldn't have caught this
recursion anyway.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(sandbox): fixed linting issue
* fix(sandbox): fixed linting issue
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: kwburns-kong <kyle.burns@konghq.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>