Security
Security is built into the OpenScouter infrastructure at multiple layers. This document covers HTTP headers, CORS policy, rate limiting, input validation, SQL injection prevention, and XSS prevention. It also describes the vulnerability disclosure process.
HTTP Security Headers
Every response from the OpenScouter API and web application includes a standard set of security headers. These are applied by Next.js middleware on all routes.
| Header | Value | Purpose |
|---|---|---|
Content-Security-Policy | See below | Restricts resource loading to trusted origins |
X-Frame-Options | DENY | Prevents clickjacking by blocking iframe embedding |
X-Content-Type-Options | nosniff | Prevents MIME-type sniffing |
Referrer-Policy | strict-origin-when-cross-origin | Limits referrer information sent to third parties |
Permissions-Policy | camera=(), microphone=(), geolocation=() | Disables browser features not needed by the application |
Strict-Transport-Security | max-age=63072000; includeSubDomains | Enforces HTTPS for 2 years |
Content Security Policy
The CSP is constructed to allow only the resources required for the application to function. The policy varies between the web application and the API.
Web application CSP:
default-src 'self';script-src 'self';style-src 'self' 'unsafe-inline';img-src 'self' data:;connect-src 'self' wss://realtime.supabase.co;font-src 'self' https://fonts.gstatic.com;frame-src 'none';object-src 'none';base-uri 'self';'unsafe-inline' for styles is an accepted tradeoff for compatibility with CSS-in-JS libraries. Inline scripts remain prohibited.
API CSP:
default-src 'none';frame-ancestors 'none';The API serves no HTML content, so a restrictive CSP blocking all resources is appropriate.
CORS Policy
Cross-Origin Resource Sharing is configured to allow requests only from known origins. The allowed origins list is managed via environment variables and is different across environments.
The CORS middleware performs the following checks on every preflight and credentialed request:
- The
Originheader must match an entry in the allowed origins list - Allowed methods:
GET,POST,PATCH,DELETE,OPTIONS - Allowed headers:
Content-Type,Authorization,X-Test-Token,X-Api-Key - Credentials are allowed:
Access-Control-Allow-Credentials: true
Requests from origins not on the allowed list receive a 403 Forbidden response without CORS headers. The browser will block the response.
Wildcard origins (*) are never used because the API supports credentialed requests. Credentialed requests with a wildcard origin are rejected by browsers.
Rate Limiting
Rate limits are enforced per IP address and per authenticated identity. Limits are applied at the middleware layer before route handlers execute.
| Context | Limit | Window |
|---|---|---|
| Unauthenticated requests | 60 requests | 1 minute |
| Authenticated API requests | 300 requests | 1 minute |
| Test event ingestion (extension) | 1000 requests | 1 minute |
| AI agent endpoints | 10 requests | 1 minute |
| Authentication endpoints (login, magic link) | 10 requests | 15 minutes |
Rate limit state is stored in Redis with a sliding window algorithm. When a limit is exceeded, the API returns 429 Too Many Requests with a Retry-After header indicating when the next request will be accepted.
Authentication endpoint limits are stricter to prevent credential stuffing and brute-force attacks.
Input Validation
All input is validated at the API boundary before it reaches business logic or database queries. Validation uses Zod schemas defined in the route handler files.
Every request body, query parameter, and path parameter is validated against a schema. Validation errors return a 400 Bad Request response with a structured error body describing which fields failed and why.
{ "error": "Validation failed", "details": [ { "field": "session_id", "message": "Expected UUID, received 'abc'" }, { "field": "events", "message": "Array must contain at least 1 element" } ]}String inputs are trimmed and length-limited. File uploads are validated for MIME type and size before being processed. Image uploads for facial snapshots are validated as JPEG or PNG and must not exceed 2MB.
SQL Injection Prevention
OpenScouter does not construct SQL strings manually. All database access goes through the Supabase JavaScript client, which uses parameterized queries internally. User input is never interpolated into query strings.
For the small number of complex queries that use raw SQL via Supabase’s RPC functions, all parameters are passed as named bind variables, not concatenated strings.
Additionally, RLS policies provide a second layer of defense. Even if a parameterization error occurred in application code, the resulting query would be constrained by the authenticated user’s RLS context.
XSS Prevention
The primary XSS defense is React’s built-in output encoding. React escapes all string values before inserting them into the DOM. Raw HTML insertion via dangerouslySetInnerHTML is used in exactly two places in the codebase: the Plain English summary renderer and the WCAG criterion description renderer. Both of these render content that originates from AI model output.
AI-generated content is passed through a sanitization step using the DOMPurify library before being stored in the database. The sanitization step runs server-side during the Report Writer agent’s output processing. Content stored in the database is already clean by the time it reaches the frontend.
Stored content is sanitized once on write, not on every read. This avoids double-encoding issues that would corrupt legitimate HTML entities in technical content.
Vulnerability Disclosure
OpenScouter operates a responsible disclosure program. Security researchers who discover vulnerabilities are encouraged to report them before public disclosure.
Contact: security@openscouter.com
PGP key: Available on the OpenScouter security page at https://openscouter.com/.well-known/security.txt
Response SLA: Initial acknowledgement within 48 hours. Severity assessment within 5 business days.
Scope: All production systems at *.openscouter.com. The Chrome extension. The OpenScouter mobile application.
Out of scope: Social engineering. Physical attacks. Denial of service. Issues in third-party services not under OpenScouter’s control.
Researchers who responsibly disclose valid vulnerabilities are acknowledged in the security hall of fame and may be eligible for a bounty depending on severity.