Security & credentials

Where every secret lives, what crosses the network, and what the operator can and cannot see.

The short version

  • Your cases execute on your own machine or CI runner. Credentials, fixtures, and response bodies stay there.
  • By default only report metadata is uploaded — the ingest contract has no field that can carry a credential.
  • Evidence upload is opt-in, and its redaction runs in your CLI before anything is sent.
  • Optionally storing credentials in the hosted platform (a Paid convenience) encrypts them at rest under keys the operator manages but cannot use to read your plaintext out of a database dump alone.

Credentials in case files: ${ENV_VAR}, never literals

Case files are meant to be committed to your repo. They never contain secret values — only ${ENV_VAR} references, resolved from the environment when the case loads:

cases/order.yaml
pipeline:
  - operation: http.request
    with:
      url: ${API_BASE_URL}/orders
      headers:
        Authorization: Bearer ${API_TOKEN}
  • The $${...} pattern accepts only A–Z, 0–9 and _.
  • A reference to an undefined variable is a hard load error — the case does not run with a blank or literal placeholder.
  • Fixture locators are path-contained: no .., no absolute paths, no escaping the fixtures/ root. The file is read locally and verified by SHA-256 before the pipeline runs.

API tokens

A project token is what the CLI presents to upload results (and, if you use them, to fetch stored secrets). Issued from the dashboard:

  • Format rtw_ followed by 256 bits of cryptographic randomness.
  • The server stores only a SHA-256 hash of the token, plus a short display prefix. The raw value is shown once, at creation, and never again.
  • Scoped to a single project — a token issued for project A cannot read or write any other project's data, including other projects in the same organization.
  • Revocable from the dashboard; a revoked token is rejected immediately.
  • A token and a web-session credential (a Clerk JWT) are different auth domains: a web JWT cannot call the ingest API, and an API token cannot act as a web session.

Keep the token in your CI's secret store and pass it as RELEASETWIN_API_TOKEN. Traffic to the hosted API is HTTPS.

What is uploaded, and what cannot be

Default upload — metadata only:

  • case ID, oracle reference, fixture hash, pass/fail, failure classification, cleanup status, timing
  • the paired-leg summary for a flag-proof run

This is a property of the contract, not a policy: the ingest payload schema defines no field capable of carrying fixture content, operation response bodies, or a credential. A malformed payload is rejected in full, with nothing partially stored.

Evidence redaction (opt-in)

Turn on evidence capture per project and a run also produces a structured document — per-step request/response summaries, assertion path / expected / observed, UI screenshots. Before any of it is uploaded, the CLI redacts it on the machine that ran the case. Un-redacted evidence is never transmitted under any configuration.

Redaction is a three-layer model, applied in order:

  • Built-in denylist — removes Authorization and Cookie headers, credential-shaped fields, and any value equal to a secret or token that was resolved during the run (so a secret that echoes back in a response body is masked there too).
  • Per-case denylist — additional field names, headers, JSONPath expressions, or UI selectors/regions you name.
  • Per-case allowlist — lets you keep a specific field a built-in rule would otherwise drop. It cannot re-enable anything the built-in denylist removed.

The redactor fails closed: a rule it cannot evaluate results in masking, not exposure. The ingest API stores the document opaquely — it never inspects or re-strips it — under a per-project retention window (default 30 days, max 365), and a daily purge deletes expired evidence while leaving the metadata report intact.

Stored credentials & project secrets (Paid convenience)

Rather than wiring the same environment variables everywhere the CLI runs, you can store adapter credentials and arbitrary named secrets per project through the dashboard. The CLI then fetches them at run time using its project-scoped API token. This is entirely optional — the ${ENV_VAR} path never goes away.

  • Values are encrypted at rest with ASP.NET Core Data Protection. The key ring is persisted to AWS Systems Manager Parameter Store, not to the application database.
  • Adapter credentials, project secrets, and connection state each use a separate protector purpose, so a payload from one can never be decrypted as another.
  • Once set, a value is never redisplayed — the dashboard only shows that a credential exists and its non-secret metadata.
  • Rotate or revoke any time, without operator involvement. A revoked value is not returned by a later CLI fetch.
  • Storing secrets requires the Paid tier; the failure is distinguishable from an auth error.

What the operator can and cannot see

  • Cannot see: your fixtures, request and response bodies, or any un-redacted evidence — none of it leaves your infrastructure.
  • Cannot recover from a database dump alone: your API tokens (only hashes are stored) or your stored secret values (ciphertext; keys live in a separate Parameter Store path).
  • Can see: the metadata you upload — case IDs, hashes, pass/fail, classifications, timing — and, if you enabled it, the evidence document exactly as your CLI redacted it.
  • Sign-up needs no human approval; there is no operator in your critical path.

Continuity — what happens if we stop

ReleaseTwin is built and run by a very small independent team. The design makes that a non-issue for your releases:

  • The CLI, execution kernel, and adapters are open source (AGPL-3.0) and run entirely in your own infrastructure. They keep working with no account and no network call to us — a hosted outage, or the hosted platform going away entirely, never blocks a release.
  • An organization admin can download the full run history and stored evidence at any time from the dashboard, as a single ZIP with a documented format — no proprietary lock-in on the data itself.
  • If we wind the company down, active hosted licenses convert to perpetual for their remaining term and the hosted source is published so a customer or third party can self-host it.
  • Payments run through a Merchant of Record (Polar): card and billing-address data are entered only on Polar’s hosted checkout and portal, never seen or stored by us, and Polar issues invoices and remits sales tax. A lapsed subscription degrades hosted entitlements on a published grace schedule but never deletes your uploaded evidence.

This is a deliberate commitment, not just a side effect of open-sourcing the core — it is the answer to the fair question “what if this two-person company disappears.”

Reporting a vulnerability

Email ernestoalejo22@gmail.com with details and a proof of concept. Please don't open a public issue for a security report.

See also Hosted platform for the upload flow end to end.