Skip to content

fix(scripting): wire bru.sendRequest() into the shared cookie jar#7518

Open
mareczek wants to merge 3 commits intousebruno:mainfrom
mareczek:fix/send-request-cookie-jar
Open

fix(scripting): wire bru.sendRequest() into the shared cookie jar#7518
mareczek wants to merge 3 commits intousebruno:mainfrom
mareczek:fix/send-request-cookie-jar

Conversation

@mareczek
Copy link
Copy Markdown

@mareczek mareczek commented Mar 18, 2026

Problem

bru.sendRequest() was completely disconnected from Bruno's global cookie store. This caused two silent failures:

  1. Cookies set by earlier requests were never forwarded — session cookies, authentication tokens, and any cookie stored by a previous response were invisible to bru.sendRequest().

  2. Set-Cookie headers in the scripting response were discarded — the most common case being a CSRF token returned as a cookie from a preflight GET request in a pre-request script. The token was silently dropped, so the subsequent main request never received it.

This is a direct regression from Postman's pm.sendRequest(), which automatically participates in Postman's cookie jar. Users migrating from Postman expect the same behaviour from bru.sendRequest().

Related issues: #1394, #1493, #5419

Root cause

The main Bruno network layer (bruno-electron/src/ipc/network/index.js) explicitly manages cookies around every request:

// Before sending:
const cookieString = getCookieStringForUrl(request.url);

// After receiving:
saveCookies(request.url, response.headers);

bru.sendRequest() in bruno-requests/src/scripting/send-request.ts called makeAxiosInstance() with no such hooks, so the shared cookieJar (backed by tough-cookie) was never read from or written to during scripting requests.

Fix

Added attachCookieInterceptors() — a small helper that registers two Axios interceptors on the instance used by bru.sendRequest():

Interceptor What it does
Request Calls getCookieStringForUrl(url) and injects any matching cookies into the outgoing Cookie header, merging with cookies the caller set explicitly.
Response (success + error) Calls saveCookies(url, headers) to persist any Set-Cookie headers back into the shared jar — including on 4xx/5xx responses, since servers often refresh CSRF tokens even on error.

Both interceptors swallow their own exceptions so a cookie-jar failure can never abort a user's request.

Before

pre-request script
  └─ bru.sendRequest('GET /driver_api')
       └─ axios (no cookie hooks)
            └─ response: Set-Cookie: CSRF-TOKEN=xyz   ← DISCARDED

main request (POST)
  └─ getCookieStringForUrl() → ''   ← jar is empty, no CSRF token sent
       └─ server returns 422 / CSRF error

After

pre-request script
  └─ bru.sendRequest('GET /driver_api')
       └─ axios + cookie interceptors
            └─ response: Set-Cookie: CSRF-TOKEN=xyz   ← saveCookies() called

main request (POST)
  └─ getCookieStringForUrl() → 'CSRF-TOKEN=xyz'   ← injected automatically
       └─ server accepts request ✓

Changes

File Change
packages/bruno-requests/src/scripting/send-request.ts Add attachCookieInterceptors() and call it inside createSendRequest()
packages/bruno-requests/src/scripting/send-request.spec.ts Add cookie jar integration test suite (7 new tests)

Testing

All 19 tests pass (7 new + 12 existing):

npm test -- --testPathPattern=send-request

Tests: 19 passed, 19 total
Time:  0.256s

New tests cover:

  • Request interceptor injects stored cookies into Cookie header
  • Request interceptor merges with cookies already set on the request
  • Request interceptor does nothing when jar has no cookies for URL
  • Response interceptor saves Set-Cookie headers on success
  • Response interceptor saves Set-Cookie headers on HTTP error responses
  • Full end-to-end CSRF flow (bru.sendRequestsaveCookiesgetCookieStringForUrl)
  • Interceptor errors are swallowed so the request still proceeds

Migration / backwards compatibility

No breaking changes. The cookie injection only adds a Cookie header when the jar actually contains matching cookies. Requests to domains with no stored cookies are completely unaffected.

