6 min read

How agent cookie jars keep sessions you did not intend

Agent cookie jars can silently retain HTTP sessions. Learn how to test Set-Cookie persistence, redirects, credential changes, and run boundaries.

How agent cookie jars keep sessions you did not intend

HTTP clients make cookies feel harmless because browsers have trained us to expect them. An autonomous agent changes the consequence. A response that carries Set-Cookie can grant the next request authority that the agent never asked for, never displayed, and may keep after its original credential has changed.

Treat a cookie jar as an authentication store, not as a transport detail. If an API requires cookie sessions, give the jar a named owner, a short lifetime, and an observable boundary. If it does not, turn it off. I have seen too many incident investigations begin with somebody insisting that the request had no credential, while the client quietly sent one in the Cookie header.

A server sends Set-Cookie in a response; a client that owns a jar may send that value in a later Cookie header. When the value identifies a server-side session, it is a credential in practical terms. The fact that it arrived after an authenticated request does not make it less capable of authorizing the next one.

This catches teams because they focus on the secret they deliberately supplied: a bearer token, a basic-auth password, or a signed request. They review where that secret comes from and whether it reaches the agent. Then an HTTP library accepts a session cookie without any explicit line of application code. The next request may work after the original Authorization header disappears.

The distinction that matters is between credential injection and session continuation. Credential injection attaches a known secret to one request. Session continuation lets the server issue a new secret and asks the client to replay it later. Both can authorize work. Only one is usually visible in a tool's call arguments.

RFC 6265 describes this exchange as state management: the user agent stores cookie information from Set-Cookie and returns applicable cookies in Cookie. That wording is deliberately broad because web browsers need it. An agent client should not inherit the browser's habit of keeping state simply because the HTTP protocol permits it.

A cookie also changes the meaning of a failed token rotation. Suppose an agent calls an API with a token, receives sid=..., and later loses access to that token. If the API accepts the session cookie on its own, the agent still has a path to the account. The rotation fixed the known credential but did not terminate the issued session. That is a server-side session management question and a client-side containment question. You need to handle both.

The client decides whether hidden state exists

Set-Cookie does nothing by itself. A client must choose to retain it. That choice can hide in a surprising number of places: a reusable HTTP client, a library's cookie manager, a wrapper around fetch, a test harness, or a redirect implementation.

Some common clients do not retain cookies unless you give them a jar. Others retain them when code attaches a standard cookie handler. A shared client can then make a jar shared by accident. Do not infer behavior from the API documentation or from the fact that a call succeeded. Inspect the client construction and run a probe.

A clean test uses a server you control or a harmless test endpoint. The first response should set a clearly named cookie, and the second request should report the headers it received. The result needs to answer two separate questions:

  1. Did the client retain the cookie after receiving Set-Cookie?
  2. Did the client send that cookie on a later eligible request?

With curl, an explicit cookie file makes the state visible:

curl -i -c /tmp/agent-cookie-test.txt https://test.example/session/start
HTTP/1.1 200 OK
Set-Cookie: agent_probe=run-7f3; Path=/; Secure; HttpOnly

curl -i -b /tmp/agent-cookie-test.txt https://test.example/session/echo
HTTP/1.1 200 OK

{"received_cookie":"agent_probe=run-7f3"}

The file is the point of the exercise. The first command cannot create a hidden session unless something retains its response. The second command cannot replay one unless it receives the file. If your agent tooling gives the equivalent of that file an unnamed process-global home, you have created a session boundary that nobody can reason about during review.

Repeat the probe using the exact runtime path your agent uses, including its request wrapper and redirect settings. A direct curl test proves curl, not your tool. Record whether the jar begins empty, where it lives, and what clears it.

Reuse across calls is different from reuse across runs

A few calls inside one task may need to share a session. Reusing that session in a later task is a separate decision. Combining those two lifetimes is how a small convenience turns into persistent authority.

Use three boundaries when you assess behavior. First, ask whether a single request needs cookies at all. Second, ask whether related calls in one agent process need them. Third, ask whether a fresh process, a resumed task, another credential, or another human should ever inherit them. Each yes needs a stated reason.

An in-memory jar that disappears with a process is easier to contain than a file in a project directory. A file can survive a crash, a retry, a copied workspace, or a change in who runs the task. It can also end up in a support archive or source-control status output. HttpOnly does not protect a cookie file from the client that wrote it; it only limits access from browser script.

I prefer a fresh jar for each agent run even when the same model receives a follow-up prompt. The model's conversational continuity is not a reason to retain HTTP authority. If the follow-up genuinely needs the session, make the operator approve a continued run under the same named boundary, rather than silently letting the last run leave a usable session behind.

Concurrent calls need their own decision too. A shared jar can create order-dependent behavior: request A receives a cookie, request B starts moments later, and B gains a session it never established. That makes reproduction miserable. Give concurrent tasks separate jars unless the API requires a coordinated session and the task explicitly owns it.

Cookie attributes limit delivery, but they do not make a session harmless. Read them as routing instructions from the server to the client, then decide whether your client should honor them at all.

