Skip to content

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.

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.

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 correct WWW-Authenticate challenge responses, including realm and error attributes 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: true and sameSite: 'Strict' by default. These attributes cannot be overridden by consumer configuration, preventing accidental weakening.
  • WWW-Authenticate header values pass through sanitizeQuotedString to 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.

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:

ProtectionDefaultConfiguration
Rate limitingSliding-window counterrateLimit({ max, windowMs })
Request body size1 MiB (DEFAULT_LIMIT)body({ limit })
Decompressed body size10 MiB (MAX_DECOMPRESSED)body({ decompressedLimit })
Query string length8,192 bytesurl({ maxQueryLength })
Query key-value pairs256url({ maxPairs })
Cookie count50cookie({ max })
Multipart partsConfigurablebody({ maxParts })
Rate limit store keysEviction cap on MemoryStorerateLimit({ store })
Request timeoutConfigurabletimeout({ 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.

Risk: APIs expose administrative or privileged functions without proper access control checks.

ergo mitigation:

  • ergo-router’s automatic 405 Method Not Allowed with Allow header 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.
  • OPTIONS requests 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,
});

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.

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:

HeaderDefaultStandard
Content-Security-Policydefault-src 'none'W3C CSP Level 3
X-Frame-OptionsDENYRFC 7034
X-Content-Type-OptionsnosniffConvention
Referrer-Policyno-referrerW3C Referrer Policy
X-XSS-Protection0 (disables flawed browser filter)Convention
Permissions-PolicyRestrictive defaultsW3C Permissions Policy
Strict-Transport-SecurityVia 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, and toString cannot pollute the prototype chain.
  • CSRF cookie attributes are locked. httpOnly and sameSite cannot be overridden via cookieOptions, preventing accidental weakening.
  • Cookie-octet grammar enforcement. Cookie values and names are validated against RFC 6265 §4.1.1, preventing Set-Cookie attribute 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.

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:

  • OPTIONS requests auto-respond with an Allow header listing the methods registered on each path.
  • 405 Method Not Allowed responses include the Allow header, 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.

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-Type and 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.

Beyond the OWASP API Top 10, ergo includes protections that address broader security concerns:

ProtectionDescription
sanitizeQuotedStringEscapes 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 mitigationThe lib/prefer.js parser uses a two-alternative regex pattern that avoids catastrophic backtracking on adversarial input.
Sensitive header redactionThe structured logger redacts authorization, cookie, proxy-authorization, and set-cookie header values from log output.
Safe URI decodinglib/query.js wraps decodeURIComponent in a safeDecode() helper that catches URIError on malformed percent-encoded sequences, preventing 500 crashes.
exec-all zero-length match guardPrevents infinite loops when a regex matches the empty string.
Request-ID validationergo-router validates incoming X-Request-Id headers, rejecting oversized or malformed values.
Stream error propagationsend() and compress() propagate stream errors via res.emit('error') only when listeners are present, preventing both uncaught exceptions and hung responses.