Skip to main content

Trust Center

How CodeRifts handles your data, what permissions it requires, and where it runs. No marketing — just facts.

Data Handling

✓

No spec storage

Repository and branch identifiers are stored. Spec content is not. Specs are processed in memory and discarded after analysis. pr_analyses keeps repo and branch_name (src/db/migrate.js) and has no spec-body column.

✓

Only diff results returned

Only diff results (risk scores, detected patterns, changelog) are returned to the caller. We persist derived verdicts and metadata — not your schema bodies or source code.

✓

No PR content stored

GitHub PR comments are written by the GitHub App using the GitHub API. No PR content is stored.

GitHub App Permissions

This is the App’s complete permission set, read on 2026-09-23 from GitHub’s own record of the App (GET https://api.github.com/apps/coderifts, a public endpoint — check it yourself). Six permissions, every one of them listed below with what it is for. Until that reading, two were missing from this table.

Permission Level Why
Pull Requests Read/Write To read PR diffs and post comments
Contents Read Read allows repository file access. Core Diff reads configured specification files, and opt-in MigraGuard/ActionGuard read migration files, application source references and workflow YAML, in memory only; none of that file content is persisted
Checks Write To post check run status (block/pass)
Issues Read Slash commands. On GitHub a pull-request comment is an issue comment, so reading /coderifts … in a PR thread needs this scope — there is no PR-comment-only permission. Read, never write: the App posts through Pull Requests, not here. Subscribed events: issues, issue_comment
Deployments Read Deploy outcome. A deployment_status of success or failure on a sha with a stored analysis becomes the recorded outcome of that contract change. Read only: CodeRifts creates no deployment and changes no deployment state. Subscribed events: deployment, deployment_status
Metadata Read Required by GitHub for every App; no content access of its own

Repository and branch identifiers are stored; spec content is not. Core Diff reads configured specification files, and opt-in MigraGuard/ActionGuard read migration files, application source references and workflow YAML, in memory only; none of that file content is persisted. Configured specification files (Core Diff) from the base and head of a pull request. If none are configured, discovery reads OpenAPI-named files instead. When files are configured, the same discovery may also read OpenAPI-named files that are not in the list, to notice them. Opt-in MigraGuard reads migration files and application source references when a pull request changes a migration file. Opt-in ActionGuard reads workflow YAML when a pull request changes a workflow file. Those file reads are in memory and the file content is not persisted. No access to secrets, and no organization-level scope: every permission above is repository-scoped and granted per installation.

Corrected 2026-09-23. This line used to read “No access to issues”. That was wrong: the App holds issues: read and subscribes to the issues and issue_comment events, because that is how a slash command in a PR thread reaches it. The table listed four of six permissions and the sentence below it denied one of the two that were missing. Both are now listed with what they are for, and the set is the one GitHub reports.

What each install path asks for

The GitHub App is one of six ways to run CodeRifts, and it is the only one that asks for repository permissions. For each of the others: the minimum credential, why it needs it, what it does not read, what actually leaves your machine, and how to remove it. Measured 2026-09-23 from the shipped packages and the application source.

GitHub App

Six repository permissions — the full set is in the table above.

Minimum credential
An installation on the repositories you choose. No personal access token, no org-level scope; every permission is repository-scoped and granted per installation.
Does not read
Secrets, Actions secrets, organization settings, or any repository you did not select. Core Diff reads configured specification files, and opt-in MigraGuard/ActionGuard read migration files, application source references and workflow YAML, in memory only; none of that file content is persisted.
What leaves your repository
The contract files of the pull request under analysis, processed in memory, never persisted. The result path is a PR comment plus a check run.
How to remove it
GitHub → Settings → Applications → Installed GitHub Apps → CodeRifts → Uninstall. Revokes every permission above in one action; nothing remains installed in the repository.

MCP server (hosted)

No credential to connect. A key only for metered governance.

Minimum credential
None to connect, initialize or tools/list at https://app.coderifts.com/mcp. An API key (X-API-Key or Authorization: Bearer) is what turns preflight_mode: authorize into a decision that carries a grant; without one the server answers authorization_requires_issuer.
Does not read
Your filesystem, your repository, your git history. It has no access to anything you do not put in the call. Three tools, and none of them fetches.
What leaves your machine
Exactly the arguments of the tool call — the change set you pass to preflight_change_set, a receipt to verify_receipt, or a decision id to get_decision_details.
How to remove it
Delete the server entry from your MCP client config. Nothing is installed anywhere else; there is no local agent and no background process.

Claude Code PreToolUse hook — @coderifts/agent-hooks

Runs on your machine. Sends one JSON-RPC POST per matching tool call.

Minimum credential
None to install. CODERIFTS_API_KEY in the environment is optional: with it the gate asks in authorize mode, without it in analyze mode and then falls to approval.
Does not read
Your repository, your other files, your environment beyond that one variable. It inspects the parameters of the tool call the agent is about to make, and only when the target path matches a contract-file pattern (openapi*.yaml|json, swagger*, and the rest of that list). A tool call touching anything else never leaves your machine at all.
What leaves your machine
One POST to https://app.coderifts.com/mcp carrying the operation, the matched path, and the content that tool call was going to write. Nothing else — no session handshake, no second request.
How to remove it
claude plugin uninstall agent-hooks@coderifts, or delete the hook entry from .claude/settings.json if you wired it by hand.

CLI — npx coderifts

An API key for the hosted calls. Nothing installed.

Minimum credential
CODERIFTS_API_KEY for the calls that need a grant. registry-gate validates a directory of specs locally and needs no key at all.
Does not read
Anything outside the paths you hand it. It has no GitHub credential of its own: the read-only boundary readback uses the token you already have from gh auth token, and it never writes through it.
What leaves your machine
The specs you pass to the gate, and for deploy-gate the receipt being verified. Receipt verification itself is offline — signature, body hash, expiry and scope are checked against a pinned key, not by asking us.
How to remove it
Nothing to remove if you ran it with npx. Otherwise npm rm -g coderifts, and unset the variable.

CI step — coderifts/action, GitLab CI, Bitbucket

One repository secret. No App installation.

Minimum credential
CODERIFTS_API_KEY as a repository (or CI) secret. The job also needs whatever your own checkout step needs — that is your workflow’s token, not ours.
Does not read
Your other secrets. The step receives the API key you pass it and the working tree your checkout produced; it does not call the GitHub API on your behalf.
What leaves your runner
The contract files being compared. The verdict comes back to the job summary.
How to remove it
Delete the step from the workflow and delete the secret. Nothing persists between runs.

Execution boundary — coderifts/k8s-admission, coderifts/gateway-verifier

Customer-hosted and open source. We hold no credential at all.

Minimum credential
None from us. You pin a keyring (KEYRING_PATH) and register the webhook for the resources you care about.
Does not read
Nothing reaches us: no CodeRifts-operated service is called at admit time. That is the design and also the limit — nothing we run can observe, delay or rescue that decision, and a keyring you stop updating is one we cannot refresh for you.
What leaves your cluster
Nothing. The verifier checks evidence the caller already holds, against the key you pinned.
How to remove it
Delete the admission webhook registration, or take the verifier out of the request path. Measured 2026-09-20 from those repositories’ own READMEs.

Service status

Public, continuously updated, and not ours to edit after the fact: status.coderifts.com — per-service uptime with the incident history kept. Measured 2026-09-23: all services operational, app.coderifts.com/health at 99.690% uptime, past downtime shown rather than smoothed away. The page existed and was reachable before this line linked it; until 2026-09-23 nothing on this site pointed at it.

Re-measured 2026-09-23, later the same day: the status.coderifts.com vanity domain now answers 200, and the provider host coderifts.betteruptime.com answers 301 to it. The vanity name is therefore the canonical one and the provider host is a redirect source — which is the direction this line asked for when it said not to publish the short form until it answered.

Security notes

Dated notes of what was actually exposed, what was not, and what changed. A note here is a record, not a promise that nothing else can be wrong.

2026-09-25 — Two read endpoints answered without a key

The key middleware was mounted on both routes and still let an anonymous caller through, because that middleware identifies a key and does not, by itself, refuse the absence of one.

Window

GET /api/v1/timeline and GET /api/v1/timeline/summary: 2026-03-14 → 2026-09-25. The route file’s first commit is fc64c74 (2026-03-14); the anonymous refusal landed in 9272675 (2026-09-25). GET /api/v1/preflight-logs: 2026-03-19 (fa3f294) → the same 2026-09-25 refusal. Counting the timeline as 194 days understates it. pr_analyses, the table that route read, was not on the 90-day sweep, so an anonymous caller could read the whole history of that table for any repository name, not a 194-day slice (src/db/retention.js, comment dated 2026-09-25).

What was readable

Decision metadata, from the response the handler built (src/routes/timeline.js): verdict, risk score and level, breaking-change count, the top pattern’s name, a semver suggestion, pull-request number, a 7-character commit SHA, timestamp, and the repository name the caller supplied. preflight-logs added a submitted spec URL, a truncated user-agent, a hashed IP, and a response time. Path and field names were not in that JSON: the query loaded detected_patterns and the response emitted only the pattern name.

What was not readable

Spec or diff body. The API-key hash column exists on pr_analyses (src/db/migrate.js) and was not in the SELECT or the response. No tenant id: that table has no tenant column. No user account and no email.

Whether anyone called it

Not decidable from data the route stored. The handler writes a log line only when the query throws. It does not record a successful anonymous read. How many rows belonged to someone other than us was not counted for this note.

The fix

  • Anonymous requests to /api/v1/timeline, /api/v1/timeline/summary, and /api/v1/preflight-logs receive 401 (authentication_required) since 2026-09-25. On the timeline and the timeline summary that 401 is the absence of a key. Any key still read any repository by name until 2026-10-02.
  • A keyed GET /api/v1/ledger returns rows only for repositories bound to that key’s tenant (src/routes/ledger.js, join on repo_bindings). An anonymous ledger read stays open and returns an allowlist: verdict, risk level, break count, timestamp, and a per-row digest. Repository, pull request, commit, people, and override reason are not in that view.
  • Stored request-log IPs are salted (sha256s: in src/util/hash-ip.js). The previous sha256: values were unsalted and, for IPv4, reversible; the migration nulls them. Whether the live deploy pins CODERIFTS_IP_HASH_SALT is not measured here. Unset, the process generates a salt at boot.
  • pr_analyses is on the 90-day retention sweep. The sweep does not rewrite rows that were already read.

What stayed open on purpose

GET /api/v1/timeline/public still answers without a key (src/routes/public-timeline.js). The query filters on the repository name the caller supplies and on a day window. It does not check whether that repository is public. A probe on 2026-09-26 of five private repositories this org owns, and of one invented name, each returned HTTP 200 and zero rows. A public repository returned 50 rows in the same window, including verdict, risk score, pattern name, and commit SHA. This probe did not return a private row. It also did not prove a private repository with stored analyses would be withheld.

Structural lesson

Authenticated is not authorized. The middleware sets a flag and calls next on a request with no key, so a route can serve both a public mode and a keyed mode. Mounting it is not the same fact as requiring a key. A wrong key was refused; no key was accepted, which is why the routes did not look broken. The timeline test mocked a stricter gate than the code: it rejected a missing header and never set the flag the real middleware sets, so the suite could not ask what an anonymous call received.

2026-10-02 — GET /api/v1/ledger, what an anonymous caller could read, and when

1. Full rows without a key: 2026-03-14 → 2026-09-25 14:26 UTC

The key middleware let a request without a key through, and the route did not check. From 2026-03-14 17:00 (-04:00) a caller had to name a repository and received its full rows. From 17:15 the same day the filter was optional, and a caller without a key received every row of every repository: repository, pull-request number, PR author, commit SHA, verdict, risk and omega scores, break count, policy rule and action, who approved or overrode a decision and why, and timestamps (22 fields from 2026-03-15). The table has no tenant column, so there was no tenant filter to apply. The code is dated 2026-03-14; our deployment records reach back to 2026-06-21, and the oldest record already served it.

What the table held before the fix: 2,257 rows. 1,649 came from anonymous analyses and carry no repository and no person, except one approval recorded under our own test address on 2026-03-14. 83 belong to our own repositories. 525 belong to one third-party repository, which is public on GitHub. Each of those 525 rows names a PR author (7 distinct GitHub logins, 175 of the rows authored by a bot). None of them records an approver, an overrider or an override reason. PR authors and commit SHAs of a public repository are public on GitHub; our verdicts and scores on its pull requests were not.

2. Six fields, and a repository-name oracle: 2026-09-25 14:26 UTC → 2026-10-02 22:31 UTC

An anonymous caller received only verdict, risk level, break count, timestamp, a row number and a per-row digest, with no repository and no person. A key read only repositories bound to its tenant. But a caller who supplied a repository name could still filter by it, and so learn whether that repository had history with us and read its verdicts and timestamps.

The third-party rows did not end on 2026-09-25. They run through 2026-09-30. Of the 17 rows after 2026-09-25, those six fields were visible without a key, and a caller who knew the name could also query their verdicts and timestamps with the filter until 2026-10-02 22:31 UTC.

3. Closed: since 2026-10-02 22:31 UTC

A repository named without a key is served only when it is observed public; anything else answers like a name with no history. A key still reads only its tenant's bound repositories.

Whether anyone read the ledger before 2026-09-25 cannot be measured. Our request logs do not reach back before 2026-09-25, and the route never recorded successful reads. In the window that can be measured (2026-09-25 → 2026-10-02), no caller read a repository that was neither their own nor public.

Timeline and analytics, by route

From 2026-09-25 (ledger keyless repository filter), 2026-03-14 (analytics/stability), 2026-03-14 (timeline) and 2026-03-15 (timeline/summary) until 2026-10-02, the ledger's keyless repository filter, analytics/stability, and timeline/timeline-summary (with any key) could return another tenant's repository decision history. Fixed on 2026-10-02: a history is readable by name only when the repository is public or bound to the caller's tenant; an unreadable repository answers like an unknown one. In the retained logs (2026-09-25 to 2026-10-02) no such read occurred; before 2026-09-25 it cannot be measured.

Commits, from git log -S and blame on the parent of the fix. Ledger allowlist plus the caller-supplied repo filter: b235243, 2026-09-25 16:26 +0200. The if (repo) line is older (02142dc, 2026-03-14) and then selected full rows, before this allowlist. analytics/stability queried WHERE repo = $1 from f8cfcef, 2026-03-14 11:32 -0400, and that file named no tenant until the fix. Timeline queried WHERE repo = $1 from 94c1f0b, 2026-03-14 13:25 -0400; timeline/summary from 4e796fa, 2026-03-15 05:15 -0400. 9272675 (2026-09-25) added the anonymous 401 and left the name filter. The fix commit is 5b856d5, 2026-10-03 00:31 +0200 (2026-10-02 22:31 UTC). Measured live after the fix on 2026-10-02: a keyless ledger request filtered to a repository that is not observed public, and the same request to analytics/stability, both answer 404. The read count is the 2026-10-02 ledger GET note.

2026-09-26 — A bad signature could still read as currently authorized

POST /api/v1/verify-receipt, and the MCP tool verify_receipt which calls it, computed currently_authorized from the receipt payload even when the signature did not verify. valid stayed false. Found 2026-09-26 on the mounted app. Fixed the same day in 5bbac9c. Live: engine_build_id 5bbac9c6af8b, and /health uptime puts that process start at about 08:34 UTC.

Window

2026-07-28 → 2026-09-26, about 08:34 UTC. The start is commit a02a2bb, the day the server-side authorization layer landed on verify-receipt. When that commit was deployed was not measured.

Whether anyone could act with it

Not measured. The response always said valid: false. A caller who refused on valid did not proceed. A caller who proceeded because currently_authorized was true could have. How many such calls happened is not counted here. The agent guard, the contract-gate action, the gateway verifier, and the offline verifiers do not take this answer from the server route.

The fix

Authorization is evaluated only after the signature verifies. Otherwise currently_authorized is false and authz_reason is signature_not_verified.

In the window we can measure (2026-08-24 → 2026-09-26, 67 verify calls), no call received currently_authorized: true without a verified signature. The period 2026-07-28 → 2026-08-24 predates verify telemetry and cannot be reconstructed.

P40 — 2026-10-04

P40 — 2026-10-04. Between the P32 fix and 2026-10-04, an incomplete analysis in authorize mode could come out as a signed ALLOW. The clearest case: an OpenAPI 3.1 webhook that lost a required field received CONTINUE with a receipt, while the same pair in analyze mode answered ANALYSIS_FAILED. The cause was that a 3.1 spec was converted to 3.0.3 before diffing, which removed webhooks and similar sections, and the authorize path did not read the resulting coverage gap. Since 2026-10-04 an incomplete analysis in authorize mode is ANALYSIS_FAILED with the gap named, a 3.1 spec is diffed as written, and the conversion is only a degraded fallback. We found this in our own measurement. Since 2026-09-19, every authorize decision on record was issued to our own tenants; no other tenant received one. A degraded ALLOW from before 2026-10-04 cannot be told apart in our records.

The same gap existed on the MCP manifest path until 2026-10-04 (P49): an MCP tool schema that referenced itself fell back to a path-only diff, and that incomplete analysis was not marked as such. It is now ANALYSIS_FAILED with the gap named.

P47 — 2026-10-04

P47 — 2026-10-04. Until 2026-10-04 the App read .coderifts.yml from the pull request's head, so a pull request could change the policy it was judged by: adding an ignore rule turned its own break into ALLOW and its check from failure into success. A reviewer whose commits were in the head, or a pull request without a recorded author, could also count as an independent approval. Since 2026-10-04 the decision keys of .coderifts.yml come from the base branch, a change to them in the head is shown and not applied, the authors and committers of the head commits cannot approve, and a missing author means no approval counts. We found this in our own measurement; we know of no external repository where it was used.

P61 — 2026-10-06

P61 — 2026-10-06. Before coderifts 8.7.3, the CLI's offline commands did not run CodeRifts' analysis. What it affected: coderifts diff (without --cloud or --gate) and publish-gate without a key ran a separate open-source engine (the openapi-diff package), and adopt reported the engine in a non-production mode. What we measured (17 OpenAPI changes, 2026-10-06): the local diff reported should_block: false for all 17; it could not read OpenAPI 3.1 or 3.2; through --ci it passed one change our server stops (a shrunk enum). adopt reported a different decision from the server on 8 of 31 changes. Not affected: the server-side preflight, the GitHub App and the merge gate. What we cannot say: how many runs used the older path. The CLI sends nothing home. npm counts 11,732 downloads of the coderifts package between 2025-04-05 and 2026-10-05 (4,056 in the 30 days to 2026-10-04), including our own release checks and CI caches, and a download is not a run. Since coderifts 8.7.3, published on npm at 2026-10-06 21:10 UTC, coderifts diff without --cloud or --gate and publish-gate without a key run the server's analysis and agree with it on those 17 OpenAPI changes.

P62 — 2026-10-06

P62 — 2026-10-06. From 2026-06-16 to 2026-10-06 09:56 UTC, the tool-schema registry (POST and GET /api/v1/registry/tool-schemas, /check, and the incidents) identified the tenant by the first 12 characters of the API key: cr_live_ plus 4 hex digits, 65,536 possible values. Two keys that received the same prefix could list each other's registered tools, including the alert webhook URL, compare a schema with the other's on /check, fire the other's webhook, read the other's incidents, and overwrite the other's schema. Since 2026-08-23 a new key was regenerated while its prefix was already in the store, up to 5 times. That did not check keys already issued. A prefix still taken after those attempts was stored anyway. A revoked key's prefix could be issued again. On 2026-10-06, deployed at 09:56 UTC, the registry was bound to the key's full identity, and the old rows were moved. At the fix the registry held 3 schemas and 1 incident. Each of the three old prefixes belonged to exactly one live key. None was shared or orphaned. What we cannot say: whether a revoked key's prefix had already been issued again, because a revoked key leaves no trace.

P65 — 2026-10-06

P65 — 2026-10-06. Local hooks and the contract-gate Action treated some MCP client-configuration names as contract files and sent the file's whole text, including any tokens in it, to preflight, over TLS. coderifts claude-hook did that from commit bd82bba on 2026-09-01 13:09 UTC, published in coderifts 8.0.0 at 2026-09-01 16:24 UTC, for a file whose name contains mcp and ends in .json, .yaml, or .yml. That includes mcp.json, .mcp.json, .cursor/mcp.json, .vscode/mcp.json, mcp_settings.json, and cline_mcp_settings.json. It did not match claude_desktop_config.json. @coderifts/agent-hooks matched a path ending in mcp.json from its first release, 0.2.0, npm 2026-09-13 16:05 UTC, and matched any JSON path whose name contains mcp from 0.3.0, npm 2026-10-03 19:50 UTC. The Claude plugin's mod did that from api-governance 1.2.0, commit 2026-10-03 12:44 UTC, for a path whose name contains mcp. The contract-gate Action's classifier matched mcp.json, a path ending in /mcp.json, and mcp_settings.json from the repository's first commit on 2026-07-26 17:34 UTC. The first published release that carries that classifier is v0.7.0, published 2026-09-01 12:43 UTC. That regex did not match .mcp.json, claude_desktop_config.json, or cline_mcp_settings.json. The text was not stored and was not logged. The request log records the method, the path, the status, the duration, and a key prefix, and not the body. On a keyed authorize, decision_authorized_paths keeps the path and the SHA-256 of the text after the file, for 90 days, and not the text. A read of 30 days of live logs counted 0 lines with a token shape. Until the fix, the stored rows that name a client-configuration file are 2, and the rows that carry only the fixed id api are at most 12. All of those rows are our own tenant. They came from our own tests. No other customer's configuration arrived. For the mod and for coderifts claude-hook the stored path uses the fixed id api, so one of those rows cannot be told from a real tool manifest. Nothing here describes a network between the caller and us. Since the fixes, an MCP client configuration is not a contract at each entry point: api-governance 1.2.7 (tag v1.2.7, 2026-10-06 12:49 UTC), coderifts 8.7.3 (npm 2026-10-06 21:10 UTC), @coderifts/agent-hooks 0.3.2 (npm 2026-10-06 19:55 UTC), @coderifts/contract-path 1.2.0 (npm 2026-10-06 19:58 UTC), contract-gate v0.11.3 (published 2026-10-06 19:50 UTC; the v0 tag is the same commit 97d4cea), and the App required check (commit 4d231d9, deployed 2026-10-06 13:26 UTC). The fix reaches an install only after that install is updated.

P65c — 2026-10-08

P65c — 2026-10-08. A plain mcp.json is decided by its content. The P65 fix (2026-10-06) took every file named mcp.json as an MCP client configuration, by name. That stopped the leak, but it also stopped checking a server's own tool manifest of that name, coderifts.com/mcp.json included. Since the dates below, the names that are only ever a client configuration stay skipped and are never read: .mcp.json, .cursor/mcp.json, .vscode/mcp.json, claude_desktop_config.json and (cline_)mcp_settings.json. Any other mcp.json is read where it already is and decided by its content: mcpServers or servers and no tools is a client configuration, never sent; tools, any other shape, or text that does not parse, is checked as a contract. Fixed in: @coderifts/contract-path 1.3.0 (npm 2026-10-08 07:47 UTC); @coderifts/agent-hooks 0.3.3 (npm 2026-10-08 08:12 UTC); contract-gate v0.11.4 (published 2026-10-08 08:15 UTC, commit 9359e7fdb8af386fa74266e3b48252c2b1cdf6f7; the v0 tag points at that same commit); CLI 8.7.4 (npm 2026-10-08 08:26 UTC, gitHead 8bcb356b75331ecd5e6b2daff7fd3c78d182d03e); the GitHub App required check (live no later than 2026-10-08 09:50 UTC (production deployment of def4cec18782, which contains the content-rule commit 64e904e)); api-governance 1.2.8 (tag v1.2.8, commit 9411e6bb52192155a624224f124cc49a9f1aa2c6, tagger 2026-10-08 20:18 UTC). From 2026-10-06 until, at the latest, the time named on each line, that entry point did not check a contract change in a file named mcp.json. The P65 measurement is unchanged: no client configuration was sent after 2026-10-06. Each fix reaches an install only after it is updated: a global CLI, agent-hooks, the plugin, and an Action pinned by SHA. The v0 tag points at the v0.11.4 commit.

Check-run 113511365478 (CodeRifts / issuer) decided a change to coderifts.com's mcp.json on closed draft pull request coderifts/website#2. The pull request head file is 68,483 bytes. The live file is 81,018 bytes. The check started 2026-10-08 19:54:53 UTC and completed 2026-10-08 19:54:56 UTC, 3 seconds. The title is STOP · merge · sha256:14e93d4…4cef5d · 2026-10-08T23:54:56Z · ENDPOINT_REMOVAL. That output does not name an engine_build_id.

P65d — 2026-10-09

P65d — 2026-10-09. A plain mcp.json that is both a client configuration and a tool manifest, or that does not parse, is not read into a contract and is not sent. By name, .mcp.json, .cursor/mcp.json, .vscode/mcp.json, claude_desktop_config.json and (cline_)mcp_settings.json are client configurations and are never read. Any other mcp.json is read where it is and decided by its content. mcpServers or servers and no tools is a client configuration, counted as no file, and never sent. Anything else is a contract: a tools key, an object without a server list, or a parsed value that is not an object. Both at once, or text that does not parse, is not read into a contract and is not sent. The hooks ask. The gate and the App give a decided red with one sentence. For both at once the sentence is the file's path followed by "is both an MCP client configuration and a tool manifest; it is not read or sent — split the server list and the tool manifest into separate files." For text that does not parse the sentence is the file's path followed by "does not parse as JSON; it is not read or sent — a client configuration may hold credentials. Make it valid JSON (no comments) to have it checked." The same rule is in the contract-gate Action v0.11.5 (published 2026-10-09 14:25 UTC, commit 8542c07ad021b41e062278d64aa9e88473f004ff; the v0 tag points at that commit), the App webhook on the production deployment of 3a9974895d0a (succeeded 2026-10-09 15:22 UTC), the coderifts CLI 8.7.5 hook (npm 2026-10-09 15:07 UTC, tag cli-v8.7.5, commit aecc300ebab998f52433dd69b56f078b85f330e5), @coderifts/agent-hooks 0.3.4 (npm 2026-10-09 14:31 UTC) and later, and the Claude Code plugin mod api-governance 1.2.9 (tag v1.2.9, tagger 2026-10-09 16:22 UTC, commit 437e397ea67980d015f75b5a4a1952bc6aa0d6d0). @coderifts/contract-path 1.3.1 was published on npm at 2026-10-09 14:23 UTC. From the P65c time on each entry point (2026-10-08) until, at the latest, the time named for it above, a plain mcp.json that was both, or that did not parse, was checked as a contract, and its text was sent to the check. This round did not measure whether any such file was sent. Each fix reaches an install only after it is updated. The production deployment of 3a9974895d0a went inactive at 2026-10-09 16:08 UTC, when 5a02cb3ca4a1, which contains it, succeeded. A keyless analyze that removed a tool from an mcp_manifest, posted to https://app.coderifts.com/api/v1/preflight on 2026-10-09, returned BREAKS_DETECTED with engine_build_id 5a02cb3ca4a1. That call is not the mixed-file webhook.

Check-run 113892360938 (CodeRifts / issuer) on closed draft pull request coderifts/website#3 returned conclusion failure. The check started 2026-10-09 15:27:18 UTC and completed 2026-10-09 15:27:20 UTC, 2 seconds. The title is Not checked · mcp.json is both an MCP client configuration and a tool manifest; it is not read or sent — split the server list and the tool manifest into separate files. That is a decided red, not a timeout. The check does not name an engine_build_id. At that clock time the production deployment of 3a9974895d0a was the one in the success state. Measured by us on our own probe, not by an external user. The pull request was closed without merging.

@coderifts/agent-hooks 0.3.5 (npm 2026-10-09 16:30 UTC). The hook runs when invoked through a symlinked path, for example a package-manager layout. Before 0.3.5 it exited without a decision there. This affects the agent-hooks package only.

Package versions named here are tagged on release day.

P66 — 2026-10-12 · Keyless callers received signed receipts.

`POST /api/v1/verdict`, `/api/v1/action-verdict`, `/api/v1/diff`, the agent preflight and `mcp-diff` signed a `chain_receipt` for a caller without an API key (measured live: v2 receipt, kid `2026-07-k1`, `caller: "anon"`). A signed receipt was therefore no proof that a known caller asked. In addition, `contract-gate` and `@coderifts/receipt-verifier` accepted receipts below v4. Those versions do not bind the request envelope and carry no expiry. A decision could also be read by id without a key, across tenants. Fixed: receipts are issued only to verified callers. contract-gate 0.11.9 accepts only a v4 receipt bound to its envelope with a readable expiry. receipt-verifier 1.1.1 reports `RECEIPT_VERSION_UNSUPPORTED` for v<4 and `MALFORMED` for a non-string signed field. Decision lookup needs a key and returns only the caller's own tenant's rows. Affected: any check that treated a v1–v3 receipt, or a receipt with `caller: anon`, as authorization. Action: upgrade contract-gate and the verifier; re-run any verification that relied on a pre-v4 receipt.

P67 — 2026-10-12 · Protobuf changes the engine did not see.

A field written with options (`= 3 [deprecated = false];`) was not parsed, so removing it or changing its type answered ALLOW with a signed receipt. Comments were parsed as code: an rpc commented out was not "removed", and a commented message looked real. A package rename, enum renumbering, singular↔repeated, leaving a oneof and a new proto2 `required` field also answered ALLOW. Fixed in the engine; each of these is now a named breaking change.

P68 — 2026-10-12 · A malformed contract counted as analyzed.

A gRPC or AsyncAPI side that did not parse as a schema, or was not one ("hello", `{}`, a YAML list), was treated as an empty contract. A broken base hid real removals behind ALLOW; a broken head invented removals. Fixed: such a side is `ANALYSIS_FAILED`, which blocks. A failed analysis is not a result.

P69 — 2026-10-12 · Cross-tenant ledger writes and server-side fetches of caller URLs.

`POST /api/v1/ledger/:id/approve` and `/override` looked up a ledger entry by id alone, so a key from another tenant could approve or override it. Five routes fetched any URL the caller named, including link-local metadata (169.254.169.254), loopback and private ranges: `score-mcp`, `sdk-sim`, `hallucination-risk`, `notifications/test` and `shadow-fix/report`. `notifications/test` returned the target's response body. Fixed: ledger approve and override are scoped to repositories bound to the caller's tenant; a foreign id reads as not found. Every caller-URL fetch passes one guard: http(s) only, no credentials, every resolved address public, metadata names refused, each redirect re-checked. Response bodies are no longer echoed. Residual: DNS rebinding between the check and the connection.

P70 — 2026-10-12 · Renames and the reader's control plane.

The merge gate classified a renamed file by its new name only. A contract renamed with a breaking change read as a new artifact. A contract moved to `.bak` or into `vendor/`, or the gate's own workflow, `.coderifts.yml` or a signing key moved out of place, read as no change. The control-plane list missed `keyring/pinned-keys.json`, root `action.yml` and `.github/actions/**`. The reader could not send control-plane files, so its receipts bound an empty set. Fixed: a rename is a change of the class it leaves, the list covers the gate's trust and code paths, and the reader's control-plane artifacts are analysed when the request binds repository, base and head.

P71 — 2026-10-12 · Local hook fail-open branches.

The installed Claude Code hook command was `coderifts claude-hook`. Claude Code proceeds on every exit code other than 2, so with the binary missing (exit 127) or a crash (exit 1) every write went through, while `coderifts status` still said `Runtime PREVENTS`. A `--log` path with a space broke the command. A legacy-format decision such as `ESCALATE_TO_HUMAN` or lowercase `block` was treated as allow by the hook and the pre-push hook. The pre-push hook truncated a spec containing \c and put the API key on the curl command line. Self-protection did not cover `git config coderifts.specPathExclude` or `env.CODERIFTS_API_BASE` / `CODERIFTS_MONITORING_SINK_WIRED`. Fixed in coderifts 8.7.6: the command ends in `|| exit 2` and quotes its path (existing installs are upgraded by `coderifts hook install --claude`), `status` reports `BINARY_MISSING`, unknown decisions block, the pre-push hook sends the spec and key through 0600 files, and self-protection refuses these switches. Action: upgrade and re-run `coderifts hook install --claude`.

Public beta — what we store, how long, and how to get rid of it

Measured on 2026-09-22 against the application source. Every claim below names the file and line it came from, so you can check it rather than take it.

What a usage event contains

Usage is recorded as a metering event. The table takes four columns — workspace_id, event_name, event_json, created_at (src/metering/shadow-event.js:68–70). The workspace_id written there is a hash, not your organisation name (shadow-event.js:23–25, hashIdentity).

The event itself is validated against a closed schema with additionalProperties: false (schemas/metering-event.v1.json). Thirteen fields are permitted and nothing else: event_name, workspace_hash, integration_source, invocation_kind, timestamp, correlation_id, outcome, reason_code, supported_scope, artifact_type, case_id_hash, mcp_tool_name, source_id. source_id was appended on 2026-09-23. It names an acquisition channel. It is not permission, and it is not a rename of integration_source.

We do not train models on your contract artifacts, and agent traffic leaves a metering event, not a payload. Measured 2026-09-23 across the whole application source: zero occurrences of fine-tuning, training-data collection, embeddings, a vector store, or retrieval-augmented generation. There is no model here being trained on anything. What agent traffic leaves behind is a metering event, and that event cannot carry your content — which is the next paragraph, and it is a property of the code rather than a promise about our intentions.

No payload, and it is enforced twice. The writer refuses a set of keys outright — payload, spec_body, before, after, email, contract, body, artifacts (src/metering/shadow-event.js:19–21) — and the closed schema rejects anything else that is not one of the thirteen. A spec body cannot reach a usage row by accident.

How long it is kept

90 days (src/db/retention.js, RETENTION_DAYS = 90). A daily sweep deletes rows older than that from eleven tables. pr_analyses joined that list on 2026-09-25. Until then the sweep covered preflight_logs and audit_log and did not cover the table GET /api/v1/timeline read, so the retained history there was the whole table. The eleven are pr_analyses, preflight_logs, audit_log, decisions, decision_authorized_paths, agent_usage_observations, instability_scans, ops_surface_events, analysis_attempts, metering_events, and repository_visibility_observations.

⚠ One table is not on that sweep, and we say so rather than let you assume it. authorization_case_ledger — one row per authorization case — has no retention rule. It is absent from the list in src/db/retention.js. It carries no spec content by design (the table has no payload column, and the writer refuses the same forbidden keys), but it is not deleted on a schedule today.

How to stop it and remove it

  1. Uninstall the GitHub App. Remove coderifts from your organisation’s installed apps. New pull requests stop being analysed.
  2. Revoke your API key. The key endpoint revokes by prefix or by full key (src/routes/keys.js:204–257). A revoked key stops authorising immediately.
  3. Ask us to delete the workspace. Email hello@coderifts.com from an address on the organisation. ⚠ This step is a request, not a button. There is no self-serve workspace deletion, and several tables reference workspaces with ON DELETE RESTRICT (src/db/migrate.js:957, 1073, 1120), so the rows are removed deliberately rather than cascaded away.

Who to mail

hello@ — support and general · security@ — as in security.txt · peter@ — founder.

⚠ No response time is promised. This is a public beta and there is no support agreement behind it. If a reply matters to you on a deadline, say so in the message.

What we do not promise during beta

  • No SLA. GET /api/v1/sla/status reports targets, not guarantees. The field that once read guaranteed_uptime_pct was renamed for exactly that reason (src/routes/sla.js:14–26). There is no credit, and no artifact recording a breach.
  • No retention guarantee. 90 days is what the sweep does today, not a commitment about tomorrow. The authorization_case_ledger exception above is part of that.
  • No statement. No SOC 2, no ISO certificate, no signed data-processing statement. What this page gives you instead is the file and line for each claim.
  • No billing during beta. Beta organisations run at $0; see Pricing for what that includes and what it does not.

What this page does not cover yet

Everything above is about one of the three ways CodeRifts touches your work: the GitHub App. The other two are not described here, and if you are installing an agent adapter this is where you would look for them.

  • The hosted MCP server. app.coderifts.com/mcp receives the change set you send it — the before and after of the artifacts, not your repository. initialize and tools/list take no key; tools/call does. The MCP page lists the three tools and the published surface digest.
  • The host adapters (agent-hooks). These run on your machine, not ours. A PreToolUse matcher reads the file an agent is about to write before the write happens. The install page states, per host, what each adapter enforces and what it does not see. @coderifts/agent-hooks 0.3.1. A write to a named contract file is denied. A write that can reach a contract file without naming it asks, and it asks only when a contract file is under that directory. Published 2026-10-04. When CodeRifts could not decide, the refusal says GOVERNANCE_UNAVAILABLE: CodeRifts could not decide (why); this is not a finding about your change. The api-governance 1.2.3 mod still does not inspect shell commands.
  • The keyless endpoints. /api/v1/action-verdict and keyless preflight answer without an account, and return a signed receipt anyone can verify offline. A caller with no key is still a caller: what is retained for those requests is not described on this page.

Listed as a gap rather than left silent. Measured 2026-09-14.

Infrastructure

Backend: Railway

The region is an operational fact. This page will not name US or EU until a console or API readout is dated here.

Website: Cloudflare Pages

Static site served from Cloudflare's edge network.

No third-party analytics on API traffic

Read that scope literally. This site — including the page you are on — loads Google Analytics and Cloudflare web analytics; the privacy notice names both. What carries no third-party analytics is the API: requests to app.coderifts.com, the MCP server and the keyless endpoints are not sent to an analytics vendor.

API requests are not tracked by any external analytics service.

API keys hashed at rest

API keys are stored as salted SHA-256 hashes (prefix + hash + salt). Plaintext keys are never stored. This is authentication data at rest; the Privacy page names the same store. A caller with no key is still a caller — keyless retention is a separate row, not this one.

Licensing

Legal review pending. Measured 2026-09-28 from the local checkouts. This is the file state, not a lawyer's opinion. The lawyer is not this page.

A receipt is one line in a change-management process you already run. The reading of that line against DORA, the AI Act and NIS2, and what it does not cover, is the record-keeping page.

The public components below carry an MIT LICENSE. The hosted app is closed source. There is no source-review licence. 13 MIT repositories; the hosted app is closed source; third-party notices where third-party code ships. The hosted app's runtime closure is 211 third-party packages: 182 MIT, 13 ISC, 8 BSD-3-Clause, 7 Apache-2.0 and 1 Python-2.0, with no copyleft-class licence. Since 2026-10-01 that repository carries a proprietary LICENSE and a generated THIRD_PARTY_NOTICES.md, kept current by the same fleet generator and guard as the public repositories.

A notices file is required only where third-party code ships. Dev dependencies and @coderifts packages do not get one. An empty notices file is not a substitute. The filename in these repositories is THIRD_PARTY_NOTICES.md.

Repository LICENSE Notices
receipt-verifierMITnone ships; no notices file
sdkMITnone ships; no notices file
coderifts-conformanceMITruntime dependencies are first-party; no notices file
coderifts-contract-gateMITdev dependencies only; no notices file
coderifts-gateway-verifierMITno dependencies; no notices file
coderifts-agent-guardMITdev dependencies only; no notices file
api-governanceMITnone ships; no notices file
coderifts-k8s-admissionMITnone ships; no notices file
coderifts-agent-hooksMITnone ships; no notices file
coderifts-python-verifierMITTHIRD_PARTY_NOTICES.md (cryptography, cffi, pycparser, typing-extensions)
coderifts-python-sdkMITTHIRD_PARTY_NOTICES.md (requests, charset_normalizer, idna, urllib3, certifi MPL-2.0)
coderifts-example-langgraphno LICENSE fileTHIRD_PARTY_NOTICES.md is present. The LICENSE file is the gap.
coderifts-appproprietaryTHIRD_PARTY_NOTICES.md names the 211 third-party packages.

Compliance

✓

GDPR

CodeRifts runs on read-only GitHub access and analyzes in memory. We persist derived verdicts, risk scores, structural identifiers and repository/PR metadata — not your schema bodies or source code. Request logs retain only truncated SHA-256 IP hashes, kept 90 days.

○

SOC 2

No report. No third-party assessment is published. Status is not a report.

✉

Security contact

security@coderifts.com

Uptime

✓

Auto-deploy on main branch

Backend hosted on Railway with automatic deployments from the main branch.

○

No formal SLA yet

Targeting 99.9% uptime.

During the public beta every organization gets Team features at $0 with a self-serve key, public and private repositories alike, no separate Team key to request, and each key allows 1,000 requests a month: after that, /preflight authorize returns BETA_LIMIT_REACHED while its analysis keeps working; other API routes return 429.