Contribution Checklist

Summary by CodeRabbit

  • New Features

    • Automatic cookie management: cookies are read from the jar and injected into requests; Set-Cookie headers from responses are saved for subsequent requests.
    • Interceptors attached to request flow with robust error handling so cookie operations don't disrupt requests.
  • Tests

    • Extensive integration tests covering cookie injection, merging, no-cookie cases, Set-Cookie handling on success and error, end-to-end cookie flow, interceptor error resilience, and CSRF flow verification.

bru.sendRequest() created a bare axios instance with no cookie hooks,
so Set-Cookie headers from scripting requests were silently discarded
and stored cookies were never forwarded.

Add attachCookieInterceptors() with two axios interceptors:
- request: injects stored cookies from the global jar
- response: persists Set-Cookie headers back into the jar (including on errors)

Fixes usebruno#1394 usebruno#1493 usebruno#5419
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 18, 2026

Walkthrough

Adds Axios interceptors to integrate a cookie jar into the send-request flow: request interceptor injects jar cookies into outgoing requests; response and error interceptors persist Set-Cookie headers back to the jar. Tests cover injection, merging, persistence, and error resilience.

Changes

Cohort / File(s) Summary
Cookie Interceptor Implementation
packages/bruno-requests/src/scripting/send-request.ts
Adds attachCookieInterceptors and invokes it for every Axios instance. Request interceptor reads cookies via getCookieStringForUrl and merges into Cookie header; response/error interceptors call saveCookies with Set-Cookie values. Errors in cookie handling are swallowed to avoid aborting requests.
Cookie Integration Tests
packages/bruno-requests/src/scripting/send-request.spec.ts
Introduces extensive tests and mocks for cookie-jar behavior and Axios interceptors: cookie injection, merging with existing headers, no-cookie behavior, saving Set-Cookie on success and error, end-to-end persistence across requests, and interceptor error handling.

Sequence Diagram

sequenceDiagram
    participant Client as Client/Application
    participant Axios as Axios Instance
    participant CookieJar as Cookie Jar
    participant Server as Server/Response

    Client->>Axios: sendRequest(url, config)
    Axios->>CookieJar: getCookieStringForUrl(fullUrl)
    CookieJar-->>Axios: cookieString
    Axios->>Axios: merge cookieString into Cookie header
    Axios->>Server: HTTP Request (with Cookie header)

    alt Success Response
        Server-->>Axios: Response (may include Set-Cookie)
        Axios->>CookieJar: saveCookies(fullUrl, setCookieHeaders)
        CookieJar-->>Axios: saved
    else Error Response
        Server-->>Axios: Error response (may include Set-Cookie)
        Axios->>CookieJar: saveCookies(fullUrl, setCookieHeaders)
        CookieJar-->>Axios: saved
    end

    Axios-->>Client: Response or propagated Error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

size/M

Suggested reviewers

  • lohit-bruno
  • naman-bruno
  • bijin-bruno
  • helloanoop

Poem

🍪 Tiny crumbs in headers flow,
From jar to request and back they go,
Interceptors hum, persistence true,
Errors handled, cookies, too.
Network baked, and tests say whoo! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: wiring the bru.sendRequest() function into the shared cookie jar by adding cookie interceptors.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can generate a title for your PR based on the changes.

