Agent-First HTTP v0.15.0: A Cookie Knows Its Host
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 knows its host
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:
- network entries and console events: at most 4,096 each, with a slot reserved for the main document, and existing entries still receiving lifecycle updates once the limit is reached;
- WebSocket and SSE: at most 4,096 requests per stream kind and 1,024 frames or events per request.
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
- Network and console capture is capped, and stream capture is capped per
request. Read
truncatedandevents_droppedbefore treating counts as complete. - A known-oversized network body is skipped instead of fetched and truncated.
- Whitespace-only host tokens are rejected.
- Agent-First UI 0.6.1 and Agent-First Data 0.35.0 are the pinned dependencies.
Getting it
$ brew install agentfirstkit/tap/afhttp
$ cargo install agent-first-http