Security
Origin binding
Which origins may create challenges for a site key, how origins are normalised, and how the origin is carried into proofs and tokens.
Rules
POST /api/v1/challenges requires an Origin header (browsers send it automatically for cross-origin fetches). The origin is accepted when any of these hold for the site whose key was used:
- It is exactly
https://<domain>. - It is
https://<anything>.<domain>(any depth of subdomain). - It equals one of the site's explicitly allowed origins after normalisation.
Rules 1 and 2 require https and no explicit port. Development origins such as http://localhost:5173 must be listed under allowed origins (up to 20 per site).
| Site domain | Origin | Allowed |
|---|---|---|
| example.com | https://example.com | Yes |
| example.com | https://shop.example.com | Yes |
| example.com | https://a.b.example.com | Yes |
| example.com | http://example.com | Only if listed explicitly |
| example.com | https://example.com:8443 | Only if listed explicitly |
| example.com | https://example.com.evil.net | No |
| example.com | https://notexample.com | No |
| example.com | http://localhost:5173 | Only if listed explicitly |
Normalisation
An origin is parsed as a URL and reduced to scheme://host[:port]. Values with a path, query, fragment or credentials, or a scheme other than http/https, are invalid and rejected with ORIGIN_REQUIRED (missing/invalid header) or ORIGIN_UNAUTHORIZED (valid but not allowed). Site domains are lowercase hostnames without scheme, path or port; a bare label without a dot is refused unless it is localhost.
Where the origin goes
- Stored on the challenge as
originand covered by the signature base (@origin). - Copied into the token as
inh.originand returned bytokens/verifyasverification.origin. - Recorded on every verification event.
Creating challenges server-to-server
If your backend creates challenges (for a non-browser flow), send an Origin header yourself with an allowed value. The origin is then what agents and tokens will see.
curl -X POST https://imnotahuman.ai/api/v1/challenges \
-H 'origin: https://example.com' \
-H 'content-type: application/json' \
-d '{"site_key":"site_live_exampleweb2ab3cd4ef5gh6j","action":"api_access","ttl":300}'CORS
Browser calls send an Origin. Public routes reflect that origin in Access-Control-Allow-Origin when it is https on a normal site, or http on localhost. Operator routes (/me, /sites, /agents, /dashboard, /events) reflect only https://imnotahuman.ai and local development. Infrastructure hosts are never reflected, and credentialed CORS is not enabled. Requests without an Origin — server SDKs and curl — are unchanged, because CORS does not apply to them. Allowed request headers are authorization, content-type, x-request-id, signature, signature-input, signature-agent and content-digest. A reflected CORS origin does not authorize a site; the challenge endpoint still checks the site's allowed origins.