Security
ergo is not a security certification — it is a middleware toolkit whose pipeline structure and default configuration address the most common REST API security risks. The protections described on this page are built into ergo’s design. You don’t opt into them; you would have to deliberately override them to weaken your API’s security posture.
OWASP API Security Top 10
Section titled “OWASP API Security Top 10”API1: Broken Object Level Authorization
Section titled “API1: Broken Object Level Authorization”Risk: APIs expose endpoints that handle object identifiers, creating a wide attack surface for object-level access control.
ergo mitigation: The authorization() middleware runs in Stage 2 — before
body parsing and before execution. Every request is authenticated and identity
information is available in the accumulator before any business logic runs.
Pluggable strategies support Bearer, Basic, and custom schemes.
router.get("/accounts/:id", { auth: { strategies: [ { type: "Bearer", authorizer: async (attributes, token) => { const user = await verifyToken(token); return user ? { authorized: true, info: user } : { authorized: false }; }, }, ], }, execute: (req, res, acc) => { if (acc.auth.id !== acc.route.params.id) { return { response: { statusCode: 403, detail: "Access denied" } }; } return { response: { body: getAccount(acc.route.params.id) } }; },});The pipeline guarantees that acc.auth is always populated before execute
runs. There is no code path where business logic executes without
authorization having completed first.
API2: Broken Authentication
Section titled “API2: Broken Authentication”Risk: Weak or improperly implemented authentication mechanisms allow attackers to assume other users’ identities.
ergo mitigation:
authorization()implements RFC 6750 (Bearer) and RFC 7617 (Basic) with correctWWW-Authenticatechallenge responses, includingrealmanderrorattributes formatted per the RFCs.- CSRF protection uses
crypto.timingSafeEqual()for token comparison, preventing timing attacks that could reveal token values. - CSRF cookies are locked to
httpOnly: trueandsameSite: 'Strict'by default. These attributes cannot be overridden by consumer configuration, preventing accidental weakening. WWW-Authenticateheader values pass throughsanitizeQuotedStringto prevent header injection via challenge attributes.
router.post("/transfer", { auth: { strategies: [bearerStrategy] }, csrf: { secret: process.env.CSRF_SECRET }, execute: handleTransfer,});API3: Broken Object Property Level Authorization
Section titled “API3: Broken Object Property Level Authorization”Risk: APIs expose object properties that should not be accessible or modifiable by the user.
ergo mitigation: The validate() middleware uses JSON Schema (via AJV)
for strict property allowlisting. With additionalProperties: false, any
unexpected property in the request body causes a 400 Bad Request before
execution.
router.put("/users/:id", { validate: { body: { type: "object", properties: { name: { type: "string", maxLength: 100 }, email: { type: "string", format: "email" }, }, required: ["name", "email"], additionalProperties: false, }, }, execute: updateUser,});Properties like role, isAdmin, or createdAt are rejected automatically
because they are not declared in the schema. This is structural protection —
it does not depend on the developer remembering to strip fields manually.
API4: Unrestricted Resource Consumption
Section titled “API4: Unrestricted Resource Consumption”Risk: APIs that do not limit resource consumption are vulnerable to DoS and brute-force attacks.
ergo mitigation: Multiple layers of resource bounding are built in:
| Protection | Default | Configuration |
|---|---|---|
| Rate limiting | Sliding-window counter | rateLimit({ max, windowMs }) |
| Request body size | 1 MiB (DEFAULT_LIMIT) | body({ limit }) |
| Decompressed body size | 10 MiB (MAX_DECOMPRESSED) | body({ decompressedLimit }) |
| Query string length | 8,192 bytes | url({ maxQueryLength }) |
| Query key-value pairs | 256 | url({ maxPairs }) |
| Cookie count | 50 | cookie({ max }) |
| Multipart parts | Configurable | body({ maxParts }) |
| Rate limit store keys | Eviction cap on MemoryStore | rateLimit({ store }) |
| Request timeout | Configurable | timeout({ ms }) |
Decompression bomb protection deserves special mention: a second meter stream
after the decompressor enforces decompressedLimit independently of the
wire-side limit. A 1 KiB compressed payload that expands to 100 MiB is
caught and rejected.
router.post("/upload", { body: { limit: 5 * 1024 * 1024, decompressedLimit: 50 * 1024 * 1024, maxParts: 10, }, execute: handleUpload,});When rate limiting triggers, ergo returns 429 Too Many Requests with a
Retry-After header and an RFC 9457 Problem Details body — no custom error
handling needed.
API5: Broken Function Level Authorization
Section titled “API5: Broken Function Level Authorization”Risk: APIs expose administrative or privileged functions without proper access control checks.
ergo mitigation:
- ergo-router’s automatic
405 Method Not AllowedwithAllowheader ensures that only explicitly registered methods are reachable on each path. There are no accidental method exposures. - Per-route auth strategy overrides allow different authorization requirements for different operations on the same resource.
OPTIONSrequests are auto-generated from the route table, providing an accurate inventory of supported methods.
router.get("/admin/users", { auth: { strategies: [adminOnlyStrategy] }, execute: listAllUsers,});
router.get("/users/:id", { auth: { strategies: [authenticatedUserStrategy] }, execute: getUser,});API6: Unrestricted Access to Sensitive Business Flows
Section titled “API6: Unrestricted Access to Sensitive Business Flows”Risk: APIs expose business flows without compensating controls, enabling automated abuse (credential stuffing, scalping, etc.).
ergo mitigation: The rateLimit() middleware provides sliding-window rate
limiting that can be configured per-route. Combined with CSRF protection on
state-changing operations, this provides the foundational controls for
throttling automated abuse.
router.post("/login", { rateLimit: { max: 5, windowMs: 15 * 60 * 1000 }, csrf: { secret: process.env.CSRF_SECRET }, execute: handleLogin,});API7: Server Side Request Forgery
Section titled “API7: Server Side Request Forgery”Risk: APIs that fetch remote resources based on user input can be tricked into making requests to internal services.
ergo mitigation: ergo is an inbound-only middleware toolkit. It does not provide HTTP client utilities, URL fetchers, or proxy helpers. There is no built-in mechanism for making outbound requests based on user input. If your application needs to fetch external resources, you control that code entirely — ergo does not introduce SSRF surface.
API8: Security Misconfiguration
Section titled “API8: Security Misconfiguration”Risk: Missing or misconfigured security settings at any level of the API stack.
ergo mitigation: ergo and ergo-router ship with conservative defaults that require no configuration to be secure:
| Header | Default | Standard |
|---|---|---|
Content-Security-Policy | default-src 'none' | W3C CSP Level 3 |
X-Frame-Options | DENY | RFC 7034 |
X-Content-Type-Options | nosniff | Convention |
Referrer-Policy | no-referrer | W3C Referrer Policy |
X-XSS-Protection | 0 (disables flawed browser filter) | Convention |
Permissions-Policy | Restrictive defaults | W3C Permissions Policy |
Strict-Transport-Security | Via ergo-router (HTTPS only) | RFC 6797 |
Additional structural protections:
- Null-prototype objects for all user-input-derived data (query params,
cookies, Prefer headers). Keys like
__proto__,constructor, andtoStringcannot pollute the prototype chain. - CSRF cookie attributes are locked.
httpOnlyandsameSitecannot be overridden viacookieOptions, preventing accidental weakening. - Cookie-octet grammar enforcement. Cookie values and names are validated
against RFC 6265 §4.1.1, preventing
Set-Cookieattribute injection via semicolons or other special characters in values. - Error detail redaction. 5xx error responses never expose internal error messages. The raw error is available to the logger but the API consumer sees only a generic “Internal Server Error” title.
const router = createRouter({ transport: { security: {}, cors: { origin: "https://myapp.com" }, rateLimit: { max: 100, windowMs: 60_000 }, requestId: {}, },});Every response — including 404, 405, and 429 short-circuits — includes security headers and a request ID automatically.
API9: Improper Inventory Management
Section titled “API9: Improper Inventory Management”Risk: APIs expose undocumented or deprecated endpoints, increasing the attack surface.
ergo mitigation: This is primarily a governance concern, but ergo-router provides structural support:
OPTIONSrequests auto-respond with anAllowheader listing the methods registered on each path.405 Method Not Allowedresponses include theAllowheader, giving clients (and auditors) an accurate inventory of supported methods.- The declarative route configuration serves as a single-source-of-truth for what the API exposes.
API10: Unsafe Consumption of APIs
Section titled “API10: Unsafe Consumption of APIs”Risk: APIs trust data received from third-party integrations without proper validation.
ergo mitigation: While this risk primarily concerns outbound API consumption, ergo’s inbound protections provide a strong foundation:
- The body parser validates
Content-Typeand charset before parsing, preventing content confusion attacks. - JSON Schema validation via
validate()applies the same strictness to webhook payloads and third-party callbacks as to client requests. - Bounded parsing defaults apply to all inbound data regardless of source.
Additional Hardening
Section titled “Additional Hardening”Beyond the OWASP API Top 10, ergo includes protections that address broader security concerns:
| Protection | Description |
|---|---|
sanitizeQuotedString | Escapes backslashes, double-quotes, and strips all CTL characters (except HTAB) per RFC 7230 §3.2.6. Used in WWW-Authenticate, Link, and Set-Cookie headers. |
| Prefer header ReDoS mitigation | The lib/prefer.js parser uses a two-alternative regex pattern that avoids catastrophic backtracking on adversarial input. |
| Sensitive header redaction | The structured logger redacts authorization, cookie, proxy-authorization, and set-cookie header values from log output. |
| Safe URI decoding | lib/query.js wraps decodeURIComponent in a safeDecode() helper that catches URIError on malformed percent-encoded sequences, preventing 500 crashes. |
exec-all zero-length match guard | Prevents infinite loops when a regex matches the empty string. |
| Request-ID validation | ergo-router validates incoming X-Request-Id headers, rejecting oversized or malformed values. |
| Stream error propagation | send() and compress() propagate stream errors via res.emit('error') only when listeners are present, preventing both uncaught exceptions and hung responses. |
Further Reading
Section titled “Further Reading”- OWASP API Security Top 10 (2023)
- OWASP REST Security Cheat Sheet
- OWASP Input Validation Cheat Sheet
- RFC 9205 — Building Protocols with HTTP (BCP 56)
- Standards Compliance — full list of IETF RFCs implemented by ergo