A host-only cookie returns to the exact host that issued it. A cookie with Domain=example.com can return to eligible subdomains such as api.example.com and admin.example.com. RFC 6265 also says a user agent rejects a Domain value that does not domain-match the origin host, but that does not solve the ordinary mistake of trusting every subdomain under a broad corporate domain.

Path=/billing limits a cookie to requests whose paths match the cookie path. It does not stop a server at another permitted path from accepting the same cookie if it receives it through some other route, and it does not replace authorization checks. Do not use Path as a security boundary in design discussions. It is a client sending rule.

Secure tells the client to send the cookie only over secure transport. HttpOnly tells a browser not to expose it through script APIs. Both attributes are good hygiene, but neither limits retention, task sharing, redirects, or the agent's ability to use the cookie. SameSite mainly governs browser site context; a non-browser agent should not use it as proof that cross-site behavior is safe.

There is another easy mistake: logging raw headers during debugging. A redactor that removes Authorization but leaves Cookie has not redacted authentication. Log the presence of a cookie, its name, declared Domain and Path, expiration type, and a nonreversible correlation identifier if you need one. Do not log its value.

Revoke the responsible run
Sessions journal records the agent run that made HTTP calls and lets you revoke that run instantly.

A redirect is not merely a different URL. It can change which server sees the next request, whether credentials are retained, and which response gets to set state. Test it as its own flow.

Start with an endpoint that returns a redirect after setting a cookie. Then test each destination separately: the same host, a permitted subdomain, a sibling subdomain, and an unrelated host. Watch both outgoing Cookie and outgoing Authorization. Different HTTP stacks make different choices, and a wrapper may override the defaults.

A useful fixture produces a short trace such as this:

request 1  GET https://api.example.test/start
response 1  302 Location: https://api.example.test/next
            Set-Cookie: probe=A; Path=/; Secure
request 2  GET https://api.example.test/next
            Cookie: probe=A
response 2  200

Then change only the Location host. If api.example.test redirects to reports.example.test, a host-only cookie should not follow. A Domain-scoped cookie may. Your test should state the expected result before it runs, because a trace that surprises you is the point, not an inconvenience to paper over.

Reject cross-origin redirects when the API contract does not require them. If you must follow them, compare the old and new origins, strip request credentials according to an explicit rule, and let a fresh response establish any new cookie state. Never use a broad automatic redirect policy and assume that cookie scope will save you.

A session test must cross credentials and processes

The test that catches the expensive bug is not request one, request two. It crosses the boundaries your operating model claims to enforce.

Build a harmless endpoint that issues a cookie only after it receives a chosen credential label. Have it return the session label on every authorized request. Then run this sequence:

  1. Start run A with credential A and receive sid=A.
  2. Make a second call without credential A but with the same jar.
  3. Start run B with credential B and an empty jar.
  4. Start run C with no credential and any persisted jar from run A.
  5. Revoke credential A or invalidate its session on the test server, then retry run A's jar.

The expected output is a policy decision, not a universal answer. A cookie-based API may intentionally allow the second call within run A. Run B should not see A's state. Run C should fail unless you explicitly approved persistence. After revocation, the test server should reject the old session if your threat model says token rotation must remove active access.

Write the expectation beside the test rather than burying it in a developer's memory. A concise table in a test comment works:

run A, same jar, no bearer header: allowed only if session continuation is intended
run B, fresh jar, credential B: must identify as B
run C, persisted jar, no credential: rejected
run A after session invalidation: rejected

This exposes a distinction teams often blur: revoking a bootstrap credential and revoking issued sessions are not the same operation. The API owner has to invalidate sessions. The agent tool owner has to avoid preserving them beyond their approved lifetime. Neither side can assume the other handled it.

Disable implicit jars unless the API needs one

Lock the credential vault
The vault stays locked behind Secure Enclave and Touch ID, denying all actions until it opens.

The sensible default for an agent HTTP action is no cookie storage and no automatic Cookie header. A response may still contain Set-Cookie; record that it happened if your audit design permits, then discard it. The API should use its normal request credential on the next call.

This recommendation is unpopular because many web-adjacent APIs work after a login endpoint only when the client keeps a cookie. People reach for a shared jar because it makes demos and integration tests pass. It is the wrong fix when the documented API supports bearer tokens or another request-scoped mechanism. You are preserving invisible authority to compensate for an integration path you should not have chosen.

If the API truly requires cookies, make the jar an explicit capability. The caller should select a named, empty jar at run start, the tool should report when the response creates or replaces a cookie, and the jar should disappear at the end. Do not let an endpoint opt every agent into a durable session merely by returning a header.

A minimal policy can be plain enough to review:

cookie mode: disabled by default
allowed mode: ephemeral per agent run
persistence: prohibited
sharing: prohibited between agent processes
redirects: same-origin only unless the action definition permits another origin
logging: record cookie names and scope, never values

That is deliberately less clever than a rules engine. Clever cookie policy grows exceptions until nobody can say which action carries what state. A small set of choices gives reviewers a real answer.

Audit the state transition, not only the request

