This tool decrypts a Windows DPAPI-protected cookie store and uses the extracted
sessionKey to authenticate as you against claude.ai. That is, byte-for-byte, the
same technique a credential-stealing trojan uses. We're not going to pretend
otherwise, and a license disclaimer alone would not make it safe. What actually makes
it safe is the architecture below — enforced in code (security_test.go), not just
promised in this document.
1. It cannot target another user's account.
DPAPI master keys are bound to the Windows user account that created them. There is no
code path anywhere in this project that can decrypt another user's cookie store — it
only ever works within the OS session it's run in, against whatever account is logged
into Claude Desktop on that machine. See loadMasterKey() in main.go.
2. It cannot run covertly.
Claude.ai sits behind a Cloudflare managed JS challenge that headless browsers cannot
pass. This tool must launch a real, visible Chrome window to clear it
(chromedp.Flag("headless", false) — hardcoded, never a flag or env var; enforced by
TestHeadlessIsHardcoded). An attacker who had already compromised a victim's session
well enough to run this tool covertly would see a browser window pop up on the victim's
screen — a terrible trade against any purpose-built infostealer, which doesn't need a
GUI at all. This friction is the point, not an oversight.
On the DPAPI/EDR question, since it comes up: CryptUnprotectData against a
browser's own cookie store is the exact behavioral signature (MITRE ATT&CK T1555.003)
that credential-stealing malware uses, and endpoint security products flag tools built
on it — Microsoft Defender classifies GhostPack's SharpDPAPI/SharpChrome, LaZagne, and
NirSoft's WebBrowserPassView this way even when signed, because that detection is
behavioral, not signature-based. Code signing alone does not make this go away; that's
expected, not a bug in this project's plan. Two things are worth keeping distinct: (1)
what this tool does today — CryptUnprotectData against Claude Desktop's own cookie
store, decrypting exactly one row, only ever within the OS session that created it (see
point 1) — is the offensive-shaped side of that primitive, and any serious reviewer
should expect an EDR to notice; (2) the planned encryption-at-rest work (README
roadmap) uses CryptProtectData/a TPM-backed CNG key to wrap a key this program
generates itself, which is the ordinary, defensive use of the same API family — the
same mechanism Chrome, Windows, and ASP.NET Core Data Protection all use to store their
own secrets. Conflating the two is an easy mistake for a scanner or a skimming reviewer
to make; this document exists so a human doesn't have to.
3. It cannot exfiltrate anything.
Every output goes to local disk (desktop-chats/) or local MemPalace. There is no
network client anywhere in this codebase capable of sending data to a third-party
server — no webhook, no telemetry, no "phone home." Every outbound network call targets
claude.ai and nothing else, enforced by TestNetworkEgressIsClaudeOnly, which scans
the source for every literal request destination and fails the build if one doesn't
resolve to claude.ai. (That test's actual boundary: it only catches literal string
URLs — a request destination built via a variable or string concatenation wouldn't be
caught. The stronger, structural guarantee is that there is no net/http import
anywhere in this module — see go.mod — so there is no general-purpose HTTP client
to redirect in the first place.)
4. The sessionKey is never observable in this program's own output.
The moment it's decrypted, it's registered with a redactor (security.go) that scrubs
it from stdout (installStdoutRedactor — replaces os.Stdout with a pipe so every
write, from this code or any future third-party library targeting stdout, is scrubbed
before reaching the terminal), from chromedp's own internal logging
(chromedp.WithErrorf/WithLogf, wired in cdp.go's openClaudeSession — without
this, chromedp's library defaults to Go's stdlib log package, which targets stderr
and would otherwise bypass the stdout redactor entirely while network.Enable() is
processing the injected sessionKey cookie for the whole session), and the daemon's log
file and probe-report.txt (both route through the same redact() call). Enforced by
TestRedactionScrubsRegisteredSecret and TestStdoutRedactorSurvivesSplitWrites.
(Scope: this covers every output path this program controls. It does not protect
against out-of-band forensics of the process's own memory — a debugger or memory dump
taken by someone who already has code-execution as the same Windows user, who could
just call CryptUnprotectData on Local State/Cookies directly instead. Since v2.2.0,
the raw AES master key and raw decrypted plaintext ([]byte, unlike the immutable string
this function returns) are explicitly zeroed (security.go's zeroBytes, deferred in
readSessionKey) the moment their last read completes — this narrows, but does not
close, the window an attacker with that level of access would have; the returned
sessionKey string itself is still held for the session's lifetime by design, both as
the CDP cookie value in use and in the redactor's own registry, so it can keep scrubbing
output for as long as the program runs.)
5. Only the minimum secret is ever decrypted.
Earlier versions of this tool decrypted the entire claude.ai cookie jar (session key,
Cloudflare tokens, everything). readSessionKey() now decrypts exactly one row — the
sessionKey cookie — because CDP only ever needs that one value; Chrome earns its own
cf_clearance by solving the challenge itself. Smaller blast radius: exactly one
decrypted (plaintext) secret is ever held in process memory. (Note: copyCookieDB()
does copy the entire Cookies SQLite file — every domain, still DPAPI/AES-GCM-encrypted
at rest — to a temp directory for every session, with best-effort cleanup that isn't
guaranteed if the process is killed mid-run. That's a broader on-disk footprint than
"one secret" might suggest by itself, though nothing in that copy is ever decrypted
except the single sessionKey row.)
6. It's not published as a reusable library.
Everything lives in package main, not an importable module (TestNoImportableCookiePackage).
Someone wanting to reuse the DPAPI-decrypt or Cloudflare-bypass primitives for an
unrelated purpose has to read and copy the code deliberately — not go get it as a
drop-in ingredient.
7. Rate-limited by design.
getWithRetry applies exponential backoff on rate_limit_error responses. This exists
because heavy sessions legitimately hit transient rate limits, but it also means this
tool cannot be trivially turned into a hammer against Anthropic's infrastructure.
TestHeadlessIsHardcoded and TestNetworkEgressIsClaudeOnly are regex/substring scans
of the source, not a general-purpose static analyzer. They're regression tripwires —
built to catch an accidental reintroduction of headless mode or a stray non-claude.ai
network call during ordinary maintenance — not a hard security boundary that would
survive a deliberately adversarial change. Concretely: splitting an override across two
lines specifically to dodge TestHeadlessIsHardcoded's same-line proximity check, or
building a request URL via a variable instead of a literal (invisible to
TestNetworkEgressIsClaudeOnly), would both slip through undetected. For a
solo-maintained project where the realistic risk is "I changed something and didn't
notice the consequence," not "a malicious contributor with commit access," that's an
honest and appropriate scope for what these two tests buy you.
TestNoImportableCookiePackage is the one test in this set that's robust against
deliberate evasion — dodging it requires literally restructuring the module out of
package main.
We deliberately did not add an "ethical use only" clause to the LICENSE. Such clauses are legally close to unenforceable and mostly function as security theater — they make the author feel better without stopping anyone. The actual protection is everything above: architectural constraints, enforced by tests, that make this tool a poor choice for anything other than exporting your own data from your own account on your own machine.
This project's original 2026-03 plan was a freemium SaaS (free tier, paid Pro/Team tiers aimed at individuals). That plan is retired. The individual tool is, and will remain, free and open under the AGPLv3 — no paid tier for the person running it against their own account, no telemetry, no upsell.
What is not free is one specific thing: the right to take this code and ship it inside a closed-source or SaaS product without honouring the AGPLv3. That is a separate commercial licence (see COMMERCIAL-LICENSE.md), aimed at organisations, not at the individual end user. The reasoning:
- The real addressable audience is a technical niche (people running Claude Desktop
- VS Code Claude Code + a local memory system like MemPalace), not a mass consumer market. There is no mass-market paid tier to chase — and none is chased.
- A tool that decrypts your own credentials should never ask you to pay a stranger for the privilege. For the individual, free + open source is the whole value exchange: read exactly what it does before trusting it with DPAPI access. The commercial licence is not charged to that person — it is charged to a company that wants to build a closed product on top and escape the copyleft.
- The AGPLv3 is the mechanism, not an afterthought. Its network-copyleft is what makes the free edition genuinely free (any hosted derivative must also open its source) and what makes a commercial licence meaningful (a company that won't open its source buys the exception instead). Free software ships "AS IS, WITHOUT WARRANTY" (see LICENSE); any warranty or support obligations would be negotiated separately in a commercial licence, not carried by the free edition.
This is intentionally scoped to claude.ai. Other providers (ChatGPT, Gemini, ...) each have their own auth model, cookie format, and challenge/anti-bot behavior — building correct, equally-hardened support for each is a separate effort, not a quick extension of this codebase. Not a limitation we plan to paper over; a deliberate v2 scope decision.
Open a private security advisory on this repository, or contact @KeilerHirsch directly. Do not open a public issue for a security report.
| Version | Supported |
|---|---|
| v2.x (CDP-based) | Yes |
| v1.x (tls-client impersonation, removed) | No — superseded, code deleted |