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.

FieldTypeWhy it exists
eventIduuidIdempotent inserts: a retried batch never double-counts.
accountIduuid (server-set)The account the token belongs to; every per-account question.
sessionIduuid, random per server processOrders one session's tool calls, so the last call before silence is visible.
machineId64 hex, random, made once per machine in <dataRoot>/machine.jsonMachines per account. Not a hardware id: nothing about the machine is in it.
repoKey64 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.
occurredAtISO 8601 timestampWhen the call happened on the user's clock.
receivedAttimestamp (server-set)When the Worker stored it; the gap shows offline queues and clock skew.
serverVersionstring, version-shaped, at most 20 charactersWhich Hacketix build; errors and outcomes by version.
ruleVersionstring, version-shaped, at most 20 charactersWhich rule set; class frequency by rule version.
clientclaude-code, codex or other, mapped from the MCP handshake's clientInfo.name (server.server.getClientVersion()); the name itself is not sentClaude Code versus Codex usage.
platformdarwin, linux, win32Explains incomplete route proofs (the sandbox is Linux only) and platform-specific errors.
sandboxAvailablebooleanWhether route proofs could run on this machine.
toolone 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 registersWhat was called.
okbooleanWhether the call returned a result or threw. The error message is never sent: messages can name files.
durationMsintegerSlow calls by tool and version.
classHypothesisClass, optionalThe class a prove_finding, verify_fix, submit_hypothesis or propose_fix call was about.
proofreproduced, not_reproduced, incomplete, not_applicable, optionalThe proof outcome (prove_finding result, or what record_decision recorded).
incompleteReasonsandbox or other, optionalWhy 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.
verificationverified, still_vulnerable, breaks_behavior, incomplete, optionalThe fix outcome (verify_fix result, or what record_decision recorded).
decisionconfirmed, dismissed, accepted, rejected, optionalWhat the user decided on a finding.
countsobject of rule id to integer, keys limited to the ids in CHECK_VERSIONS, optionalFindings 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 }, optionalThe 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 the repoSalt that 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.

FieldTypeWhy it exists
detailIduuidIdempotent inserts.
accountIduuid (server-set)Whose repository.
machineId, repoKeyas in the usage event; repoKey is required here, since a decision always has a repositoryJoins to usage; repositories per class.
occurredAt, receivedAt, serverVersionas in the usage eventWhen, under which build.
consentVersion1Which consent text the user saw; a new text is a new version and re-asks.
ruleIdone of the ids in CHECK_VERSIONSWhich check found it.
classHypothesisClass, optionalWhich oracle class, for loop findings.
severity, confidencehigh, medium, lowHow Hacketix rated it.
origindetector, hypothesisWhether a detector or the agent raised it.
evidenceKindexecuted, deterministicWhether a proof ran.
shapeobject of the two keys belowThe finding's anonymized shape, grouped so a row has one.
shape.paramKeyssubset of table, ownerColumn, schema, name, kind, envVar, route, actionFile, exportName, pathWhich parameter kinds the finding had. Keys only, never values.
shape.targetapp-route, page, pages-api, action, table, definer, env, file, other, noneWhat kind of construct it was, decided by the same file-pattern regexes contracts.ts already uses.
frameworkssubset 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, verificationthe enums aboveThe outcome.

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:

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 from occurredAt, 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_TOKEN environment variable, or the token file 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_sharing again.