An audit trail that lists URLs and status codes will miss the most useful event: a response changed what later calls can do. Capture cookie state changes as first-class events without retaining the session secret itself.

For every HTTP action, the record should make it possible to answer: did the request send cookies, did the response set or clear any, which jar received them, did the jar belong to this run, and did a redirect occur? Cookie names and attributes are usually enough for diagnosis. Store values only if you have a compelling security design for protecting and expiring them, which most agent tools do not need.

When an agent uses Sallyport for HTTP actions, the vault can keep the configured API credential out of the agent while the action executes. That separation is useful only if the HTTP client treats any returned session state with the same suspicion instead of quietly turning it into another credential path.

Audit records also need a human-readable run boundary. If an operator revokes an agent run, they should know whether that act prevents future calls merely through the configured credential or also clears the session state attached to the run. If it does not clear it, say so plainly and make the remaining state inaccessible to a later process.

The first test I would add is intentionally boring: one endpoint sends Set-Cookie, the next confirms whether it arrived, and a new agent process repeats the call. Run it before you add retries, redirects, browser compatibility, or a persistent cache. If the answer is not obvious from the trace, your client has more authority than its interface admits.

API authors can make agents safer without guessing

Keep HTTP authority contained
Sallyport executes HTTP API calls itself, keeping the configured secret outside the agent's prompt and tools.

API authors should document whether cookies are required, what creates them, their intended lifetime, and how clients invalidate them. A sentence that says "use this endpoint after login" is not enough when login may issue a session that outlives the credential used to obtain it.

Offer a request-scoped alternative when possible. Bearer authentication, signed requests, or a narrowly scoped action token often make client behavior easier to audit because each call carries its authority openly. That does not make those mechanisms automatically safe, but it avoids an extra replay channel hidden in client memory.

If you issue a session cookie, make session invalidation work and test it. Token rotation without session invalidation leaves operators with an unpleasant false sense of completion. If you accept both a bearer token and a session cookie, define which one wins when they disagree and expose that choice in diagnostic output.

Do not tell agent builders to imitate browsers unless your service truly depends on browser behavior. Agents make repeated, unattended calls and often operate across different tasks. A browser's long-lived convenience state is a poor default for that environment.

The safe default is a fresh client with no remembered session

Cookie support is not bad; unowned state is. A short-lived, explicitly selected jar can be the correct way to finish a multi-call API task. A jar that appears because somebody reused an HTTP client is an accident waiting for the right retry, redirect, or credential rotation.

Make the session boundary visible in the tool interface and in the audit record. Then prove it with the cross-run test. The request trace should make a reviewer able to point to every credential path the agent had, including the ones the server tried to hand back.

FAQ

What is a cookie jar in an HTTP client?

A cookie jar is the client-side store that remembers cookies received in HTTP responses and decides which later requests should send them back. In an agent tool, that memory can turn separate calls into one authenticated browser-like session.

Should an AI agent keep cookies between API calls?

It can be, if the API uses cookies as its intended session mechanism and the run needs several related calls. It is a bad default when the agent has bearer tokens, signed requests, or separate task boundaries, because the cookie creates authority that is hard to see in the prompt.

Can a session cookie survive credential rotation?

A cookie can outlive the credential that caused the server to issue it. If the server accepts the session cookie without rechecking the bearer token, removing or rotating that token does not end the already-issued session.

Do HTTP clients store Set-Cookie headers automatically?

Do not assume so. Test it with an endpoint that records the incoming Cookie header, because clients vary: some retain only an explicit jar, while others add a cookie store through a shared client, wrapper, or redirect handler.

How long should an agent cookie session last?

The safest boundary is one fresh cookie jar per agent process or per explicitly defined task. Clear it at the end, do not write it to a reusable workspace, and require a deliberate decision before sharing it with another run.

What do Domain and Path mean on an HTTP cookie?

A host-only cookie returns only to the host that set it, while a Domain attribute can make it available to subdomains. The Path attribute narrows where the client sends it, but it is a routing rule, not an access-control boundary.

Does the Secure cookie attribute make agent sessions safe?

No. Secure means the client should send the cookie only over HTTPS; it says nothing about whether an agent should retain it, share it, or treat it as a substitute for another credential.

Can redirects cause cookie leakage?

Redirects can carry a client to a host that sets a cookie, or to a host that receives one, depending on the jar and the cookie scope. Test redirects separately and reject cross-origin redirects when the API does not need them.

What should I log about cookies without leaking secrets?

Record the request destination, the fact that cookies were sent or received, the cookie names, their scope, and the jar identity or run boundary. Avoid logging cookie values because they often are the session credential.

How do I prevent hidden sessions in agent HTTP tools?

Keep the regular credential injection and a cookie jar separate in both code and review. Give the cookie jar a short lifetime, make its sharing rule explicit, and clear it when the run ends; treating it as ordinary HTTP convenience hides an authentication decision.

Sallyport

Sallyport runs API calls and SSH commands for your AI agent. The keys stay in a local vault on your Mac; you approve each run and every action lands in a sealed journal.

© 2026 Sallyport · Open source under Apache-2.0 · Oleg Sotnikov