Security Practices
Kronos is designed with defense-in-depth. No API keys to leak, no credentials to steal — payments are on-chain and trustless.
How does authentication work without API keys?
Kronos uses x402 micropayments instead of API keys. The payment itself is the authentication — if you can pay, you can access. No keys to leak, rotate, or revoke. Free endpoints need no payment at all.
What are the API rate limits?
Rate limiting is applied per IP and, where applicable, per payer wallet. GPU-intensive and abuse-sensitive endpoints have additional wallet-level controls. Free endpoints (e.g. /decision/:id, /risk, /catalog) are rate-limited per IP only.
| Endpoint | Limit | Scope |
|---|---|---|
| Predict (cached) | 10/min | Per IP + 20/min per wallet |
| Predict (?refresh=true) | 2/min | Per IP (refresh) + 20/min per wallet |
| Forecast distribution | 10/min | Per IP + 20/min per wallet |
| Playground | 3/5min | Per IP + 3/5min per wallet |
| Market brief / Digest | — | 10/min per wallet (GPU-intensive) |
| Paid endpoints (default) | — | 30–60/min per wallet (varies per endpoint) |
| On-demand discovery | 10/hour | New symbols per IP |
| Decision by ID (free) | 30/5min | Per IP only (no wallet) |
| Free endpoints (risk, catalog, previews) | 30/min | Per IP only (no wallet) |
How is input validation handled?
- All query parameters are validated against endpoint-specific bounds (e.g.
/decisions:limitmax 100 (20 with?detail=1),/similar-markets:limitmax 50,textmax 2000 chars,/rationale-novelty:limitmax 100,threshold0–1,/playground:sample_count1–100,temperature0–2,top_p0–1) - Negative, NaN, Infinity, and overflow values are rejected or clamped
- UUID parameters are validated against the UUID format
- Symbol parameters are validated against the curated catalog or the supported on-demand provider symbol rules (Gate.io), depending on the endpoint
- SQL injection is prevented via parameterized queries (
$1, $2, ...)
How does error handling prevent information leakage?
Error responses never expose internal details:
- No stack traces, file paths, or internal hostnames in responses
- No SQL queries or database error messages in responses
- No API keys, provider names, or connection strings in responses
- Errors return a generic error code (e.g.
{"error":"internal_error"}) - Full error details go to server logs only
- Global Express error handler as safety net
CORS and security headers
| Header | Value |
|---|---|
| Content-Security-Policy | default-src 'self'; inline styles + Google Fonts allowed; frame-ancestors 'none' |
| X-Frame-Options | DENY — no clickjacking |
| X-Content-Type-Options | nosniff |
| X-XSS-Protection | 0 — disabled (modern browsers use CSP instead) |
| Strict-Transport-Security | max-age=31536000; includeSubDomains; preload |
| Permissions-Policy | Camera, microphone, geolocation disabled; payment=(self "https://kronos.seshat.markets") |
| Referrer-Policy | strict-origin-when-cross-origin |
CORS policy
- Public discovery artifacts (
/kronos/openapi.json,/kronos/registry.json,/kronos/skill.md,/kronos/llms.txt,/.well-known/x402,/openapi.json,/llms.txt,/skill.md):Access-Control-Allow-Origin: * - API endpoints: same-origin only (browser CORS restriction — does not affect server-to-server calls from
curl, Node, Python, or LLM agents, which bypass CORS entirely) Access-Control-Allow-Credentials: false— no cross-origin credential leaksAccess-Control-Allow-Headers:Content-Type, Authorization, X-Admin-Key, X-PAYMENT, PAYMENT-SIGNATUREAccess-Control-Expose-Headers:PAYMENT-REQUIRED, PAYMENT-RESPONSE, X-PAYMENT-RESPONSE, WWW-Authenticate- Session cookies:
httpOnly,sameSite: 'lax'
SSRF protection
Outbound HTTP calls (to data providers, agent endpoints, webhooks) are protected against SSRF:
- DNS resolution checked against blocked IP ranges (RFC 1918, loopback, link-local, cloud metadata
169.254.169.254) - Literal IP addresses checked directly at registration time
- Redirects set to
manual— no redirect chains followed (prevents redirect-based SSRF bypass) - Re-resolved at fetch time — closes the DNS-rebinding gap (hostname that was public at registration but resolves to private later)
API inventory
Every endpoint is explicitly classified as PUBLIC (free, no auth), PUBLIC + PAID (x402 micropayment), or AUTH (requires writeKey).
Responsible disclosure
Found a vulnerability? Email admin@seshat.markets. We respond within 48 hours.
Frequently asked questions
Is the Kronos crypto API secure without API keys?
How does the crypto API prevent SQL injection?
$1, $2, ...) — user input is never concatenated into SQL strings. Additionally, all query parameters are validated against endpoint-specific bounds (e.g. /decisions limits limit to 100, /similar-markets limits text to 2000 chars, /playground limits sample_count to 1–100).What security headers does the Kronos API use?
default-src 'self', X-Frame-Options DENY, Strict-Transport-Security with preload, X-Content-Type-Options nosniff, and Permissions-Policy disabling camera, microphone, and geolocation. See the security page for the full list.Does the crypto API leak error details?
{"error":"internal_error"}. Full error details go to server logs only.How is SSRF prevented in the crypto API?
manual, and DNS is re-resolved at fetch time to close the DNS-rebinding gap.