All notable changes to this maintained fork are documented here. The format is based on Keep a Changelog, and the project follows Semantic Versioning.
- Hosted claimed-browser cookie seeding no longer loses a run to a single stalled attempt. A branded fork that accepts the DevTools connection but never navigates now gets a bounded number of fresh corpus targets against the same launch root, a partially seeded corpus still fails immediately rather than restarting an interleaving redirect chain, the corpus poll window is overridable per browser, the post-shutdown cookie-flush wait spans bounded logged windows, and each hosted-CI launch first reaps leftover processes of its own launch root and product so a stale vendor service cannot contend with it.
- Linux Chromium v11 cookies now fall back to the empty-password key when the
OS keyring yields no usable password. The keyring password, when there is
one, is still tried first; the empty-password key is appended as a last
candidate so profiles Chromium sealed with its own
BASICTEXTfallback (no reachable Secret Service or KWallet) and profiles whose keyring entry is stale are decryptable instead of failing the whole v11 tier. A value that the fallback cannot open is still reported with the keyring diagnostic and its retryability, so the fallback adds decryptions without hiding why the keyring lookup failed.
- The
flat_onlyvalue for a browser registry root'slegacy_profile_layoutis retired. Opera was its only declarant, and its refusal to fall back to a siblingDefaultprofile was the discovery defect fixed in 0.6.0; sinceflat_and_defaultbehaves identically wherever noDefaultdirectory exists, nothing is left for the name to describe.browser_registry.jsonnow fails to load if any root declares it, namingflat_and_defaultas the replacement rather than silently falling back to the default layout.
- Python binding coverage is now measured and ratcheted. A pull-request job
instruments the extension, runs the installed-wheel suite against it, and
holds every
bindings/python/src/*.rsfile plus__init__.pyanddto.pyto per-file line and branch floors; the nightly schedule repeats it on Linux, macOS, and Windows. - Every public Python export is now held to a declarative contract covering its platform availability, stub presence, parameter shape and defaults, one success path, and one classified-error path.
- Runtime/stub compatibility is now checked against the installed wheel with
mypy.stubtestand a type-consumer fixture, against a committed allowlist of the documented divergences. - Each browser-specific convenience function now receives an exact-corpus
assertion on the platforms where it exists, or a documented exception in
tests/e2e/browser_coverage.json.
- The CLI documentation now defines its stderr contract: typed library errors
emit JSON with stable
codeand humanmessagefields, while usage, wrapped, and non-library errors retain human-readable output. - The Node binding build now uses napi-rs v3 while preserving the existing runtime exports, TypeScript surface, Node-API v4 baseline, and Node.js 22 minimum.
- Real-browser validation now compares complete controlled cookie corpora across Rust, Python, Node, and CLI surfaces, including active stores, partition/container isolation, and concurrent stress cases.
- Opera and Opera GX compatibility helpers on macOS and Windows now fall back
to a Chromium-style
Defaultprofile after checking the historical flat profile, and source absence no longer appears as a failed-enumeration error with an empty diagnostic. - Browser profile listing now reports a timeout or cancellation as an error instead of returning a partial Gecko, Safari, or Internet Explorer listing that appears complete.
- Node structured errors retain their native diagnostic when a decoded payload
omits
message, and the serialization fallback always emits valid JSON. - Firefox's stored SameSite sentinel
256is normalized to the public unspecified value instead of being exposed as an unknown storage value. - SQLite live-store lock polling now observes request deadlines and cancellation rather than entering one opaque busy wait.
- WAL snapshots now verify the sidecar and recheck the main database before use, preventing a concurrent Firefox write or checkpoint from yielding a stale but otherwise readable cookie snapshot.
- Release-control checks no longer let a newer, intentionally skipped copy of a dispatch-only gate mask the successful exact-commit release run.
- The Python type stub no longer leaves
jar()andReadResult.as_jar()returning an unresolvable type, so consumers inferhttp.cookiejar.CookieJarrather thanAny. - Platform-conditional Python exports are now hidden from type checkers on
platforms that do not provide them. Calling
cachy()on macOS,safari()on Linux, orinternet_explorer()anywhere but Windows is now a type error instead of type-checking clean and failing at runtime.
- crates.io publication now uses its GitHub OIDC trusted publisher instead of a stored registry token, and PyPI publication can recover an interrupted run by reconciling the original digest-verified distributions.
- Release manifests are now recomputed before every consumer-harness and CI proof check, and release preparation rejects an empty changelog section.
- The Rust workspace now declares and continuously tests Rust 1.88 as its MSRV; the published crate also declares crates.io categories.
- Windows ESET scan records remain available for incident investigation but are no longer described as a release gate.
- Rust and Node now expose
jaralongside Python. It is projection sugar forread: Rust returnsVec<Cookie>, Node resolves toCookieObject[], and both discard snapshot warnings and isolation context just as Python'sread(...).as_jar()projection does. - Three path request types become two.
DirectPathRequestandChromiumPathRequestare replaced byPathExtractRequest+extract_from_path;DirectPathRequestwasChromiumPathRequestminus credentials minus the locked-database policy, so the pair was one type split by whether the caller happened to know the file was Chromium. Constructors:plaintext,sniff, and the platform-gatedunix_identity(Unix) /windows_local_state(Windows).ChromiumCredentialSource::Automaticis gone: it was the default and it could never succeed on Windows. Isolation-carrying path output now comes fromfrom_path(..).detailed_cookies(), so the Rust free function backingchromium_cookies_from_path_detailedis gone too — a real narrowing at that layer, since a domain-filtered detailed path list is no longer expressible through it. Python keeps the function (now backed byfrom_path(..).into_detailed_cookies()) but drops itsdomainsoption: passingdomainsraisesRookieRequestErrorrather than the binding reimplementing the core'spub(crate)matching rule to fake the filter back. Python'sfrom_path()gainsplaintext_only/browser_id/local_state_pathkeyword arguments (mutually exclusive, validated with the now-wired-upconflicting_credential_selectorscode before any I/O) for the same credential selectionchromium_cookies_from_path's options dict already offered. - Sniffing a Chromium database is plaintext-capable only; an encrypted row is
the new
missing_chromium_credentials. On Unix that is a narrowing (the ordered identity probe is gone). On Windows it is a widening: the old call returnedmissing_local_state_filebefore attempting extraction, so even a fully plaintext database failed. ProfileSelectionandReportScopemake an illegal selection unrepresentable.Requestis renamedExtractRequest(prerelease-only) and joined byReportRequest, whose scope may widen to every profile because only a report can describe more than one.ExtractRequest::browserselects the first legacy-eligible profile;ReportRequest::browserreports every profile, matching v0.5.9'sbrowser_report(id, None, ..).From<ExtractRequest> for ReportRequestnarrows, never widens.ReadResult::headertakes aSendContextinstead of a bare URL, and is send-safe: it never merges two isolated browsing contexts. A snapshot holding a partitioned or containered cookie demands the selector that identifies it and raisesRequestError::IncompleteSendContext(with a stablerequiredtoken list) rather than guessing. NewSendContext,ResourceKind, andMethodClass;SameSiteis now applied, withSitedefined as (scheme, host). FirefoxpartitionKeytuples and Chromiumtop_frame_site_keyvalues are normalized into that one space, so a Firefox dFPI cookie is matched rather than silently absent from every header. New warningunparsable_partition_keyfor a key neither parser understands — such a row is a non-match everywhere, never treated as unpartitioned.ReadResultis isolation-aware on both source axes. Its native representation isDetailedCookie, so a CHIPS partition key or a Firefox container survives toheader(). New:detailed_cookies(),into_detailed_cookies(),common::format::detailed_json, and CLI--format detailed.cookies()keeps its&[Cookie]signature, backed by a projection built once at construction.CookiegainsClone/PartialEq/Eq/Hash;DetailedCookiegainsClone/PartialEq/Eq.SessionPolicyandReadRequest::include_session(). Session cookies used to be an accident of naming a profile; they are now their own question, andread(ReadRequest::browser("firefox").include_session())is expressible for the first time.- Rust:
ExecutionControl(timeout, cancellation, and the newAppBoundPolicy) composed once into every request type instead of copied per type, plusexecution(..)setters andload_report_with,browser_profiles_with,chrome_profiles_with,profiles_with, andLoadReportRequestso the stable v0.5.9 listing and aggregate signatures can stay unchanged while still taking control. - Rust: one typed public
Error(Request/Stopped/Source/Engine) with a stablecode()on every variant, plusEngineErrorcarrying theno_selected_source,no_discovered_source,discovery_failed, andengine_failurecodes. Direct-path I/O/SQLite inspection failures also keep their existingsource_inspection_failedcode while classifying as engine failures rather than caller mistakes. Those codes were previously unrecoverable: the sites that produce them raised formatted strings. Python gainsRookieError,RookieStoppedError, andRookieSourceErrorbeside the existing request and engine exceptions; Node'skindis nowrequest/stopped/source/engine. - Native linux-arm64 artifacts: PyPI manylinux aarch64 wheel, npm
rookie-cookies-linux-arm64-gnu, and a CLIaarch64-unknown-linux-gnubinary, all built onubuntu-24.04-arm. - Node and Python errors expose stable request/engine identity, library fault
codes, stop reasons, ambiguous-profile IDs, and redacted direct-path
metadata. Node read warnings also report when their count was saturated to
JavaScript's
Number.MAX_SAFE_INTEGER(2^53 - 1). - The generated report JSON Schema now enforces the same lexical constraints as Rust for open vocabulary identifiers and opaque installation/profile IDs.
- Python
ReadResultgainsdetailed_cookies(): isolation-intact records, each{"cookie": <8-field dict>, "context": {...}}, backed by the core'sdetailed_cookies(). New warning codesmalformed_host_identityandunparsable_partition_keysurface through the existingReadWarning.codestring, no binding change needed. - Python
read()/jar()gaininclude_session: bool = Falseandselect: Literal["legacy_first"] = "legacy_first";browser_report()gainsselect: Literal["legacy_first", "all"] = "all". Passingprofile=/profile_id=together withselect="all"(orselect="all"toread/jarat all, which cannot express it) raisesRookieRequestErrorwithcode == "conflicting_profile_selection"before any I/O. - Python
ReadResult.header()accepts aSendContext-shaped mapping or keyword arguments (top_level_site,resource,method,user_context_id,private_browsing_id,now) in addition to a bare URL string, mirroring Rust'sSendContext.RookieRequestErrorgains arequired: list[str]attribute naming the selectors anincomplete_send_contextfault was missing (empty for every other kind).
- The Linux Arc registry entry. Arc's vendor does not ship or support Arc on Linux; the macOS and Windows registrations remain.
- PyPI wheels for linux i686, armv7, s390x, and ppc64le. Those arches have no desktop browser cookie store this project can honestly support.
-
A direct-path
SourceInspectionFailedcaused by an I/O, SQLite, locked, or corrupt-file inspection failure is nowError::Engine/ deprecatedFaultKind::Engine; Python raisesRookieEngineError/RuntimeError, and Node reportskind == "engine"withGenericFailure. The stablesource_inspection_failedmachine code and sanitized diagnostic are unchanged. Caller-correctable path, source-kind, platform, and option failures remainError::Source/RookieSourceError/InvalidArg. -
Breaking (Rust):
rookie_cookies::Result<T>is nowResult<T, rookie_cookies::Error>. It wasanyhow::Result<T>through v0.5.9. A caller who wroterookie_cookies::Result<T>around a bridge function should userookie_cookies::anyhow::Result<T>, which still resolves. The deprecated v0.5.9 bridge functions are unaffected: they keep returninganyhow::Result, spelled explicitly.Errorimplementsstd::error::Error + Send + Sync, so?from the new surface into ananyhowcall site keeps working. -
Breaking (Python): a timeout, cancellation, or resource-exhaustion stop now raises
RookieStoppedError, notRookieEngineError. The two-way request/engine split had no separate bucket for a cooperative stop, so it fell under the engine class; the new four-way split gives it its own class. Code that caughtRookieEngineErrorto readstop_reasonmust catchRookieStoppedErrorinstead.kindisrequest/stopped/source/engine(wasrequest/engine). -
One
Requestvalue no longer selects differently depending on which function it is passed to. In 0.6-beta the same value meant "the first legacy-eligible profile" toextractand "every profile" toextract_report— two calls that looked identical and read different profiles. That is now a type-level distinction. -
Engine extraction seams take the typed internal
ProfileSelectioninstead ofOption<&str>, which could only express "one profile" or "all" and made the legacy-first scope inexpressible on the report path. -
A profile-scoped
readno longer flows through the report builder. The report DTO is frozen atschema_version: 1and carries the eight-fieldCookie, so a snapshot flattened out of it had already lostCookieContext—header()would have seen no isolated cookies and merged partitions on the recommended path.readnow stops at the finalized record and projectsDetailedCookiefor both single-profile selections. -
Breaking:
ReadResult::browser_id()returnsOption<&str>instead of&str. It was the empty string forfrom_path, an in-band sentinel a caller had to know about. Python exposesOptional[str], Nodestring | null. -
Breaking (Gecko):
.profile(q)alone no longer imports session cookies.SessionPolicydefaults toPersistentOnly, enforced before lookup, so the crate does not opensessionstore.jsorrecovery.jsonlz4unless asked. Passinclude_session()(Rust),include_session=True(Python),includeSession: true(Node), or--include-session(CLI). This fails quietly — a smaller list, no error. Report jobs are unaffected: they always retain session sources. -
A row whose required host identity did not survive decode is omitted rather than emitted as
domain: "", which matches nothing and belongs to no site. Snapshots count it under the newmalformed_host_identitywarning; reports record it as a source issue of the same name;extractinherits the omission and not the count, because a bareVec<Cookie>has nowhere to put it — usereadorextract_reportwhen the count matters. Unknown optional isolation fields stayNoneand never drop a row. -
Windows App-Bound (v20) recovery is now an explicit per-request policy.
AppBoundPolicydefaults toInjectionOnly: unprivileged reflective COM injection (Chrome 127+), but never the elevated SYSTEM impersonation thatAllowElevatedFallbackpermits — that now has to be asked for out loud.Disabledperforms no injection, no browser spawn, no process enumeration and no impersonation at all; v20 rows are then skipped and surface asdecrypt_failedread warnings with aprovider_failedreport issue naming the policy as the cause. The default isInjectionOnlyrather thanDisabledbecause Chrome has written v20 cookies on Windows since Chrome 127, so on a current profile essentially every row is v20 — aDisableddefault would return an empty list for the most common Windows case, and would leave the deprecated v0.5.9 bridge more capable than the recommended API. Injection is still not free of consequence: it spawns a browser process and writes into it, which endpoint security products can flag, so passDisabledwhere that matters. The deprecated v0.5.9 bridge keepsAllowElevatedFallback, so its 0.5.8 capability is unchanged. Python'sread,from_path,browser_report, andload_reporttakeapp_bound: str = "injection_only"("disabled"/"injection_only"/"allow_elevated_fallback"); an unrecognized string raisesRookieRequestErrorbefore any I/O. Node'sappBoundand the CLI's--app-boundfollow the same default.browser_profilesandchrome_profilestake noapp_boundparameter, since listing does no App-Bound work. -
ROOKIE_E2E_APPBOUND_MODEno longer steers a published build. It is compiled in only undercfg(test)or the off-by-defaulte2e-appbound-steeringfeature, and even there it can only narrow what the request policy already permits -- it can never widen one or overrideDisabled. -
Internal stop classification no longer round-trips a typed value through the report DTO's
terminationstring.TerminationCodeexists only at the wire edge; the flatten seam behindextractreads the enum. -
Single-browser compatibility APIs now return typed timeout, cancellation, and resource-exhaustion errors instead of silently returning partial cookies. Flat Rust
load()retains its documented best-effort behavior for cookies committed by browsers already in flight when the shared job stops. -
Profile-scoped
extract()andread()resolve the profile once and share one absolute deadline across resolution and extraction. -
ReadResult::header()now re-checks expiration at send time and excludes a cookie whose expiry equals the current second.include_expiredcontrols snapshot inventory only. Profile-scoped and compatibility reads now project the same decrypt and row-read warning categories. -
Node
fromPathand the CLIfrom-pathcommand reject every combination of conflicting Chromium credential selectors before source I/O. Python option shape failures consistently useRookieRequestError. -
Breaking (CLI): the former top-level flag modes are replaced by required job subcommands:
read,from-path,header,report,profiles, andbrowsers. Explicit paths are positional tofrom-path, and the Windows Chromium credential option is now--local-state-path; top-level--path,--browser,--load,--report,--list-*, and--key-pathare not accepted by the current CLI. -
Stopped report work now carries a typed request issue and cannot be reported as ordinary
no_sources; completed source data remains available with a partial status. Finalization issues preserve causes including decrypt, decode, encrypted, provider-unavailable, and provider-failed. -
CI is split into PR, nightly, and release lanes. Pull requests run one
checkjob per OS (fmt, package, metadata, cargo-audit, rust lint+test, public API), and stagger Node build+test (22/24/26) plus Python build+tests (3.12/3.13/3.14) across Ubuntu/macOS/Windows. The full Node and Python version product, FreeBSD, packaging wheels/sdist, Chrome/Firefox e2e, and artifact smoke move to nightly /main. A small real Chrome/Firefox gate now also runs on pull requests. Extra hosted browsers (Edge, Chromium, Brave, Opera, Opera GX, Vivaldi, Yandex, LibreWolf, Zen, Safari) are installed on the runner when a silent installer exists. Claimed-browser fixtures remain for products we cannot install. Extra hosted browsers run on nightly and again on release. Claimed-browser fixtures run onv*tags, GitHub Releases, orworkflow_dispatch. -
Releases now fail closed unless the exact tagged commit passes the full language/OS, artifact, assurance, security, real-browser, and claimed-browser suites. A guarded workflow-only npm bootstrap can create a new contract package with the release environment token before its trusted publisher can be configured; established packages remain OIDC-only.
-
The Safari hosted canary uses the normal application profile because Apple intentionally destroys SafariDriver automation storage at session teardown. Internet Explorer remains fixture-only because current hosted images expose only Edge IE mode, which cannot produce the legacy
CookieEntryExESE store supported by the deprecated decoder. -
macOS Chromium now reads its vendor-defined
Chromium Safe Storage/ChromiumKeychain identity rather than mixing the Chrome service with the Chromium account. -
docs/testing.mdlists every registry browser against hosted CI, release fixtures, or manual coverage.
- Rust
load()is superseded byread(ReadRequest::browser(...))for snapshots andload_report()for grouped diagnostics.CookieToStringis an unfiltered compatibility formatter; useReadResult::header(&SendContext)for a send-scoped header view. - The free
stop_reason/fault_kindfunctions andError::fault_kind.FaultKindis a two-way FFI split that collapses three ofError's four variants; match onError, or compareError::code(). - Rust
browser(id, domains), superseded byextract(ExtractRequest::browser(id).domains(..)). It was missing a#[deprecated]and is scheduled for deletion in 0.7.0. - The crate-root
pub use anyhowre-export, superseded byrookie_cookies::Error/rookie_cookies::Result. The compiler cannot warn on this one:#[deprecated]on apub useof an external crate root does not fire forrookie_cookies::anyhow::Result<T>. The attribute is present so rustdoc shows the banner, and this entry is the notice. It is also, deliberately, the escape hatch for theResultalias break above, so it stays working for the whole 0.6.x line.
-
macOS Yandex now declares its
Yandex Safe Storage/YandexKeychain identity and encryptedv10tier. Claimed Chromium forks create the seed tab through their native DevTools endpoint, avoiding Playwright persistent context hangs and vendor startup pages that ignore a positional URL. -
Artifact smoke on Ubuntu ARM64: maturin-action's manylinux container left
RUSTC_WRAPPER=sccachein the host job environment, so the native Node binding build failed looking for a hostsccache. -
The Windows legacy-DPAPI real-browser canary now gives system Chrome the same bounded 120-second cold-start allowance as the Ubuntu canary. Chrome 151 could exceed Playwright's 30-second default before extraction began; cookie extraction and validation assertions are still never retried.
-
FreeBSD and other unsupported Unix targets again compile the Unix direct-path identity constructor. It preserves the request value so the platform leaf can return the typed
unsupported_targetresult at execution.
- Rust
RequestErrorclassifies unknown browser / empty / unknown / ambiguous / lossy profile selectors, missing browser, and invalid header URLs as request faults. Unknown browser onresolve_registered_browserisFaultKind::Request. Request::profileandextract_report.browser_report's middle argument now accepts the same profile query (id, name, directory, non-lossy path, or persistent cookie-DB path). CLI--profilerequires--browseronly.- Job API:
read/ReadResult(into_cookies,header) /ReadWarning(code,count) /from_path/profiles. Python also exportsjarandreport. Node exportsread,profiles,report,fromPath. CLI subcommandsread,profiles,report,from-path,header. - ADR 0004:
readis the recommended entry.
- Documentation rehaul for 0.6: package-owned language guides (
read/jar), Chrome/Edge/Brave App-Bound v20 coverage notes, and migration from 0.5.6. browser_reportwidens non-id profile queries that previously always failed.firefox_profilenow resolves throughextract(Request::browser("firefox").profile(q)).- Recommended docs entry is
jar(browser=…)/read(…).as_list().
chrome_profile,firefox_profiles, and thefirefox_profileselector retarget toextract/extract_report/browser_profiles.
- Windows Chrome-family browsers (Chrome, Brave, Edge, CocCoc, and Avast) can decrypt App-Bound (v20) cookies via reflective COM injection into a spawned browser process, without administrator privilege. Elevated SYSTEM impersonation remains available as a fallback when injection is unavailable.
- Rust gains
FaultKind/fault_kind(&anyhow::Error), classifying an error as a request fault (bad input, e.g. an invalid explicit source) or an engine fault, alongside the existingstop_reason(). Python'sRookieRequestError(aValueErrorsubclass) andRookieEngineError(aRuntimeErrorsubclass) and Node'sInvalidArg/GenericFailureerror statuses now use this classification instead of one flat error type for every failure. - Python gains
rookie_cookies.dto, typed dataclasses for the canonical report/descriptor shapes (ExtractionReport,BrowserDescriptor,Cookie, etc.) generated from a newschema/report-dto.schema.json, with afrom_dict()classmethod converting the existing dict-shaped return values. This is additive; every function's existing dict return type is unchanged. - Python and Node.js now expose canonical
cookies_from_path/cookiesFromPathand explicit Chromium path APIs with credential options matching Rust's typed direct-path builders. The CLI adds--browser-idand--plaintext-only. - Rust's
Request,DirectPathRequest, andChromiumPathRequestgain.timeout(Duration)and a newCancellationHandle/.cancellation(...)for cooperative, cross-thread cancellation of an in-flight extraction, plus astop_reason()helper reporting why an extraction stopped early --TimedOut,Cancelled, orResourceExhausted. Node'scookiesFromPath,chromiumCookiesFromPath(Detailed), and every single-browser export (firefox,chrome,safari, etc.) accept matchingtimeoutMs/cancellationparameters, via a newCancellationHandleclass. Python'scookies_from_pathgains matchingtimeout/cancellationkeyword arguments, andchromium_cookies_from_path(_detailed)gain matchingtimeout/cancellationoptions-dict keys, via a newCancellationHandleclass; Python's other named-browser functions (firefox,chrome, etc.) are unchanged. The CLI's--browserand--pathmodes now cancel cleanly onSIGINT/SIGTERMinstead of aborting mid-extraction (a second signal forces an immediate exit), and no longer panics on a closed downstream pipe (e.g.rookie-cookies --load | head -1).
- Breaking (0.6.0): Python's
cookies_from_pathandchromium_cookies_from_path(_detailed)now raiseRookieRequestError(aValueErrorsubclass) instead ofRuntimeErrorfor a request fault (e.g. a missing or malformed explicit source, or mutually exclusive options); anexcept RuntimeErroraround one of these three functions no longer catches that case. Every other function's error type is unchanged. Node's thrown error.status/.codemoves from alwaysUnknowntoInvalidArg(request faults) orGenericFailure(everything else) across every export. - Rust's
load()andload_report()now probe registered browsers concurrently on a small bounded worker pool sharing one deadline/ cancellation budget, instead of one browser at a time. A slow or hung source no longer starves every other source's share of the shared budget. Results are always grouped by browser in the same fixed registry order regardless of completion order, and a per-source timeout or cancellation stops not-yet-started browsers without discarding results already completed by browsers that were in flight at that moment. - Breaking (0.6.0): The Python package now requires CPython 3.11 or newer.
CPython 3.8–3.10 and PyPy are no longer supported, and published wheels move
from the
cp38-abi3tag tocp311-abi3. - Breaking (0.6.0): CLI
--key-pathis now always a Chromium WindowsLocal Statecredential selector. It requires--path, is mutually exclusive with--browser-idand--plaintext-only, and no longer remains silently ignored on Unix or beside a Firefox database. Inapplicable combinations now fail with the typed direct-path diagnostic. - Breaking (0.6.0): The npm packages now require Node.js 22 or newer and are tested on Node.js 22, 24, and 26. Node.js 18 and 20 are no longer supported. The Node-API v4 ABI target is unchanged.
browser_registry.jsonis now the single browser discovery and credential source for named APIs, profile/report APIs, bindings, and CLI modes. Named functions keep their flat first-profile behavior through an explicit compatibility selection policy, whileCONFIGremains available as a registry-derived public compatibility view.- In the Python and Node bindings,
any_browser/anyBrowser, Chromium*_basedfunctions, and flat Firefoxfirefox_based/firefoxBasedfunctions are deprecated for removal no earlier than 0.7. Their 0.6 runtime signatures and behavior remain unchanged; detailed Firefox functions remain supported in those bindings. - Rust's
internet_explorer()andinternet_explorer_based()are deprecated for removal, not just superseded by a newer call shape: their ESE-format cookie database is read through an unmodified native C library with no process isolation, and containing that is not worth building now that the Internet Explorer browser app is discontinued (2022). Their 0.6 behavior is unchanged.
- Removed the duplicate internal
config.json+common/paths.rsdiscovery stack. No public browser function or configuration type was removed.
- Structured browser, profile, and extraction-report APIs now reach Rust, Python, Node.js, and the CLI with matching status, issue, counter, and cookie provenance semantics.
- The private browser registry now exposes the maintained platform variants, including Cốc Cốc and Yandex on macOS, Cốc Cốc, DuckDuckGo, Yandex, and Octo Browser on Windows, and Cachy Browser on Linux. Legacy named selectors remain source compatible.
- Detailed extraction preserves Chromium partition keys and Firefox container
identities without changing the legacy
Cookiewire shape.
- Browser discovery is installation- and profile-aware across Chromium, Gecko, Safari, and Internet Explorer sources. Active Chromium profiles are preferred without hiding other discovered profiles.
- Extraction failures now remain typed and visible through reports and legacy APIs instead of collapsing into successful empty results.
- WAL-mode cookie databases with no pending WAL are now read through the
verified private DB+WAL snapshot path. Read-only extraction no longer creates
-wal/-shmsidecars in a live profile and works when the source directory is genuinely read-only, without hiding WAL frames behind an unsafe immutable open. - Node extraction now converts native worker panics and invalid JavaScript arguments into rejected Promises instead of aborting Node or throwing before callers can attach Promise handlers. JavaScript examples now consistently await the asynchronous extraction API.
- Firefox session recovery accepts the browser-produced root cookie layout, bounds raw and decompressed session files, retains failed candidates, and handles seconds and milliseconds without coupling session data to the SQLite schema version.
- Chromium and Firefox schema-aware decoding now preserves valid metadata while counting and reporting malformed rows. Chromium v24 host-bound values are verified before their digest prefix is stripped.
- Safari and Internet Explorer parsing now retains partial results, reports malformed pages or records, and preserves security and SameSite semantics.
- macOS Keychain and Linux Secret Service/KWallet failures are explicit; KDE 6 is supported and successful passwords are not discarded by cleanup errors.
- Persistent Chromium and Mozilla domain filters now enforce exact-host and subdomain boundaries after their SQL candidate query. Explicitly empty filters and blank domain entries no longer expose the entire cookie store.
- Chromium key candidates remain zeroizing from derivation through final use, and profile extraction no longer multiplies ordinary heap copies of master keys.
- Windows App-Bound impersonation restores any pre-existing thread identity and treats restoration failure as fatal.
- Release workflows bind every published artifact to one reviewed tag commit,
recheck that tag immediately before publication, and require the tagged
commit to be part of
mainhistory. - Pull-request revalidation builds untrusted code without write credentials and reports statuses from a separate trusted job bound to the exact reviewed head SHA.
- Windows App-Bound (v20) cookie decryption for Chrome 133+: the
ChaCha20-Poly1305 (flag 2) and CNG-wrapped AES-256-GCM (flag 3) key-wrapping
schemes, ported from the
runassu/chrome_v20_decryptionreference. firefox_profiles()andfirefox_profile()in the Rust API, which enumerate every Firefox profile holding a cookie database and read cookies from a specific one selected by name, directory name, or path.
- Python extraction releases the GIL, and Node.js extraction functions now run as asynchronous tasks so cookie reads do not block their host runtimes.
- Domain filtering now has consistent matching behavior across browser backends, including mixed SQL filters.
- Extraction now returns an aggregate error when every requested browser fails, while retaining the individual browser failures for diagnosis. Linux keyring and D-Bus failures are also surfaced instead of silently discarded.
- SQLite-backed browsers now copy active
-waland-shmfiles with the main database, preventing recently committed cookies from disappearing while the browser is open. - Internet Explorer cookies now report
secureandhttp_onlyfrom the ESEFlagscolumn instead of alwaysfalse, so a Secure cookie is no longer extracted as one safe to replay over plain HTTP. Flags that cannot be read fail closed: an unrecognised cookie table is an error, and an individual cookie whose flags do not decode is skipped rather than reported as insecure. Theirsame_siteis now-1(unspecified) rather than0, which had claimedSameSite=Nonefor a store that records no SameSite attribute at all. - Firefox
profiles.iniresolution no longer returns whichever[Install...]section comes first in the file. A single unambiguous install still wins, but competing installs (a release and a nightly sharing oneprofiles.ini) are now broken by the[ProfileN] Default=1marker, and an install section without aDefault=key falls through to that marker instead of resolving to an empty path. - Firefox cookie discovery now falls through to secondary profiles when the
default profile has no
cookies.sqlite, instead of giving up. - Firefox
profiles.iniis parsed with escape processing disabled, so anIsRelative=0profile storing an absolute Windows path such asC:\Users\me\Profiles\workis no longer mangled intoC:UsersmeProfileswork. - Windows App-Bound key derivation now parses the key-blob framing header instead of slicing a fixed trailing window, so Chrome 133+'s 93-byte flag-3 key layout is decoded correctly (its scheme flag was previously read from the middle of the blob, leaving flag 3 unsupported).
- The CLI now sends tracing and log output to stderr so redirected stdout remains a valid cookie export.
- Chromium discovery includes the modern
Network/Cookieslocation on macOS and Linux, and valid unencrypted plaintext cookies are preserved. - Malformed cookie rows, truncated binary data, and out-of-range timestamps no longer discard an entire database or panic. Safari expiry timestamps are decoded as 64-bit floating-point values, and CBC decryption continues trying candidate keys after an invalid UTF-8 result.
- Source builds no longer fail when
gitis unavailable, watch the repository's actualHEADfor rebuilds, and include the packaged Rust examples.
- SQLite domain filters are parameterized rather than interpolated into SQL.
- Windows App-Bound extraction restores SYSTEM impersonation and
SeDebugPrivilegeon every path, removes shadow-copy temporary directories, and only force-closes a browser when explicitly enabled.
- A maintained fork published as
rookie-cookiesacross crates.io, PyPI, and npm, with matching Rust, Python, Node.js, and CLI names. - CPython 3.11–3.14 module tests and ABI3 wheel validation.
- Rust parser and helper tests, Python and Node.js module tests, CLI snapshot tests, and seeded Chrome/Firefox end-to-end coverage on Linux, macOS, and Windows.
- A release metadata validator and guarded first-release workflows for the Rust crate, Python wheels/source distribution, and five npm packages.
- Release, build, and maintained-fork documentation for downstream users such
as
notebooklm-py.
- Browser extraction failures now emit per-browser warnings instead of being silently discarded.
- Python error handling uses
anyhow, enabling current PyO3 and Python 3.13+ builds. - Node development, testing, and publication use npm consistently.
- Repository and package metadata now point to the maintained fork.
- Broken Rust doctests and CI coverage gaps.
- Python package version discovery with current Maturin releases.
- Chrome OS-crypt end-to-end setup on Linux, macOS, and Windows, including the Windows App-Bound Encryption path.