Agent-First HTTP v0.15.0: A Cookie Knows Its Host

by Agent-First Kit Contributors

A host-only cookie read back from the browser came out as a domain cookie, and a deleted cookie could come back from the jar on disk. Cookies keep their scope now, deletions stay deleted, and redirects carry them hop by hop. Plus captures with fixed limits, credentials kept out of error messages, readiness that survives lost events, and a host that cleans up after peers that never answer.

A profile’s cookie jar is afhttp’s memory of a site between fetches. This release is mostly about that memory being exact — the right cookie, for the right host, gone when the site says it is gone — and about every capture a fetch makes having an upper bound.

A cookie set without a Domain attribute is host-only: it goes back to example.test and nowhere else. CDP writes a domain cookie with a leading dot and a host-only one without. Reading cookies back from the browser into the jar lost that distinction, so a host-only cookie came out as a domain cookie and was sent to sub.example.test too. The scope is preserved now, for named hosts, localhost and IP addresses alike.

Deletion had its own gap. A site deletes a cookie by sending it again already expired. The jar merged what the fetch saw with what was on disk, and an expired replacement was dropped before the merge — so the older copy on disk won, and the deleted cookie came back. Expired replacements now reach the merge and remove the old identity, and Max-Age takes precedence over Expires, including Max-Age=0.

Plain HTTP fetches with a cookie jar follow redirects hop by hop, so a cookie set by a login response is stored and sent to the page it redirects to. Method and credential rules are preserved across hops, a loop stops after ten, and a caller-supplied cookie that does not match a hop’s host is skipped for that hop rather than failing the whole fetch.

Every capture has a limit

A fetch records network entries, console events, WebSocket frames and SSE events. None of those had a ceiling, so a page that logs in a loop or streams forever grew a fetch’s memory with it. Now:

When a limit is reached, the summary says truncated: true. When CDP drops events because the collector fell behind, it says how many in events_dropped. Collectors used to stop on that kind of loss; they count it and keep going, and they stop when the fetch that owns them is dropped.

A response body whose size is already known — from encodedDataLength or Content-Length — and is above --network-body-max-bytes is no longer fetched from the browser just to be cut down. It is skipped with network_body_truncated. Unknown sizes still use the decoded-prefix limit.

Readiness after a lost event

--wait auto decides a page is ready partly from network quiet. The first version of the fix above made event loss pin network_quiet to false, on the reasoning that a collector which had missed events could not claim nothing was pending. In practice that made every lossy fetch run to its deadline.

Every Network.* event still marks activity even when its request was not retained, so the idle window remains a sound signal on its own. After loss or a capture limit, network_quiet means “no network event for the idle window” rather than “no pending request”, and the loss itself is reported through events_dropped and warnings.

Credentials stay out of errors

An error for an unreachable or malformed target quoted the URL, and with it any user:password@ userinfo or credential query parameter. The CDP endpoint, fetch errors and every output format now redact through the same URL policy used for normal output: userinfo, *_secret parameters and the existing token and API-key names.

Whitespace-only host tokens are refused by the CLI, the container entrypoint and the listener itself, instead of being accepted as a token anyone can send.

Cleaning up after peers that never answer

Several shutdown paths waited on the other side. A CDP peer that never answers a Close frame kept the socket alive. Closing or detaching a tab after a failed setup step could wait forever; it has a two-second limit now, and a target created for a fetch is closed when enabling its page fails. A download that never finishes uses what is left of the fetch’s own deadline, and a partial download is never reported as a completed artifact.

On the host, accepted connections belong to the listener’s scope, so stopping the listener closes idle connections instead of leaving them detached. The browser is stopped before its temporary profile is deleted or its persistent profile lock released.

Smaller things

The docs say what upload actually sends — a path, which must already exist in the browser’s filesystem, since the default container shares no directory with the driver — and no longer claim cdp, upload and tabs require an endpoint. The README counts twelve commands, including ui.

Camoufox is pinned to 156 and Fingerprint Chromium to 150.0.7871.186. The test docs are honest about optional backends: a missing backend binary makes its tests return early, so a green gate does not mean that backend ran.

Breaking changes

Getting it

$ brew install agentfirstkit/tap/afhttp
$ cargo install agent-first-http