Add @coderabbitai placeholder anywhere in the title of your PR and CodeRabbit will replace it with a title based on the changes in the PR. You can change the placeholder by changing the reviews.auto_title_placeholder setting.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bruno-requests/src/scripting/send-request.ts`:
- Around line 56-63: The error handler in send-request.ts currently reads the
raw error?.config?.url before saving cookies; update it to resolve the final
request URL the same way you handled the success path (i.e., account for
config.baseURL + config.url) before calling saveCookies. Locate the error
callback (the arrow function using error, referencing error?.config?.url and
saveCookies) and replace the direct access with the same URL resolution logic
used elsewhere in this module so you pass the fully resolved URL and headers to
saveCookies.
- Around line 51-52: The response interceptor and error handler use
response.config?.url without resolving against response.config?.baseURL, causing
saveCookies(url, ...) to receive relative URLs; update both places to resolve
the final URL the same way the request interceptor does (combine
response.config.url with response.config.baseURL, falling back to
response.request?.responseURL if available) before calling saveCookies,
referencing the response interceptor, error handler, response.config,
config.baseURL, and saveCookies so the saved cookie domain always uses the
fully-resolved URL.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0fb18281-1a1b-43b3-810b-89266c55f45d

📥 Commits

Reviewing files that changed from the base of the PR and between 37be721 and 148e6f1.

📒 Files selected for processing (2)
  • packages/bruno-requests/src/scripting/send-request.spec.ts
  • packages/bruno-requests/src/scripting/send-request.ts

Comment thread packages/bruno-requests/src/scripting/send-request.ts Outdated
Comment thread packages/bruno-requests/src/scripting/send-request.ts
…docstrings

Address CodeRabbit review comments:
- attachCookieInterceptors: resolve baseURL+url in response success handler
- attachCookieInterceptors: resolve baseURL+url in response error handler
- Add JSDoc to attachCookieInterceptors and reposition createSendRequest JSDoc
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/bruno-requests/src/scripting/send-request.ts (1)

46-49: Prefer name-aware merge for Cookie header values.

At Line 46, direct string concatenation can emit duplicate cookie names when both caller headers and jar contain the same key, which can make precedence backend-dependent. A key-aware merge (caller value wins) would make behavior deterministic.

Possible merge approach
-          const merged = existing ? `${existing}; ${cookieString}` : cookieString;
+          const splitCookies = (header: string) => header
+            .split(';')
+            .map((part) => part.trim())
+            .filter(Boolean);
+          const toName = (cookiePart: string) => cookiePart.split('=')[0]?.trim().toLowerCase();
+          const existingParts = existing ? splitCookies(existing) : [];
+          const existingNames = new Set(existingParts.map((part) => toName(part)).filter(Boolean));
+          const jarParts = splitCookies(cookieString).filter((part) => !existingNames.has(toName(part)));
+          const merged = [...existingParts, ...jarParts].join('; ');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-requests/src/scripting/send-request.ts` around lines 46 - 49,
The current concat logic that builds merged from existing and cookieString can
produce duplicate cookie names; update the merge to be name-aware: parse
existing and cookieString into individual cookie name=value pairs, build a map
where values from the existing header (caller) take precedence over the jar
cookies, then reserialize the map back into a single cookie header string and
set it via config.headers.set('Cookie', ...). Locate the variables merged,
existing, cookieString and the config.headers.set call in send-request.ts and
replace the concatenation with this deterministic name-aware merge.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/bruno-requests/src/scripting/send-request.ts`:
- Around line 46-49: The current concat logic that builds merged from existing
and cookieString can produce duplicate cookie names; update the merge to be
name-aware: parse existing and cookieString into individual cookie name=value
pairs, build a map where values from the existing header (caller) take
precedence over the jar cookies, then reserialize the map back into a single
cookie header string and set it via config.headers.set('Cookie', ...). Locate
the variables merged, existing, cookieString and the config.headers.set call in
send-request.ts and replace the concatenation with this deterministic name-aware
merge.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: edb7d472-38ab-4d43-a3b9-10374f99b51a

📥 Commits

Reviewing files that changed from the base of the PR and between 148e6f1 and db6aec1.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • packages/bruno-requests/src/scripting/send-request.ts

…ose of lowering the automatically assigned tag: size/L because this fix is TINY
@mareczek
Copy link
Copy Markdown
Author

@helloanoop , @lohit-bruno , @naman-bruno , @bijin-bruno let's get a move on it! :)

@ThenTech
Copy link
Copy Markdown

Ran into this issue today as well, that I am trying to refresh an access token in a cookie with a pre request script, but the refresh cookie is not sent to the server, and the response probably won't set it to the cookie store either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants