API Security Baseline — OWASP API Security Top 10
This document maps each OWASP API Security Top 10 (2023) category to a concrete, testable requirement for the AURA Core API. Every endpoint must satisfy the applicable requirements before merge. This is the reference standard for Feature Readiness Gate 2.
This baseline is not aspirational. It is the minimum bar. If a requirement says “must,” the PR cannot merge without it.
How to Use This Document
When building a new endpoint or modifying an existing one:
- Read the requirements below that apply to your change.
- Write security acceptance criteria into your PR description before writing code.
- Implement the endpoint to satisfy those criteria.
- Write tests that verify the security behaviour (see Gate 5 in FEATURE_READINESS.md).
- Reference the specific API-n category in your PR description so reviewers know what was considered.
API1: Broken Object Level Authorization (BOLA)
Every endpoint that accepts a resource identifier (session ID, transaction ID, offer ID, agent ID) must verify that the requesting agent owns or is authorized to access that resource.
Requirements:
- All resource-access queries must include an ownership predicate. A query like
WHERE id = $1 is never sufficient on its own for a protected resource. It must be WHERE id = $1 AND agent_id = $2 or an equivalent join that proves ownership.
- If the requesting agent does not own the resource, return 403 (not 404). Returning 404 for authorization failures leaks information about resource existence.
- Ownership verification must happen inside the database query, not in application code after the query. This prevents TOCTOU (time-of-check/time-of-use) race conditions.
- For resources with multiple authorized parties (e.g., a transaction involves both the session creator and the beacon), the query must verify the agent is one of the authorized parties.
Test pattern:
test('agent A cannot read agent B session', async () => {
// Create session as agent A
const session = await createSessionAs(agentA);
// Try to read it as agent B
const res = await getSessionAs(agentB, session.id);
assert.equal(res.statusCode, 403);
});
API2: Broken Authentication
Every state-changing endpoint and every endpoint that returns non-public data must require authentication via Ed25519 signature verification (verifyAgent preHandler).
Requirements:
- All POST, PUT, PATCH, DELETE endpoints must include
{ preHandler: verifyAgent }.
- All GET endpoints that return agent-specific or sensitive data must include
{ preHandler: verifyAgent }.
- GET endpoints that return only public, non-sensitive data (e.g.,
/health, /metrics, /) may be unauthenticated.
- Authentication failures must return 401 with a
WWW-Authenticate header.
- Revoked or suspended agents must receive 403, not 401.
- Timestamp validation window must not exceed 5 minutes.
API3: Broken Object Property Level Authorization (BOPLA)
Responses must not expose properties that the requesting agent is not authorized to see.
Requirements:
- Response payloads must be filtered based on the requesting agent’s relationship to the resource.
- Session constraints (budget, delivery requirements) must only be visible to the session creator, not to beacons.
- Internal fields (database IDs, internal timestamps, audit metadata) must never appear in API responses.
- When adding a new field to a response, explicitly document who should see it and add a filter if it is not universally visible.
Test pattern:
test('beacon cannot see session constraints', async () => {
const session = await createSessionAs(scout);
const res = await getSessionAs(beacon, session.id);
assert.equal(res.json().constraints, undefined);
});
API4: Unrestricted Resource Consumption
Every list endpoint must enforce pagination. Every request must enforce size limits.
Requirements:
- All endpoints that return collections must accept
limit and offset parameters.
limit must have a maximum value (500 for most endpoints). Requests exceeding it receive 400.
offset must be non-negative. Requests with negative values receive 400.
- Request body size must be bounded. Fastify’s default
bodyLimit (1MB) is acceptable for most endpoints. Endpoints that accept large payloads (e.g., bulk operations) must set an explicit, documented limit.
- Database queries must always include a
LIMIT clause, even for single-resource lookups that use LIMIT 1 implicitly via unique constraints.
API5: Broken Function Level Authorization (BFLA)
Endpoints must enforce authorization based on the agent’s role and the action being performed, not just authentication.
Requirements:
- An authenticated agent must not be able to perform actions outside their role. A beacon agent cannot create sessions. A scout agent cannot submit offers.
- Role checks must happen at the route level, not inside handler logic. Use dedicated preHandlers for role verification.
- When a new endpoint is added, explicitly document which agent roles are authorized to call it.
Test pattern:
test('beacon agent cannot create sessions', async () => {
const res = await createSessionAs(beaconAgent);
assert.equal(res.statusCode, 403);
});
API6: Unrestricted Access to Sensitive Business Flows
Business-critical flows must have protections against abuse, replay, and automation.
Requirements:
- Transaction commits must require an idempotency key (UUID format, validated).
- State transitions must be validated by the session/transaction state machine. Invalid transitions receive 409.
- Atomic operations (commit, accept) must use database-level locking (
SELECT ... FOR UPDATE).
- Webhook replay must carry an
X-AURA-Replay: true header and respect the original idempotency key.
API7: Server-Side Request Forgery (SSRF)
Any endpoint or component that makes outbound HTTP requests based on user-supplied URLs must validate the destination.
Requirements:
- Webhook endpoint URLs must be HTTPS only. HTTP URLs must be rejected at registration time.
- Webhook endpoint URLs must not resolve to private IP ranges:
127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, ::1, fc00::/7.
- Webhook endpoint URLs must not resolve to
localhost or any loopback address.
- URL validation must happen both at beacon registration and at dispatch time (the DNS resolution could change between registration and dispatch).
- Outbound requests must enforce a timeout (5 seconds maximum for webhooks).
Test pattern:
test('rejects webhook URL pointing to private IP', async () => {
const res = await registerBeacon({ endpointUrl: 'https://192.168.1.1/webhook' });
assert.equal(res.statusCode, 400);
assert.match(res.json().message, /private|internal|not allowed/i);
});
API8: Security Misconfiguration
The application must be configured securely by default with no manual hardening required.
Requirements:
- Security headers must be set via Helmet (HSTS in production, X-Content-Type-Options, X-Frame-Options: DENY, Referrer-Policy: no-referrer).
- CORS must be restricted to whitelisted origins (no wildcard
* in production).
- Error responses must not leak stack traces, internal paths, or database details in production. The
NODE_ENV check must control error verbosity.
- Debug/dev routes (if any) must be disabled in production via environment check.
- Default database SSL must use
rejectUnauthorized: true in production with CA certificate validation.
API9: Improper Inventory Management
API versions, deprecated endpoints, and documentation must be actively managed.
Requirements:
- All business routes must be under a version prefix (
/v1/).
- When deprecating an endpoint, add
Deprecation: true and Sunset: <date> response headers with a minimum 90-day grace period.
- The version discovery endpoint (
GET /) must list all active API versions.
- OpenAPI/schema documentation must be updated in the same PR that changes an endpoint.
API10: Unsafe Consumption of APIs
Outbound API calls must authenticate and validate responses.
Requirements:
- Webhook payloads must be signed with HMAC-SHA256 so receivers can verify authenticity. The signature must be sent in an
X-AURA-Signature header.
- Webhook responses must be validated (status code check, timeout enforcement).
- External service responses must not be trusted as authoritative. Parse defensively and validate expected structure before acting on the data.
- Outbound calls must propagate W3C Trace Context headers via
getTraceHeaders(request).
Cross-Cutting Requirements
These apply to every endpoint regardless of category:
- All path parameters that represent resource IDs must be validated as UUID format before any database query. Invalid UUIDs receive 400.
- All security-relevant events (auth failures, authorization denials, rate limit hits, SSRF blocks) must be logged at
warn or error level with request.log, including the agent ID, IP address, and request path.
- No endpoint may return a 5xx error that includes internal details (stack traces, query text, file paths) in production.
- Secrets (database URLs, signing keys, API keys) must never appear in logs, error messages, or API responses.
Compliance Tracking
Each endpoint in the codebase should be annotated with the OWASP categories it has been verified against. The format is a comment at the top of each route handler:
// Security: API1 (BOLA), API2 (Auth), API3 (BOPLA), API5 (BFLA)
app.get('/v1/sessions/:sessionId', { preHandler: verifyAgent }, async (request, reply) => {
This makes it visible during code review which categories have been considered and which have not.