Privacy
What the account build sends
This is the plain list of what the account build sends. It is the usage-events document that governs Hacketix: a field exists in Hacketix's code only if it is in one of its tables. Without an account token, Hacketix sends nothing.
What leaves the machine, stated exactly. No finding content ever leaves: no finding ids, titles, evidence, parameter values, code, file paths or secret values. An account build sends the usage fields the tables below list, including the vulnerability class (class) and the per-rule counts (counts); a repository whose user opted in also sends the anonymized detail rows described under "Vulnerability detail".
01Two modes
One build, two modes
Hacketix is one build. What it does depends on whether an account token is present.
- Local build (no account token): makes no outbound network request. A test proves it.
- Account build (an account token, made when you sign in on the Hacketix page): sends one usage event per Hacketix tool call, and, only for a repository you opted in with
set_vulnerability_sharing, at most one vulnerability detail per recorded decision. Nothing else. Offline, events are dropped, never retried from disk.
02Every tool call
Usage event: one row per tool call
Your machine fills every field except accountId and receivedAt, which the ingest service sets from the token and the clock. A verify_fix call whose result is verified records its own decision, so it also sends that decision's record_decision row, built as a record_decision call's would be, with what was recorded. Every value is an identifier Hacketix made, an enum Hacketix defined, a count, a timestamp or a boolean. An unknown field is rejected. Every field is kept 13 months.
| Field | Type | Why it exists |
|---|---|---|
eventId | uuid | Idempotent inserts: a retried batch never double-counts. |
accountId | uuid (server-set) | The account the token belongs to; every per-account question. |
sessionId | uuid, random per server process | Orders one session's tool calls, so the last call before silence is visible. |
machineId | 64 hex, random, made once per machine in <dataRoot>/machine.json | Machines per account. Not a hardware id: nothing about the machine is in it. |
repoKey | 64 hex, sha256(repoSalt + "\0" + main worktree realpath), optional (absent for get_status, which has no repository) | Repositories per account and per machine. repoSalt is a second random 32-byte value in machine.json that is never sent, so a reader of the store, who sees machineId, still cannot try common paths against the key; two machines holding the same repository count as two. |
occurredAt | ISO 8601 timestamp | When the call happened on the user's clock. |
receivedAt | timestamp (server-set) | When the Worker stored it; the gap shows offline queues and clock skew. |
serverVersion | string, version-shaped, at most 20 characters | Which Hacketix build; errors and outcomes by version. |
ruleVersion | string, version-shaped, at most 20 characters | Which rule set; class frequency by rule version. |
client | claude-code, codex or other, mapped from the MCP handshake's clientInfo.name (server.server.getClientVersion()); the name itself is not sent | Claude Code versus Codex usage. |
platform | darwin, linux, win32 | Explains incomplete route proofs (the sandbox is Linux only) and platform-specific errors. |
sandboxAvailable | boolean | Whether route proofs could run on this machine. |
tool | one of the ten names in TOOL_NAMES: the nine tools every build registers, plus set_vulnerability_sharing, which a server started with an account token registers | What was called. |
ok | boolean | Whether the call returned a result or threw. The error message is never sent: messages can name files. |
durationMs | integer | Slow calls by tool and version. |
class | HypothesisClass, optional | The class a prove_finding, verify_fix, submit_hypothesis or propose_fix call was about. |
proof | reproduced, not_reproduced, incomplete, not_applicable, optional | The proof outcome (prove_finding result, or what record_decision recorded). |
incompleteReason | sandbox or other, optional | Why a proof was incomplete: sandbox when a route class had no sandbox; other otherwise. The oracle's free-text reason can name a file, so it stays local. |
verification | verified, still_vulnerable, breaks_behavior, incomplete, optional | The fix outcome (verify_fix result, or what record_decision recorded). |
decision | confirmed, dismissed, accepted, rejected, optional | What the user decided on a finding. |
counts | object of rule id to integer, keys limited to the ids in CHECK_VERSIONS, optional | Findings per rule id in a review_change or diagnose_repository result. The counts cover only the findings a result lists, and diagnose_repository truncates its list. Keys are Hacketix's own identifiers, never repository text. |
proofs | { run, reproduced, notReproduced, notApplicable, incomplete, notRun, ms }, optional | The diagnose's proof statistics, the same object the result already carries. |
03Never
Never collected
Never in any event or detail, or in Hacketix's own logs, in the local build or the account build:
- source code, diffs, patches or snippets;
- file paths, file names, directory names, worktree paths, or the data directory path;
- repository names, remotes, commit hashes, branch names (the repository is
repoKey, a salted hash), or therepoSaltthat salts it; - table, column, schema, view, function, route, action, export or environment variable names (finding parameters);
- finding ids, titles, evidence text, notes, reasons or any other free text from a result;
- secret values, fingerprints of secrets, or env file contents;
- the user's name, email or IP address in the tables (the Worker reads the account from the token; Cloudflare's request logs are the only place an IP exists, under Cloudflare's own retention);
- anything from a repository the user did not run Hacketix on.
04Your account
What your account holds
Signing in on the Hacketix page creates an account. It holds:
- Your email address. The sign-in link is sent to it, and the sign-in service (Supabase Auth) keeps it with your account. It is never in the usage events or details.
- Your token hashes. For each token you make: the SHA-256 hash of the token, the optional label you gave it (at most 80 characters) and when you made it. The token itself is made in your browser and only its hash is sent; Hacketix uses the hash to know which account an event belongs to.
- Your feedback. Each message you send with the feedback form: its text (at most 4,000 characters), when you sent it, and your account.
05Opt-in
Vulnerability detail (opt-in per repository)
A repository you opted in sends at most one detail row per decision recorded with record_decision (or recorded by a verified verify_fix), so every row carries an outcome. A row is sent only when all of these hold: the server started with an account token and still has one; the decision was recorded; the repository's stored choice (sharing.json, on this machine, outside the repository) is on; and the server still knows the finding when it builds the row, after the call's result has gone back. Like an event, a row the service does not accept is dropped. Every field is kept 180 days.
| Field | Type | Why it exists |
|---|---|---|
detailId | uuid | Idempotent inserts. |
accountId | uuid (server-set) | Whose repository. |
machineId, repoKey | as in the usage event; repoKey is required here, since a decision always has a repository | Joins to usage; repositories per class. |
occurredAt, receivedAt, serverVersion | as in the usage event | When, under which build. |
consentVersion | 1 | Which consent text the user saw; a new text is a new version and re-asks. |
ruleId | one of the ids in CHECK_VERSIONS | Which check found it. |
class | HypothesisClass, optional | Which oracle class, for loop findings. |
severity, confidence | high, medium, low | How Hacketix rated it. |
origin | detector, hypothesis | Whether a detector or the agent raised it. |
evidenceKind | executed, deterministic | Whether a proof ran. |
shape | object of the two keys below | The finding's anonymized shape, grouped so a row has one. |
shape.paramKeys | subset of table, ownerColumn, schema, name, kind, envVar, route, actionFile, exportName, path | Which parameter kinds the finding had. Keys only, never values. |
shape.target | app-route, page, pages-api, action, table, definer, env, file, other, none | What kind of construct it was, decided by the same file-pattern regexes contracts.ts already uses. |
frameworks | subset of FRAMEWORKS (next, @supabase/supabase-js, @supabase/ssr, stripe, @clerk/nextjs, next-auth, jsonwebtoken, jose, prisma) | Which of these the root package.json names. Other dependency names are never read out. |
decision, proof, verification | the enums above | The outcome. |
Consent
Hacketix shows this text at the end of a diagnose_repository report in an account build while no choice is stored for the repository (a yes or a no ends it), and the agent repeats it word for word before calling set_vulnerability_sharing:
Hacketix can share with its team what kinds of vulnerabilities it finds in this repository and whether their fixes worked: the class (for example idor-read), which of a fixed list of frameworks package.json names, the severity and confidence, how the finding was found (a detector or a hypothesis) and proved (executed or deterministic evidence), the proof and verification results, the decision you recorded, and the shape of the finding (a route handler, a page, a Server Action, a table) with no names in it. Each row also carries which Hacketix check found it, the time, the Hacketix version, this text's version and a random row id. It never shares code, file paths, table, route, variable or repository names, finding text or secret values. The repository is identified by a random key this machine made, and this machine by a random id. Rows are deleted after 180 days. Turn it off any time by asking for set_vulnerability_sharing again. Say yes to turn it on for this repository.
The choice is stored on the machine, outside the repository, in projects/<key>/sharing.json beside the baseline. It is per repository and per machine. The server never sends a detail without detail: true in that file.
06Retention
How long it is kept
- Usage events are deleted after 13 months and vulnerability details after 180 days, every night.
- Both periods run from when the service received the row (
receivedAt, set by the server), not fromoccurredAt, which is your own clock: a wrong clock on a client can neither delete its events or details early nor keep them. - A user who deletes their account deletes their rows: the usage events, vulnerability details, token hashes and feedback of an account are deleted with it.
07Your choice
Turn it off
- Usage events. Hacketix sends them only with an account token: the
HACKETIX_TOKENenvironment variable, or thetokenfile in its data directory (macOS:~/Library/Application Support/hacketix-mcp/token; Linux and WSL2:~/.local/share/hacketix-mcp/token). Without one it sends nothing and works the same. - Vulnerability detail. Off until you say yes for a repository. Turn it off any time by asking for
set_vulnerability_sharingagain.