keyContextMeta is a plain object, so keys such as constructor or __proto__ resolved through Object.prototype and produced an empty Source row. Require an own property before indexing it, and cover the case in the smoke test.
Background:
The Edit Variable modal captured a variable's source when its chip was clicked and kept it for the modal's lifetime. Switching the selected variable or editing a custom template refreshed the Live Preview but left the Source row and Open action pointing at the previous environment.
Changes:
- Derive the source from the current template via the render context inside VariableEditor; hide the row unless the template is exactly one known variable reference.
- Ignore superseded async runs so preview, error, options and source always describe the same template.
- Drop the one-shot variableSource option from NunjucksModal; onOpenSource now receives the freshly derived metadata.
- Cover variable switching, custom-template editing, hiding and Open in the variable-source smoke test.
Follow-up to #10583.
* feat: handle websocket socket.io mcp response
* update
* feat: redact Event Stream timeline, event log and response document
T10 wired WebSocket, Socket.IO and MCP, but Event Stream was missed: the
connect route's SSE branch sits in the same function as the other three and
never passed sensitiveValues on to curl.open, and curl.ts had no redaction at
all. Its writes happen in the main process, so they bypass the renderer's
SendActionRuntime wrapper the same way the other realtime protocols do.
The renderer side already produced the values - request-url-bar's sendOrConnect
renders an Event Stream request through renderRealtimeConnectPayload like any
other realtime request - so only the consumer was missing.
curl.ts now mirrors websocket.ts: a per-requestId Set that only grows, with
every timeline and event-log write going through writeTimelineEntry /
writeCurlEventLog, and the set dropped in deleteRequestMaps. The two Response
documents are covered too: the effective URL on the success path and the error
message on the failure path.
Error messages need care about ordering. Both error paths call deleteRequestMaps
- which clears the request's registered values - before building the error
Response, so redacting inside createErrorResponse would be a no-op. The message
is redacted before deleteRequestMaps instead and passed down already clean; a
curl error readily echoes the rendered URL.
Verified with the existing event-stream smoke specs. The confidential-redaction
smoke spec lives on the upstream branch, so SSE has no targeted on-disk
assertion until this branch is rebased onto it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: only redact realtime Basic Auth when the credentials are confidential
base64(user:pass) is a derived form - it does not contain the plaintext
password, so it never matches the values the renderer registered and has to be
registered separately. T10 did that for WebSocket, but gated it on the setting
alone, so with the setting on (the default) every WebSocket Basic Auth header
was masked in the console, including credentials nobody had marked. The
websocket smoke spec, which asserts a literal credential stays readable, has
been failing on that.
Registration is now conditional on an input actually being confidential, which
the main process can tell by running its own registered values over the
username and password. The setting check goes away with it: when the setting is
off the renderer sends no values, so the set is empty and nothing ever looks
confidential - one less switch that can drift out of sync.
Event Stream had the mirror-image problem and gets the same treatment. It builds
its auth header in main through the shared getAuthHeader, after the render that
registered the credentials, so until now the base64 of a confidential password
went to disk in the clear. Socket.IO and MCP are unaffected: neither builds an
auth header in main, and Socket.IO's singleToken is rendered in the renderer, so
its plaintext is registered normally. The same derived-form gap still applies to
the plain HTTP send path, which is the known limitation already recorded in the
plan.
Both directions are pinned, because only one of them fails loudly if the
predicate is wrong: the existing websocket spec covers "a literal credential
stays readable", and a new case in the confidential-redaction spec covers "a
credential derived from a confidential value is masked" - without the latter, a
predicate that always returned false would pass silently.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix comment
* extract common function
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Purpose-built cards for WebSocket, Socket.IO and gRPC requests, the MCP
request, mock routes, the cookie jar and environments, each with tabs that
mirror its editor. Vault-backed secrets are always masked.
Co-authored-by: James Gatz <jamesgatzos@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
[INS-3767](https://konghq.atlassian.net/browse/INS-3767)
## Summary
- Remove scripting-environment imports from Insomnia application modules.
- Move shared faker, URL helpers, and response reason data into appropriate insomnia-data modules.
- Move certificate matching behavior and its test into the ClientCertificate model.
- Update all callers and remove obsolete module paths.
## Verification
- insomnia-data type-check and lint
- insomnia-scripting-environment lint and tests
- insomnia type-check
- URL/certificate tests: 50 passed
- circular-reference check: no new circular dependencies; insomnia-scripting-environment has 0 cycles
The existing scripting-environment tsconfig is intentionally unchanged here; its source-wide type-check scope is enabled in the stacked sendRequest PR after the remaining Insomnia runtime dependency is removed.
* feat(Git Diff): add a read-only Visual diff view to the commit modal
Adds a "Visual (Beta)" toggle next to the Text diff in the staging modal. The
Visual view lists the changed entities of the previewed file as cards: requests
and folders get tabbed cards that mirror their editors, every other entity uses
a generic card for now. Each card has a Settings catch-all for changes no tab
shows, and long values are shown as a lazily created Monaco diff.
Also stops the Text view from hiding indentation-only changes, which are
structural in YAML.
Co-authored-by: James Gatz <jamesgatzos@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(Git Diff): show same-named params as rows, not via the Settings catch-all
With same-named items told apart in the diff, a change to one of them now
shows as its own row in the tab rather than as a raw value in the Settings
catch-all. The catch-all stays for changes a tab can't show as rows, such as
reordering.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(Git Diff): scroll long values inside a card instead of stretching it
A long value shown on one side only (eg. the body of a newly added request)
and a highlighted diff of a large change both grew to their full height, so one
card could be thousands of pixels tall. Both are now capped at `max-h-96` and
scroll inside; past their top or bottom the wheel scrolls the card list.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(Git Diff): start a card collapsed when it has nothing below its header
A card with no tabs (eg. only the name or URL changed) only had "No further
details to show." below its header, taking up a row for nothing. It now starts
collapsed; expanding it still shows that line.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: James Gatz <jamesgatzos@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: mask confidential valut in timeline
* mask longest first
* handle script log
* prevent circular dependency
* fix comment
* fix test
* feat: close the remaining confidential-value leaks to disk
Covers items #2-#5 and #7a of the post-T10 follow-up list.
Register values the script itself wrote (#2)
A pre-request script's console output is written before any render, and an
after-response script is followed by no render at all, so a value the script
put into a confidential key (insomnia.environment.set('token', ...)) was never
registered and reached the timeline in cleartext. tryToExecuteScript now
registers the script's output data before writing its logs.
The registration logic moves into registerConfidentialValues(layers, collector),
which takes confidential flags from kvPairData and values from the layer's data
- the same split buildRenderContext already uses at render time. The pre-fill
now goes through it too, so there is one implementation rather than two, and
disabled rows, SECRET rows and duplicate names behave identically in both.
Register numeric leaves (#5)
collectLeafStrings only collected strings, so a confidential JSON value like
{ "pin": 1234 } rendered into the request as plaintext. Renamed to
collectLeafValues; numbers register as String(value). Booleans, null and
non-finite numbers stay skipped - they carry no secrecy and would mangle
unrelated JSON. No minimum-length threshold, matching the trade-off already
accepted for short strings: over-redaction beats an unprotected short secret.
Redact response documents and test results (#3, #4)
Response documents are persisted without passing through the timeline's
withRedaction wrapper. Their url (the effective URL), error (most often a
script throw, which readily echoes a value - chai prints the actual value, and
V8 echoes its input for things like JSON.parse(token)) and requestTestResults
(user-supplied test names and chai's ACTUAL/EXPECTED) are now redacted at every
write point, including the RunnerTestResult the collection runner persists.
{% response 'url' %} now throws when it finds a redacted URL instead of
silently chaining the mask to the server. Storing the real URL and masking only
on display was the alternative, but that keeps plaintext secrets on disk.
Redact the four send-bypass paths (#7a)
The OAuth2 token request, plugin sendRequest, the templating worker's
dependent-request path and mock send all call sendCurlAndWriteTimeline without
a runtime. That does not skip the timeline - it falls back to the non-redacting
default - so all four wrote rendered confidential values to disk, in the
timeline as well as the response document.
The OAuth2 call also passed no render purpose, and the policy is fail-closed,
so it was interpolating the mask into the token request instead of the
credential. develop has the same omission (it affected SECRET vault values
there); marking a variable confidential widened it. Fixed with purpose: 'send',
which has to land together with the collector: on its own it would turn a
broken-but-safe path into a leaking one.
Verification
Unit tests for the helpers, plus a smoke spec that drives the real app and
reads what landed on disk, since none of this wiring is reachable from the
node test runtime. Three cases: setting on (nothing in the timeline or the
response document, but the real value still goes over the wire), a request
re-sent by a template tag, and - as a negative control, so the first case
cannot pass vacuously - setting off, where the values must be written in
cleartext.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix test
* fix comment
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
## Background
In a Git-synced project, restarting Insomnia without any change marked every design document as modified in the Git staging list. The diff of each affected YAML contained exactly one line: the embedded ApiSpec `meta.modified` timestamp.
The v5 design-doc YAML does not serialize `ApiSpec.fileName` (the v5 schema has no such field), while v5 import parses a missing `fileName` as an empty string. Any git round-trip therefore reset `fileName` to empty in the database, and the startup repair (`_applyApiSpecName`) rewrote it via `docUpdate`, which unconditionally restamped `modified: Date.now()` for every design document in one burst. RepoFileWatcher then re-exported the database to disk and git status reported all files as modified, repeating on every startup until committed.
## Changes
- Import: fall back `ApiSpec.fileName` to the file top-level name when the spec uses the `{ contents }` shape, so a round-trip never stores an empty fileName.
- Repair: skip the fileName backfill when it would write the existing value; a no-op write only restamps `modified`.
- Regression tests for both behaviors.
Fixes INS-4058
* add basic utility for cloud sync check
* do not show error when entire deleted
* add confirmation window when submit contains unknown fields in cloud sync
* feat(phase3-ui): apply confidential policy to all preview surfaces
Wire purpose:'preview' explicitly into RenderedText, RenderedQueryString,
CookiesModal, CodeEditor, and OneLineEditor so they respond to the
hideSecretValuesInPreviewAndConsole setting (Phase 3.2).
Refactor VariableEditor to use isRevealed state with forceReveal instead
of toggling purpose, making the eye button available for all variable
types (normal confidential, SECRET, external vault).
Short-circuit external vault tag run() in both liquid-extension.ts and
liquid-extension-worker.ts via shouldMaskExternalVaultTag() so no
provider fetch occurs when the policy says mask (Phase 3.3).
Add isRevealed/forceReveal to TagEditor for consistent reveal UX.
Wire purpose:'codegen' in request-actions-dropdown and
generate-code-modal callers to complete the codegen/export split
(Phase 3.4 UI callers).
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* refactor(phase3-ui): generate-code callers use purpose:preview instead of codegen
Now that codegen purpose has been removed from RenderPurpose, switch both
Generate Code modal and Copy as cURL (request-actions-dropdown) to pass
purpose:'preview'. Behaviour is identical: setting ON masks confidential values,
setting OFF reveals them.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* update
* fix: process in worker
* fix: smoke test
* fix lint
---------
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* feat(phase3-engine): implement confidential value policy and render engine
Introduce getConfidentialValuePolicy() as the single decision point for
reveal/mask across all render surfaces (Phase 3.0). Extend RenderPurpose
with 'codegen' and add forceReveal/getSettings() to BaseRenderContext.
Update buildRenderContext() to track per-key ConfidentialValueKind via
EnvironmentRenderLayer so normal-confidential KV pairs are masked before
entering renderSubContext() — preview cannot pollute subsequent send
(Phase 3.1).
Wire maskOrDecryptVaultDataIfNecessary() through the same policy so
SECRET reveal/mask follows hideSecretValuesInPreviewAndConsole.
Add explicit purpose:'codegen' to exportHarWithRequest/exportHarRequest
IPC path so Generate Code always masks confidential values independent of
the user setting (Phase 3.4 IPC layer).
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* refactor(phase3-engine): remove codegen purpose — generate-code callers use preview
codegen and preview now have identical policy semantics (both controlled by
hideSecretValuesInPreviewAndConsole; forceReveal only applies at call-site for
preview). Keeping a separate purpose added complexity with no behavioural benefit.
Remove 'codegen' from RenderPurpose, drop the codegen branch from
getConfidentialValuePolicy, and restore shouldMaskExternalVaultTag to intercept
only 'preview'. Generate Code / Copy as cURL callers will pass purpose:'preview'
instead (updated in the UI branch).
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* refactor(phase3-engine): extract buildEnvironmentRenderLayers from buildRenderContext
Pure extract-function: the environment layer construction is unchanged,
just moved into its own exported function so downstream stacked PRs
(T9/T10 confidential-value prefill) can reuse the same layer
enumeration instead of re-implementing it. buildRenderContext's
behavior is unchanged and remains covered by render.test.ts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(phase3-engine): stop buildEnvironmentRenderLayers mutating the ancestors array
for (const doc of (ancestors || []).reverse()) reversed the caller's
array in place. Now that the layer construction is extracted into a
standalone function, downstream callers (e.g. a T9/T10 prefill pass
that enumerates the same layers before the real render pass) can
receive the same ancestors array and call it twice, flipping the
folder precedence order on the second call. Copy before reversing so
the caller's array is left untouched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* update
* fix: comment
---------
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* feat(Git Diff): add an entity-level diff engine for Insomnia YAML files
Parses both sides of a git-synced Insomnia file, matches requests, folders,
environments, mock routes, the MCP request, the cookie jar and the file-level
sections by id, and returns the added / removed / modified entities with their
field-level changes. Not wired into any UI yet.
Co-authored-by: James Gatz <jamesgatzos@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(Git Diff): tell same-named params and headers apart in the diff
diffByKey matched items by name only, so of two same-named items (eg. a
disabled default User-Agent header next to a custom one) the later hid the
earlier: a change to the first was invisible, and adding or removing one read
as a modification. Items sharing a name are now numbered by their order among
that name, as "User-Agent (1)" and "User-Agent (2)", on both sides.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: James Gatz <jamesgatzos@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: bumped zod@3.25.76 to zod@4.6.5
Zod 4 treats a required z.undefined() object key as non-optional, so
absent keys in the RequestCollectionSchema union discriminators failed.
Wrap those keys in .optional() to keep v3 semantics.
Update the deep-strict auth test to expect the unrecognized_keys issue
that zod 4 reports for the strict empty-object fallback branch.
* chore: bumped jsdom@25.0.1 to jsdom@30.1.2
- Clears @asamuzakjp/css-color@3.2.0 advisory (AIKIDO-2026-199800); css-color now 7.1.4.
- No source changes needed: none of the jsdom 26-30 breaking changes hit the repo's jsdom tests.
* chore: bumped @bufbuild/protobuf@1.10.1 to @bufbuild/protobuf@2.16.0
Moves the gRPC stack to connect v2 alongside protobuf v2 (connect-node
and connect 1.7.0 -> 2.2.0). This clears the transitive undici 5.29.0
and @fastify/busboy 2.1.1 under connect-node 1.x.
Source changes:
- grpc.ts: createPromiseClient is removed in v2, so the reflection
service is now built from a FileDescriptorProto via fromJson and
createFileRegistry (proto3.makeMessageType is removed). The unary call
goes through transport.unary directly because createClient cannot type
a runtime-built descriptor.
- grpc.ts: toBinary takes the schema (FileDescriptorSetSchema from wkt).
- grpc.test.ts: mock transport uses the v2 unary signature (no service
argument, method.output, fromJsonString).
* chore: regenerate insomnia JSON schema for dependency bumps
* test: add grpc reflection regression tests for connect v2
* test: add smoke tests for buf reflection API
* fix: don't auto-send gRPC reflection API key on request open
* fix: drop non-standard starts_with format from published schema
## Background
Hovering a `{{ _.vault.key }}` chip showed no source: vault secrets are stored under the reserved `__insomnia_vault` key in each environment and only surface under the runtime `vault` namespace after decryption, while the key source map was built by a parallel walk over the raw environment data in `getRenderContext`, duplicating the merge semantics that `buildRenderContext` implements for values.
## Changes
- Move key source registration into `buildRenderContext`, where the values are merged. Registration runs in the same order the runtime resolves values: the `__insomnia_vault` entries of every environment are recorded under the `vault` namespace first, then every environment's plain data walk — so a legacy plaintext `vault` key, which import and sync can carry without reserved-key validation, wins over sealed values just like it does in the final vault spread.
- Unify source labels to `<environment type> : <name>` — Project Environment, Collection Environment and Folder Environment; runner upload data and script variables keep their own type names.
- Prefix the source line in the hover tooltip with `Source`, matching the live preview modal.
- Rename the setting to "Template tags show variable source and value" so its scope is obvious from the name.
* migrate request related p2
- grpc-request
- cookie-jar
- fix ts errors
* add unit test to check field mismatch
* migrate request group model
* migrate the socket io request and import parser
* migrate websocket schema and fix ts error
* fix order issue and add unit tests
* fix: Warn when a Git Sync change cannot be discarded [INS-3689]
When a committed YAML file is not in the exact format Insomnia generates
(hand-edited, or written by an older version), discarding it restores the
file, the repo file watcher imports it and re-exports it, and the change
reappears. Users get stuck with a change they can never discard.
Detect this up front by replaying the import -> export round-trip in memory
on the raw index/HEAD content. When the result equals the working copy, the
whole diff is Insomnia re-generating the file, so the Commit Changes modal
shows a warning above the diff and the discard confirmation explains that
the file needs to be committed once.
- Add sync/git/canonical-file.ts, which mirrors database.update (initModel)
and the getWithDescendants ordering (created, then _id)
- Split getInsomniaV5DataExport into a DB fetch and a pure
serializeInsomniaV5Export
- Return rawHead/rawStage from GitVCS.fileStatus, since head/stage are
migrated and reordered for display
- Export initModel from insomnia-data/node
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: Replay the Git Sync round-trip on a scratch database [INS-3689]
The undiscardable-change check reimplemented parts of the import/export
pipeline (initModel, getWithDescendants ordering) to avoid touching the app
database. That copy would silently drift whenever the real pipeline changes.
Run the real pipeline instead: write the imported docs to a scratch
in-memory NeDB instance and export them with getInsomniaV5DataExport.
- Keep NeDB collections, change buffer and listeners per instance in
createNedbDatabase. They were module-level, so a second instance's init()
would have replaced the app's collections. flushChangesImpl is now handed
to the wrapper instead of being exported.
- Add an optional db parameter to getInsomniaV5DataExport and drop the
serializeInsomniaV5Export split and the initModel export
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: Ignore import timestamps when detecting undiscardable changes [INS-3689]
Import fills a missing meta.created/modified with the current time, so a
hand-added entry without timestamps got a different value in the replay
than in the working copy the watcher wrote earlier. The exact comparison
then failed and the warning never showed, although the change still came
back after every discard.
Compare the replay with the working copy using hasSignificantChanges, the
same rules the Unstaged changes list uses, so differences the list already
ignores no longer hide the warning.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: Reword the undiscardable change warning [INS-3689]
Explain the cause in user terms, using the wording suggested in the ticket.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: Shorten the undiscardable change warning [INS-3689]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: Rename committedContent to baselineContent [INS-3689]
The content a discard restores is the staged content when there is one, so
it is not necessarily committed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## Background
Users can define the same variable at many environment levels (project, collection base/sub, folder, script-local). When hovering or clicking a variable in an editor there was no way to tell where its value comes from.
[INS-3943](https://konghq.atlassian.net/browse/INS-3943)
## Changes
- **Render context**: track variable source metadata (`keyContextMeta`) alongside `keyContext` — source label plus the owning workspace / environment / folder ids. Fix `keySource` ancestor ordering to match `buildRenderContext` (closest folder wins), which previously reported the wrong source for shadowed keys — this also affected the existing "Show variable source and value" setting. Keys overridden by runner upload data or `insomnia.variables.set` get a non-navigable source meta (no workspaceId, label `uploadData` / `scriptLocalVariables`) so consumers never point at an environment that no longer supplies the value; the meta label doubles as the `keySource` string the "Show variable source and value" setting renders.
- **Live preview modal**: "Edit Variable" shows a Source row (inside the Live Preview block) with an Open action.
- **Hover tooltip**: hovering a variable shows value + source + Open, rendered by a global `VariableSourceTooltipHost` using the shared tooltip stack (`useOverlayPosition` + `tooltip__bubble` styling). Undefined variables surface the render error instead of vanishing.
- **Open action routing** (via `buildResourceUrl` from the navigation route registry, so the hook holds no hand-built URLs and no tab-specific code):
- folder environment → the folder pane (`?tab=environment`), by plain navigation; the tab-list route sync (`useInsomniaTab`) activates the matching tab or creates a temporary one — the same generic mechanism every deep link uses;
- project environment (environment-type workspace) → its workspace page (`?environmentId=`), likewise plain navigation; both deep-link params are validated against the target's own resources and consumed after applying, so forwarded search params cannot re-select them later;
- collection environment → the workspace's **Manage Environments modal** (the same dialog the environment picker's manage button opens) opened in place, with the source environment preselected. Editors reach it through a new `OPEN_ENVIRONMENTS_MODAL` event on `uiEventBus`, hosted by the workspace pane header.
- **E2E**: data-driven suite `variable-source-live-preview.test.ts` — 5 environment levels × {send value, modal Source/preview/Open, hover tooltip, "Show variable source and value" inline display} + error-chip and tooltip-content-keep-alive regressions. Collection scenarios assert the Manage Environments modal opens with the env row preselected.
## Design notes
- Tooltip timing lives in a small controller (`variable-source-tooltip.ts`) because the trigger is a CodeMirror widget outside the React tree; rendering/positioning/events on the bubble use the existing react-aria stack. No new dependencies.
- The Open hook (`use-open-variable-source.ts`) is reachable from root-mounted modals (CodePromptModal → MarkdownEditor → CodeEditor), so it must not statically import the tab system or the environments modal — either import closes a circular reference back to `root.tsx` (verified against `dependency-cruiser`, which counts type-only and dynamic imports). The tab system is reached through the generic route sync instead, and the modal through the event bus; the modal is hosted by `WorkspacePaneHeader`, which already imports it — zero new edges in the module graph (`check-cycle-references` reports the baseline count).
- Collection environments have no page of their own; the picker's Manage Environments dialog is the canonical editing surface, so Open reuses it instead of navigating. The earlier draft's dedicated env-page tab id (`${workspaceId}-env`) and `buildTabFromNavigation` overrides were removed — `workspace:environment` was already registered in the navigation route registry, so the generic mechanism covers it.
- Old-syntax `{{ a }}` variables have no source: the source map is keyed under the `_` namespace only. Extending it to bare keys is a potential follow-up.
- Security review: no findings (no XSS/prototype-pollution/open-redirect/secrets-exposure regressions; vault masking parity with develop).
The id preservation in addSetCookiesToToughCookieJar() (added in #10475) had no
test for the case this ticket describes: replaying an identical Set-Cookie
response, or a response that sets only one of several cookies, must keep the
existing cookie ids so the Git-synced cookie jar does not get dirtied.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
## Background
Project storage conversion reuses the document ids owned by the previous storage. Known problems:
- Id hijack via global upsert: converted projects keep git YAML ids pulled by RepoFileWatcher or cloud backend ids pulled by cloud sync, and external imports upsert documents globally by their original ids — a later import of the same source overwrites or steals this project's documents.
- Id conflicts block the conversion loop: a project-scoped clone rejects with "The repository being cloned contains workspaces that already exist in Insomnia." when the target project still holds the repository's ids.
- The git FS watcher is not stopped during conversion: an active watcher flushes DB changes (including re-keyed docs) back into the repository files mid-conversion.
- Git → cloud conversion creates no backend resources: no backend project initialization and no snapshot push, so the converted cloud project has nothing syncable.
- Stale workspace-level git state is left behind (gitRepositoryId, gitFilePath, gitFileLastSyncTime, hasUncommittedChanges, hasUnpushedChanges), so the project card keeps showing git status after conversion.
- WorkspaceMeta references are not remapped: the model has no rewriteReferences, so active environment/request/test-suite selections dangle after re-keying.
- Conversion gives no warning that regenerating ids invalidates inso commands, CI scripts and template tags referencing the old ids.
### Repro
1. Create a git project and pull its content.
2. Convert the project to cloud storage.
3. Convert it back to git, pointing at the same repository.
Result: the clone is rejected because the converted project still holds the repo's original workspace ids.
## Changes
- Add regenerateProjectDocIds: re-keys every document under the project's workspaces with fresh ids, remapping parent chains and embedded references (including template tags) via models.rewriteReferences; adds the missing WorkspaceMeta remapper
- Call it on every storage conversion branch of the project update route: local ↔ remote, local ↔ git, remote ↔ git; regeneration runs after the previous storage's watcher is stopped and before the new storage's import
- Stop the FS watcher and clean up the managed repo folder before touching DB docs on git conversions, and clear stale workspace-level git state afterwards
- Initialize cloud sync (initialize backend project + push snapshot) on git → cloud conversions so the re-keyed docs reach the backend with their new ids
- Show a confirmation dialog (AskModal) before any storage conversion in project-settings-form
- Tests: re-keying of nested runtime docs (responses, unit tests), multi-workspace independence, empty workspace, and WorkspaceMeta reference remapping
## Background
`isImportModalOpen` in the workspace debug route and the first-request creation screen is initialized to `false` and never set to `true` anywhere, so the `ImportModal` instances they render can never open.
## Changes
- Remove the dead `isImportModalOpen` state, the `ImportModal` render blocks, and the now-unused imports/variables in `debug.tsx` and `first-request-creation.tsx`.
No behavior change: the modals were unreachable.
## Background
Users copy a cURL command from docs and expect pasting it into Insomnia to start an import. Previously this only worked through the URL bar (Paste Curl modal) or the manual import dialogs.
## Changes
- Add `useGlobalCurlPasteImport`: a document-level capture-phase `paste` listener that intercepts cURL-shaped text only when no editable surface (input, textarea, contentEditable — CodeMirror included) has focus, then synchronously prevents the default action and hands the text over.
- Wire it in Root to reuse the deep-link import channel (`setImportObject` → `ImportModal`) with auto-scan enabled.
- Inside a workspace, the current collection is offered as the preselected import target via the existing `defaultWorkspaceId` prop (applied once so revalidation cannot override the user's choice); the modal header reflects the target workspace. All entry points passing the prop now preselect consistently, and deep-link imports keep defaulting to a new collection.
- With no project in context the modal opens on the `-- New Project --` creation flow instead of a toast.
- Fully disabled in the Scratchpad, which holds a single collection.
## Tests
- jsdom unit tests for the hook (`use-global-curl-paste-import.test.tsx`).
- Playwright smoke tests driving the real <kbd>Cmd</kbd>+<kbd>V</kbd> (`paste-curl-import.test.ts`): project page auto-scan, workspace preselect, focused URL bar (existing Paste Curl modal preserved), no-project creation flow, Scratchpad no-op.
* fix: validate paths stay within their intended base directory
* test: add regression coverage for path containment checks
* fix: only contain the symlink's own location, not its target content
* revert: drop fs-client and backup containment, superseded by #10560
* test(path-guard): cover '..test' folder allowed and traversal rejected
* test(path-guard): cover readOrCreateDataDir folder containment
* fix: bump @xmldom/xmldom to ^0.9.12 (CVE-2026-41675)
0.9.10 has a known parser vulnerability; 0.9.12 is the latest patched release.
* fix: bump dompurify to ^3.4.14
3.4.13 has a known sanitizer bypass; 3.4.14 is the patched release.
* fix: bump ajv to ^8.20.0
8.18.0 has a known prototype-pollution issue in schema compilation; 8.20.0 is the patched release.
* fix: bump es-toolkit to ^1.51.0
1.45.1 has a known ReDoS in one of its string utilities; 1.51.0 is the patched release.
* fix: bump @apidevtools/swagger-parser to 12.1.0
Pulls in json-schema-ref-parser 14.0.1, resolving a known vulnerability in the
$ref-resolution path used when validating imported OpenAPI/Swagger documents.
Only SwaggerParser.validate() is used at the two call sites and its signature
is unchanged across this range.
* fix: bump @sentry/electron to ^7.17.0
Pulls in @sentry/node 10.70.0, resolving vulnerabilities in its bundled
@opentelemetry/core and cjs-module-lexer instrumentation dependencies.
Sentry.init options, captureException, browserTracingIntegration, and
getCurrentScope().setUser() are all used unchanged across this range.
* fix: bump zod to ^4.4.0
3.25.76 has a known validation-bypass fixed only in the v4 line. The app's
own schema code already imports from the zod/v4 subpath, so no source
changes are needed for the bump itself.
* fix: bump @rjsf/core, @rjsf/utils, @rjsf/validator-ajv8 to 6.8.0
Moves off the 6.0.0 beta onto the stable release line, which pulls in
markdown-to-jsx 9.x and resolves a known vulnerability in the pinned 7.x
line. The withTheme/FormProps/RJSFSchema/UiSchema/validateForm API surface
used by this app's JSON-schema forms is unchanged across this range.
* fix: refresh jose lockfile entry to 6.2.10
6.2.2 has a known vulnerability; 6.2.10 is already within the existing
allowed range (^6.1.3) declared by @modelcontextprotocol/sdk, so no
manifest change is needed, just an up-to-date resolution.
* fix: adapt rjsf theme templates to @rjsf/utils 6.8.0 prop changes
WrapIfAdditionalTemplate's onKeyChange/onDropPropertyClick became
onKeyRenameBlur/onRemoveProperty, ObjectFieldTemplate's idSchema/onAddClick
became fieldPathId/onAddProperty, and ArrayFieldTemplate no longer receives
raw item pieces - items are now pre-rendered elements, so the per-item
border/delete-button layout moved into a new ArrayFieldItemTemplate.
* Revert "fix: bump zod to ^4.4.0"
This reverts commit 54f1d5aa4c81ff64bf4cb9f9d478440b7311e2cd.
The v3 to v4 bump breaks real-world collection validation: import-v5-parser's
discriminated/recursive union schemas reject valid v5 collection files under
the released zod@4.4.0 (unlike the bundled zod/v4 preview subpath used
during development), failing import, export, and CLI collection-run
end-to-end tests. Deferring this upgrade separately rather than shipping a
functional regression.
* chore: regenerate lockfile after rebase onto develop
* fix: bump undici to ^7.29.1
* fix: bump @xmldom/xmldom 0.8.x to 0.8.15
* fix: bump proxy-addr to 2.0.8
* fix: bump compression to 1.8.2
* fix: bump source-map-js to 1.2.2
* fix: bump http-cache-semantics to 4.3.0
* fix: bump fast-uri to 3.1.8
* fix: bump proxy-addr, compression, source-map-js, http-cache-semantics, fast-uri in component-docs lockfile
* ci(ksai): start a KSAI run only for people who can write to the repository
A comment or review now starts the KSAI run job only when its author
can write to this repository. A new authorize job reads the author's
permission with the workflow token, and the run job needs it. A
dispatched run starts as before.
Signed-off-by: Bart Smykla <bartek@smykla.com>
* ci(ksai): fail the authorize step when the permission read fails
A failed permission read used to answer writes=false with a green
job, so a writer's comment started nothing and the log said nothing.
The step now fails on a read error, the run job still does not start,
and the log names the error. An author who cannot write still reads
as writes=false.
Signed-off-by: Bart Smykla <bartek@smykla.com>
---------
Signed-off-by: Bart Smykla <bartek@smykla.com>
* unify and migrate environment model using zod and infer type from zod schema
* fix issues from comment
* commit missing parse part
* fix type failures
Background: in the environment variables table view and the generic key-value editor, a space typed within 1s of the previous printable character is swallowed (typing "My key" produces "Mykey"); users had to insert spaces via arrow keys afterwards. Raw JSON editing is unaffected.
The react-aria ListBox typeahead (useTypeSelect) intercepts space keydowns in the capture phase while its search buffer is non-empty. Disable typeahead via disallowTypeAhead on the ListBoxes that embed inline editors (environment key-value editor and the generic key-value editor), remove the now-obsolete space<->NBSP keydown workaround, and add a module augmentation for the prop that is missing from ListBoxProps typings in all published react-aria-components versions.
Add smoke tests typing at human speed (120ms per keystroke) in the environment table, headers, and query params views; zero-delay bursts do not reproduce the bug.
## Background
Toast add/remove animations run through the View Transition API. While such a transition is active, Chromium retargets hit-testing for the whole viewport to `<html>` - including the moment a newer transition skips the running one - so a click can arrive with `document.documentElement` as its target. react-aria's `useInteractOutside` reads such an event as a click outside an open modal and closes it.
Reproduction: open Settings → General → Software Updates and click **Check** repeatedly. Every click fires toasts, every toast mutation starts a view transition, and a click landing in one of those windows closed the Settings modal itself.
## Changes
- `packages/insomnia/src/ui/css/styles.css`: disable pointer events on the `::view-transition` overlay and exclude the root element from the transition, so clicks keep reaching real elements while a toast animates. Both rules are required: the overlay rule alone still leaves the captured root's contents unhittable ([spec rationale](https://drafts.csswg.org/css-view-transitions-1/#view-transition-stacking-layer)), and excluding the root alone still leaves the overlay swallowing the clicks. The same pair is documented for this scenario in [Keeping the page interactive while a View Transition is running](https://www.bram.us/2025/01/29/view-transitions-page-interactivity/) and [csswg-drafts#11596](https://github.com/w3c/csswg-drafts/issues/11596).
- `packages/insomnia/src/ui/components/toast-notification.tsx`: drop `pointerdown`/`click` events that were retargeted to `<html>` while a view transition is active. Those clicks cannot be attributed to their real target (hit-testing was suppressed by the browser) and were being misread as clicks outside an open modal. Events that land on real elements are untouched. Registration is guarded with `typeof window !== 'undefined'` because this module is also evaluated in Node (dev server, prerender).
- `packages/insomnia/src/ui/components/toast-notification.tsx`: remove the stale `[view-transition-name:toast]` utility, which the inline per-toast `view-transition-name` has always overridden.
- `packages/insomnia/src/ui/css/styles.css`: remove the `::view-transition-new(toast)` / `::view-transition-old(toast)` slide rules and their keyframes. They selected a group named `toast`, which never existed because toasts need unique names (a duplicate `view-transition-name` aborts the whole transition), so they never applied.
- `packages/insomnia/src/ui/components/check-for-updates-button.tsx`: the button was fully disabled in development mode, which made this bug impossible to reproduce manually. It now shows a toast instead ("Updates are disabled in development mode"). Production behaviour is unchanged.
## Notes
- Measured with real (CDP/`sendInputEvent`) input against the real components in Electron 43.2.0 / Chromium 150.0.7871.129: without the change a click during a toast animation hits `<html>` and closes the modal; with it the button keeps receiving clicks (25/17/12 clicks handled for 60/90/130ms click intervals) and the modal stays open.
- No regressions measured: clicks while no transition runs (20/20 handled), mask click dismissal, Escape dismissal, popover outside-click dismissal (also while a transition runs), and clicking a toast.
- Residual: hit-testing still returns `<html>` for the fraction of a click that lands inside a transition window (~10% of samples under rage clicking) - that is compositor behaviour and cannot be opted out. Such clicks are now dropped instead of dismissing the modal, which means that one click does nothing.
- `:root { view-transition-name: none }` applies to every view transition in the app; the rule is commented in place so it is revisited if transitions other than toasts are introduced.
Related: https://github.com/Kong/insomnia/pull/10553
## Background
The `value-bearing curl param` smoke test failed intermittently on CI
(run 35832135624, shard 3/6, both retries) while passing on isolated
reruns and in dev mode.
Repro: `npm run test:smoke:build -- --project=Smoke --grep "value-bearing curl param" --repeat-each=20 --workers=4`
→ ~25% first-attempt failure rate.
## Root cause
A value-bearing deep link auto-scans, and the scan result replaces the
pre-scan form within milliseconds of the modal opening. The assertion
`getByText('http://insomnia.rest/')` could only match the transient
pre-scan state — the raw cURL in the textarea's initial text content —
so the test raced against scan completion and lost whenever the scan
landed before the first assertion poll (fast in build mode on CI).
The stable post-scan state never contained the URL text at all: the
scan-results table renders
`{importerSign} resources to be imported from {oriFileName}:`, and the
cURL scan source sets no `oriFileName`, producing the dangling-colon
label "cURL resources to be imported from :".
## Changes
- `import-modal/shared.tsx` — omit the `from {oriFileName}` clause when
a scan result has no source name (cURL deep-link scan). Sources with a
real name (uri, file, clipboard, mcp) keep the existing wording.
- `import-deep-link.test.ts` — assert the stable post-scan state
("cURL resources to be imported:" and "1 Request") instead of the
transient pre-scan form; renamed the test accordingly.
## Verification
- Fixed test: 20/20 passed under the same build-mode + parallel-load
conditions that produced 5/20 flaky before the fix.
- `import deep links` suite: 3/3 passed in build mode.