mirror of
https://github.com/Kong/insomnia.git
synced 2026-07-31 01:37:32 -04:00
* 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>
180 lines
5.1 KiB
TypeScript
180 lines
5.1 KiB
TypeScript
import type { ValueOf } from 'type-fest';
|
||
|
||
/**
|
||
* The readable definition of a hotkey.
|
||
*/
|
||
export interface KeyboardShortcutDefinition {
|
||
id: string;
|
||
description: string;
|
||
}
|
||
|
||
/**
|
||
* The combination of key presses that will activate a hotkey if pressed.
|
||
*/
|
||
export interface KeyCombination {
|
||
ctrl?: boolean;
|
||
alt?: boolean;
|
||
shift?: boolean;
|
||
meta?: boolean;
|
||
keyCode: number;
|
||
}
|
||
|
||
/**
|
||
* The collection of a hotkey's key combinations for each platforms.
|
||
*/
|
||
export interface PlatformKeyCombinations {
|
||
macKeys: KeyCombination[];
|
||
// The key combinations for both Windows and Linux.
|
||
winLinuxKeys: KeyCombination[];
|
||
}
|
||
|
||
export type KeyboardShortcut =
|
||
| 'workspace_showSettings'
|
||
| 'request_showSettings'
|
||
| 'preferences_showKeyboardShortcuts'
|
||
| 'preferences_showGeneral'
|
||
| 'request_quickSwitch'
|
||
| 'plugin_reload'
|
||
| 'showAutocomplete'
|
||
| 'request_send'
|
||
| 'request_showOptions'
|
||
| 'environment_showEditor'
|
||
| 'environment_showSwitchMenu'
|
||
| 'request_toggleHttpMethodMenu'
|
||
| 'request_toggleHistory'
|
||
| 'request_focusUrl'
|
||
| 'request_showGenerateCodeEditor'
|
||
| 'sidebar_focusFilter'
|
||
| 'sidebar_showCreateDropdown'
|
||
| 'sidebar_toggle'
|
||
| 'response_focus'
|
||
| 'showCookiesEditor'
|
||
| 'request_createHTTP'
|
||
| 'request_showDelete'
|
||
| 'request_showCreateFolder'
|
||
| 'request_showDuplicate'
|
||
| 'request_togglePin'
|
||
| 'environment_showVariableSourceAndValue'
|
||
| 'beautifyRequestBody'
|
||
| 'graphql_explorer_focus_filter'
|
||
| 'close_tab'
|
||
| 'tab_nextTab'
|
||
| 'tab_previousTab'
|
||
| 'tab_reopenClosedTab'
|
||
| 'request_openInNewTab';
|
||
|
||
/**
|
||
* The collection of defined hotkeys.
|
||
* The registry maps a hotkey by its reference id to its key bindings.
|
||
*/
|
||
export type HotKeyRegistry = Record<KeyboardShortcut, PlatformKeyCombinations>;
|
||
|
||
// HTTP version codes
|
||
export const HttpVersions = {
|
||
V1_0: 'V1_0',
|
||
V1_1: 'V1_1',
|
||
V2PriorKnowledge: 'V2PriorKnowledge',
|
||
V2_0: 'V2_0',
|
||
v3: 'v3',
|
||
default: 'default',
|
||
} as const;
|
||
|
||
export type HttpVersion = ValueOf<typeof HttpVersions>;
|
||
|
||
export enum UpdateChannel {
|
||
stable = 'stable',
|
||
beta = 'beta',
|
||
}
|
||
|
||
/** Gets a subset of Settings where the values match a condition */
|
||
export type SettingsOfType<MatchType> = NonNullable<
|
||
{
|
||
[Key in keyof Settings]: Settings[Key] extends MatchType ? Key : never;
|
||
}[keyof Settings]
|
||
>;
|
||
|
||
export interface PluginConfig {
|
||
disabled: boolean;
|
||
}
|
||
|
||
export type PluginConfigMap = Record<string, { disabled: boolean }>;
|
||
|
||
export interface Settings {
|
||
autoDetectColorScheme: boolean;
|
||
autoHideMenuBar: boolean;
|
||
autocompleteDelay: number;
|
||
clearOAuth2SessionOnRestart: boolean;
|
||
darkTheme: string;
|
||
deviceId: string | null;
|
||
disableHtmlPreviewJs: boolean;
|
||
|
||
disableResponsePreviewLinks: boolean;
|
||
|
||
/** If true, Insomnia won’t show a notification when new updates are available. Users can still check for updates in Preferences. */
|
||
disableUpdateNotification: boolean;
|
||
|
||
enableKeyMapForInlineTextEditors: boolean;
|
||
editorFontSize: number;
|
||
editorIndentSize: number;
|
||
editorIndentWithTabs: boolean;
|
||
editorKeyMap: string;
|
||
editorLineWrapping: boolean;
|
||
|
||
/** If true, Insomnia will send anonymous data about features and plugins used. */
|
||
enableAnalytics: boolean;
|
||
filterResponsesByEnv: boolean;
|
||
followRedirects: boolean;
|
||
fontInterface: string | null;
|
||
fontMonospace: string | null;
|
||
fontSize: number;
|
||
fontVariantLigatures: boolean;
|
||
forceVerticalLayout: boolean;
|
||
hasKonnectPat: boolean;
|
||
konnectOrganizationId: string | null;
|
||
hotKeyRegistry: HotKeyRegistry;
|
||
httpProxy: string;
|
||
httpsProxy: string;
|
||
showVariableSourceAndValue: boolean;
|
||
lightTheme: string;
|
||
lineWrapping?: boolean;
|
||
maxHistoryResponses: number;
|
||
maxRedirects: number;
|
||
maxTimelineDataSizeKB: number;
|
||
noProxy: string;
|
||
nunjucksPowerUserMode: boolean;
|
||
pluginConfig: PluginConfigMap;
|
||
pluginNodeExtraCerts: string;
|
||
pluginPath: string;
|
||
preferredHttpVersion: HttpVersion;
|
||
proxyEnabled: boolean;
|
||
showPasswords: boolean;
|
||
theme: string;
|
||
timeout: number;
|
||
updateAutomatically: boolean;
|
||
updateChannel: UpdateChannel;
|
||
useBulkHeaderEditor: boolean;
|
||
useBulkParametersEditor: boolean;
|
||
validateAuthSSL: boolean;
|
||
validateSSL: boolean;
|
||
// vault related settings
|
||
saveVaultKeyLocally: boolean;
|
||
enableVaultInScripts: boolean;
|
||
saveVaultKeyToOSSecretManager: boolean;
|
||
vaultSecretCacheDuration: number;
|
||
dataFolders: string[];
|
||
// AST and shadowing check.
|
||
scriptSandboxEnabled: boolean;
|
||
// Wraps the user script in 'use strict', preventing accidental globals and making `this` undefined.
|
||
scriptStrictModeEnabled: boolean;
|
||
// Experimental: execute plugin template tags inside the QuickJS-WASM sandbox instead of directly in the main process.
|
||
templateTagSandboxEnabled: boolean;
|
||
// Names of security rules that have been individually disabled.
|
||
disabledSecurityRules: string[];
|
||
// AST blocked-property names that have been individually disabled.
|
||
disabledBlockedProperties: string[];
|
||
// AST blocked-root names that have been individually disabled.
|
||
disabledBlockedRoots: string[];
|
||
/** Custom npm registry URL for plugin installation (e.g., corporate mirror). Empty string uses the default https://registry.npmjs.org/. */
|
||
npmRegistryUrl: string;
|
||
}
|