Reporting
Email security@gowarrant.xyz. Include enough to reproduce it: the endpoint or screen, the steps, what you expected, what happened. A proof of concept helps. If you have a request id from an error, send it — every API error carries one and it takes us straight to the request.
If you would rather encrypt, say so in a first message with no detail and we will send a key.
Please report privately first. We will not take legal action against anyone who follows this policy in good faith, and we will not ask you to sign an NDA to receive a fix.
What we will do
| Stage | Target |
|---|---|
| We acknowledge your report | 1 working day |
| We tell you whether it is confirmed, and its severity | 5 working days |
| Critical or high, fixed or mitigated | 14 days |
| Medium or low, fixed | 90 days |
| Public disclosure | Coordinated with you after the fix |
If we are going to miss one of those, we will tell you before it lapses rather than after. Anything touching held funds, authentication or cross-tenant access is treated as critical by default until shown otherwise.
We will credit you when we publish, unless you would rather we did not. There is no paid bounty at this stage; if that changes it will be stated here.
In scope
gowarrant.xyz,console.gowarrant.xyz,api.gowarrant.xyz,docs.gowarrant.xyz- the public trace receipts at
/t/<warrant id> @warrant/sdk, including the MCP server and the CLI- the verifier and policy engines, and the state machine
- the double-entry ledger
Findings we care about most, because they map to how this system can actually hurt someone:
- Cross-tenant access. Reading or changing another organisation's warrants, evidence, keys or balances.
- Authentication and authorisation. Session forgery or fixation, a viewer performing an operator action, an API key reaching a route that requires a person, a demoted or removed member still acting.
- Anything that moves money incorrectly. A double release, a release without a passing check, a settlement that leaves the ledger unbalanced, a warrant that can be settled twice through an idempotency race.
- Funds stranded. Any path that leaves a warrant held forever with no route to release, refund or expiry.
- Evidence integrity. Making a failing check look like a passing one, forging an evidence hash, or getting a third-party payload written into storage that should only ever hold a hash and a verdict.
- Trace integrity or leakage. Publishing an unsettled warrant, exposing a redacted actor id, or altering a published receipt.
- Passkey bypass. Settling above the configured threshold without a valid assertion, replaying a captured assertion, or using a credential registered to another user.
- Webhook signature bypass. Forging a signature, or replaying a delivery outside the timestamp tolerance.
- Secrets in a client bundle. Any server-only value reaching the browser.
Out of scope
- Reports from an automated scanner with no working proof of concept
- Missing headers or a weak TLS suite with no demonstrated impact
- Rate limiting on non-authenticated, non-money endpoints
- Social engineering of our staff or users, and physical attacks
- Denial of service, volumetric testing and stress testing
- Self-XSS, clickjacking on a page with no state-changing action, missing SPF or DMARC on a domain that sends no mail
- Anything requiring a rooted or compromised device the user already controls
- Vulnerabilities in a third party's service, which should go to that third party — though tell us too if it affects Warrant users
Do not test with real funds, yours or anyone else's. Sandbox is fully functional, uses the same code path and the same ledger, and is where this testing belongs. Do not access, modify or exfiltrate data belonging to another organisation; if you find you can, stop, and describe how.
How Warrant is built
Context for anyone looking, and a statement of what we already consider settled:
- Roles are enforced server-side on every route, not hidden in the interface. The interface hides what you cannot do because showing it would be dishonest, not because that is the control.
- A demotion or removal takes effect on the next request. The role is read fresh from the database each time, not baked into the session at sign-in.
- API keys cannot approve payments. A key opens and verifies warrants. Settling requires a signed-in person; a key attempting it gets
user_session_required. - Sessions are opaque random tokens, stored as SHA-256. The cookie is
httpOnly; the console holds it on its own origin and forwards it, so it never reaches client JavaScript. - API keys are stored as SHA-256 with a constant-time comparison. SHA-256 rather than a KDF is deliberate: a key is 32 random bytes with no guessable distribution, unlike a human-chosen password.
- Every money-moving endpoint requires an
Idempotency-Key, writes an audit row and a trace row before it executes, and holds a lock so a concurrent replay conflicts instead of double-spending. - State changes go through one
transition()function with an optimistic lock on a version column. Illegal transitions are refused, and every illegal pair is covered by a test. - The ledger is double entry. Every movement nets to zero across its legs, and the balance shown is a projection of it rather than an independent number that could disagree.
- Webhooks are signed with HMAC SHA-256 over
timestamp.body, with a five-minute tolerance, capped retries and a delivery log. - Above a configurable amount, settling requires a WebAuthn passkey. Challenges are scope-bound and single-use, and the signature counter guards against replay.
- Confidential compute. The deliverable is fetched and checked inside REX. The agent's own report of its work is never the evidence.
Known gaps
Stating these is more useful than implying they do not exist. All are open and tracked.
- Evidence is not expired on a schedule. The
expires_atcolumn exists and nothing populates it, so evidence rows persist with their warrant. The row holds a hash and a per-clause verdict, never the payload. See Privacy. - Traces are not yet anchored on chain.
chain_refis null and receipts are served from our database, so today a receipt is public but not yet immutable. - No per-request log. We cannot currently reconstruct a request-level history for an incident beyond the audit trail and our providers' logs.
- Production WebAuthn verification is not wired. The sandbox verifier is dev-only and gated behind an unconfigured Privy plus a sandbox environment. In production, with no verifier configured, an above-threshold settlement is refused rather than allowed — it fails closed.
- No formal penetration test or third-party audit yet. This document will say when there has been one.
Not yet in production
Warrant has not been reviewed by a lawyer, has not been audited, and is not authorised as a payment service. Do not put real funds through it. Sandbox exists for everything else.