Skip to content

feat(templating): (H1) route user-plugin response hooks through the sandbox (PR 10b)#10286

Merged
jackkav merged 4 commits into
developfrom
claude/sandbox-pr10b-response-hooks
Jul 24, 2026
Merged

feat(templating): (H1) route user-plugin response hooks through the sandbox (PR 10b)#10286
jackkav merged 4 commits into
developfrom
claude/sandbox-pr10b-response-hooks

Conversation

@jackkav

@jackkav jackkav commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 2, PR 10b — H1 (response hooks), completing H1 (request hooks landed in #10279). Routes a user plugin's response hooks through the QuickJS sandbox: the hook reads the response and can rewrite its body, but reaches the host only through capability-gated bridges. Bundle plugins stay in-process; gated on templateTagSandboxEnabled. Stacked on #10279.

How it works

  • __buildResponseApi (in-sandbox): faithful ES5 rebuild of plugins/context/response.ts — getters (status/headers/time; getHeader returns string | string[] | null, case-insensitive). getBody() bridges to the existing response.getBodyBuffer path. getBodyStream() throws (a Node Readable can't cross the sandbox). setBody() base64-encodes the bytes over a new response.setBody bridge and updates bytesContent.
  • __invokeHook: for response hooks, attaches context.response + a read-only context.request (matches pluginRequest.init(..., true)), and marshals both back out.
  • response.setBody host handler: base64-decode → fs.writeFileSync, contained to the responses directory as defense in depth (bodyPath is host-set, never plugin-chosen). Mapped at baseline (models.read, grouped with the other response ops) — a bounded write matching the ungated in-process behavior; not a general fs write.
  • Both paths wired: network-adapter.node.ts (main/CLI) calls runResponseHookInSandbox directly; invoke-method.ts (plugin window) over the protocol. Per-plugin hook-index recovery; the returned response fields merge onto newResponse and the body change persists via the on-disk write.

Tests

  • Unit (sandbox-hooks.test.ts): a response hook reads status/headers, reads the body via the bridge, and rewrites it via setBody — asserting the base64 round-trip, bytesContent, and that the request view is read-only.
  • E2E (sandbox-hook-collection.yaml): a response hook rewrites the body; the echo response pane shows it. Flag off reports ranin-mainprocess (control); flag on reports ranin-sandboxed.

Local: 23 sandbox/worker-db unit tests + type-check + lint (incl. smoke workspace) green. Electron smoke runs in CI.

Note on scope

getBodyStream() intentionally throws in the sandbox (a Node stream is unmarshalable) — response hooks should use getBody(). This is the one deliberate behavior difference from the in-process path.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

✅ Circular References Report

Generated at: 2026-07-24T14:34:15.700Z
Status: ✅ NO CHANGE

Summary

Metric Base (develop) PR Change
Total Circular References 9 9 0 (0.00%)
Click to view all circular references in PR (9)
insomnia-inso/src/db/models/types.ts -> insomnia-inso/src/db/types.ts
insomnia/src/main/prompt-bridge.ts -> insomnia/src/main/window-utils.ts -> insomnia/src/main/plugin-window.ts
insomnia/src/main/window-utils.ts -> insomnia/src/main/plugin-window.ts
insomnia/src/network/network.ts -> insomnia-scripting-environment/src/objects/index.ts -> insomnia-scripting-environment/src/objects/collection.ts -> insomnia-scripting-environment/src/objects/response.ts
insomnia/src/network/network.ts -> insomnia/src/common/render.ts
insomnia/src/ui/components/settings/import-export.tsx -> insomnia/src/ui/components/modals/export-requests-modal.tsx
insomnia/src/ui/components/tabs/tab-list.tsx -> insomnia/src/ui/components/tabs/tab.tsx
insomnia/src/ui/components/templating/tag-editor-arg-sub-form.tsx -> insomnia/src/ui/components/templating/external-vault/external-vault-form.tsx
insomnia/src/ui/components/viewers/response-viewer.tsx -> insomnia/src/ui/components/viewers/response-multipart-viewer.tsx
Click to view all circular references in base branch (9)
insomnia-inso/src/db/models/types.ts -> insomnia-inso/src/db/types.ts
insomnia/src/main/prompt-bridge.ts -> insomnia/src/main/window-utils.ts -> insomnia/src/main/plugin-window.ts
insomnia/src/main/window-utils.ts -> insomnia/src/main/plugin-window.ts
insomnia/src/network/network.ts -> insomnia-scripting-environment/src/objects/index.ts -> insomnia-scripting-environment/src/objects/collection.ts -> insomnia-scripting-environment/src/objects/response.ts
insomnia/src/network/network.ts -> insomnia/src/common/render.ts
insomnia/src/ui/components/settings/import-export.tsx -> insomnia/src/ui/components/modals/export-requests-modal.tsx
insomnia/src/ui/components/tabs/tab-list.tsx -> insomnia/src/ui/components/tabs/tab.tsx
insomnia/src/ui/components/templating/tag-editor-arg-sub-form.tsx -> insomnia/src/ui/components/templating/external-vault/external-vault-form.tsx
insomnia/src/ui/components/viewers/response-viewer.tsx -> insomnia/src/ui/components/viewers/response-multipart-viewer.tsx

Analysis

No Change: This PR does not introduce or remove any circular references.


This report was generated automatically by comparing against the develop branch.

@jackkav
jackkav force-pushed the claude/sandbox-pr10-hooks branch from 8ad4f12 to 033d6d5 Compare July 22, 2026 15:24
@jackkav
jackkav force-pushed the claude/sandbox-pr10b-response-hooks branch from b1eff93 to 62bf5ef Compare July 22, 2026 15:26
@jackkav
jackkav force-pushed the claude/sandbox-pr10-hooks branch from 033d6d5 to 045b1c2 Compare July 22, 2026 17:29
@jackkav
jackkav force-pushed the claude/sandbox-pr10b-response-hooks branch 3 times, most recently from 236b349 to 369f56e Compare July 22, 2026 20:21
Base automatically changed from claude/sandbox-pr10-hooks to develop July 23, 2026 13:27
@jackkav
jackkav force-pushed the claude/sandbox-pr10b-response-hooks branch from 369f56e to 57ecee8 Compare July 23, 2026 13:48
@jackkav
jackkav marked this pull request as ready for review July 23, 2026 13:56
Copilot AI review requested due to automatic review settings July 23, 2026 13:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Routes user-plugin response hooks through the QuickJS sandbox (H1 phase), so response hooks can read response metadata/body and rewrite the body only via capability-gated host bridges; bundled plugins remain in-process and behavior is gated by templateTagSandboxEnabled.

Changes:

  • Add an in-sandbox context.response API (__buildResponseApi) and wire __invokeHook to support response hooks with a read-only context.request.
  • Add host bridge support for response.setBody plus host-side sandbox runners/IPC wiring to execute user response hooks from both node runtime and plugin window paths.
  • Add unit + smoke coverage for sandboxed response hook execution and body rewrite behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/insomnia/src/templating/sandbox/sandbox-hooks.test.ts Adds a unit test covering sandboxed response hooks and the new body rewrite bridge.
packages/insomnia/src/templating/sandbox/in-sandbox-bootstrap.ts Implements the in-sandbox response API and wires response hooks through __invokeHook.
packages/insomnia/src/templating/sandbox/host-bridge.ts Registers the new response.setBody bridge path under capability gating.
packages/insomnia/src/runtimes/network/network-adapter.node.ts Executes user response hooks in the sandbox (when available/enabled) in the node runtime send pipeline.
packages/insomnia/src/plugins/invoke-method.ts Executes user response hooks via the templating-worker-database protocol when sandboxing is enabled.
packages/insomnia/src/main/templating-worker-database.ts Adds runResponseHookInSandbox, plugin.runUserResponseHook, and the response.setBody host handler.
packages/insomnia/src/common/templating/types.ts Extends PluginToMainAPIPaths for the new bridge endpoints.
packages/insomnia-smoke-test/tests/smoke/sandbox-template-tags.test.ts Adds E2E coverage ensuring sandboxed response hooks rewrite the visible response body with the flag on/off.
Comments suppressed due to low confidence (1)

packages/insomnia/src/templating/sandbox/in-sandbox-bootstrap.ts:466

  • context.response.setBody() treats undefined/null body as an empty payload (body || []) and will write an empty file, whereas the in-process implementation ultimately throws if you pass undefined to fs.writeFileSync. Add an explicit null/undefined guard and avoid silently coercing missing bodies to empty bytes.
  '      setBody: function (body) {',
  '        if (!resp.bodyPath) { throw new Error("Could not set body without existing body path"); }',
  '        resp.bytesContent = (body && body.length) || 0;',
  '        var b64 = (typeof body === "string") ? Buffer.from(body, "utf8").toString("base64") : Buffer.from(body || []).toString("base64");',
  '        return __bridge("response.setBody", { bodyPath: resp.bodyPath, bodyBase64: b64 });',

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/insomnia/src/templating/sandbox/sandbox-hooks.test.ts
Comment thread packages/insomnia/src/templating/sandbox/in-sandbox-bootstrap.ts Outdated
Comment thread packages/insomnia/src/main/templating-worker-database.ts Outdated
jackkav added a commit that referenced this pull request Jul 23, 2026
…ect empty setBody

Addresses Copilot review on #10286:
- in-sandbox-bootstrap.ts: context.response.getBody() now revives the JSON-marshaled
  Buffer shape ({ type: 'Buffer', data: [...] }) back into a (shimmed) Buffer, so hooks
  consume it exactly like the in-process response API instead of a plain object.
- templating-worker-database.ts: response.setBody rejects a missing/non-string bodyBase64
  (TypeError) instead of defaulting to '' — a write primitive must not silently truncate
  the body to zero bytes. An empty string stays a valid empty body.
- sandbox-hooks.test.ts: the getBodyBuffer stub now returns the real marshaled Buffer
  shape and the hook decodes with toString('utf8'), exercising the revive end-to-end.
@jackkav
jackkav enabled auto-merge (squash) July 23, 2026 15:54

@kwburns-kong kwburns-kong left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jackkav, PR #10294 covers a few more things. I've started to look into a bit of the capabilities / bridge handlers, we should add a final step within the sandbox plan to identify and correctly scope what each can directly do.

jackkav and others added 4 commits July 24, 2026 16:27
First increment of PR 10b (response hooks). Adds the sandbox-side machinery to
run a user plugin's response hook, without yet wiring the network adapters:

- __buildResponseApi(resp): faithful ES5 rebuild of plugins/context/response.ts
  getters (status/headers/time, case-insensitive getHeader returning
  string|string[]|null). getBody() bridges to the existing response.getBodyBuffer
  path; getBodyStream() throws (a Node Readable can't cross the sandbox); setBody()
  base64-encodes the bytes over a new response.setBody bridge path (no fs in the
  sandbox) and updates bytesContent locally.
- __invokeHook: for response hooks, attaches context.response and a READ-ONLY
  context.request (matches pluginRequest.init(..., true)), and marshals both the
  request and response back out.
- host-bridge: response.setBody mapped at baseline (models.read, grouped with the
  other response ops) — a bounded write to the host-set bodyPath, matching the
  ungated in-process behavior; not a general fs write.

Unit test drives a response hook that reads status/headers, reads the body via
the bridge, and rewrites it via setBody (asserting the base64 round-trip and the
read-only request). Host runner + adapter integration + e2e are the next increment.
…andbox

Wires the response-hook sandbox core into both hook paths so a user plugin's
response hook runs in QuickJS (bundle plugins and the flag-off path unchanged).

- templating-worker-database: runResponseHookInSandbox + the
  plugin.runUserResponseHook handler; the response.setBody bridge handler
  (base64-decode -> fs.writeFileSync) guarded to the responses directory as
  defense in depth (bodyPath is host-set); pickHookResponseFields.
- network-adapter.node.ts (main/CLI): runs the response hook in the sandbox for
  user plugins, merging the returned response fields onto newResponse.
- invoke-method.ts (plugin window): reaches the same runner over the protocol.

E2E: a user plugin response hook rewrites the body via setBody; the echo
response pane shows the rewrite. Flag off it reports ranin-mainprocess (control),
flag on ranin-sandboxed. Completes H1 (request hooks in PR 10a).
…ect empty setBody

Addresses Copilot review on #10286:
- in-sandbox-bootstrap.ts: context.response.getBody() now revives the JSON-marshaled
  Buffer shape ({ type: 'Buffer', data: [...] }) back into a (shimmed) Buffer, so hooks
  consume it exactly like the in-process response API instead of a plain object.
- templating-worker-database.ts: response.setBody rejects a missing/non-string bodyBase64
  (TypeError) instead of defaulting to '' — a write primitive must not silently truncate
  the body to zero bytes. An empty string stays a valid empty body.
- sandbox-hooks.test.ts: the getBodyBuffer stub now returns the real marshaled Buffer
  shape and the hook decodes with toString('utf8'), exercising the revive end-to-end.
@jackkav
jackkav force-pushed the claude/sandbox-pr10b-response-hooks branch from 78eb899 to 60f6667 Compare July 24, 2026 14:27
Comment thread packages/insomnia/src/main/templating-worker-database-surface.ts
Comment thread packages/insomnia/src/main/templating-worker-database-surface.ts
@jackkav
jackkav merged commit 5ff4186 into develop Jul 24, 2026
21 checks passed
@jackkav
jackkav deleted the claude/sandbox-pr10b-response-hooks branch July 24, 2026 14:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants