Skip to Content
@exortek/securityerrors — SecurityError

errors

Every entry point in @exortek/security that can fail throws a SecurityError carrying a stable, machine-readable code. Branch on code, never on the message — messages may change between versions, codes never do.

import { SecurityError, ErrorCode } from '@exortek/security'

SecurityError

class SecurityError extends Error { name: 'SecurityError'; code: string; // one of ErrorCode status: number; // suggested HTTP status cause?: unknown; // wrapped underlying error, when applicable }

Extends the built-in Error. Adds a stable code (see ErrorCode) and a suggested HTTP status — adapters use this to translate the error into a response.

import { SecurityError, ErrorCode, csrf } from '@exortek/security'; try { csrf.generate('too-short'); } catch (err) { if (err instanceof SecurityError && err.code === ErrorCode.INVALID_ARGUMENT) { console.error('config bug:', err.message); process.exit(1); } else { throw err; } }

Not every failure throws. csrf.verify returns false on malformed or mismatched tokens — that’s a normal authentication outcome, not an error. cors()’s decision object carries an allowed: false flag for denials, same reasoning. Errors are reserved for programmer or configuration mistakes.

ErrorCode

Codes are stable across minor versions — new codes may be added; existing codes are never renamed or repurposed.

CodeMeaningTypical status
INVALID_ARGUMENTType / range / shape validation failed on a function argument or config option.400
CSRF_MISSINGCSRF cookie or header value is missing on a state-changing request.403
CSRF_MISMATCHCookie and header CSRF values don’t match.403
CSRF_MALFORMEDCSRF token doesn’t have the expected <random>.<hmac> shape.403
CSRF_TAMPEREDCSRF HMAC tag doesn’t match the current secret.403
RATE_LIMITEDRate-limit deny (raised by adapters when configured to throw).429
ORIGIN_DENIEDOrigin failed CORS / checkOrigin validation.403
REDIRECT_UNSAFERedirect target failed safeRedirect validation (raised by adapters that convert to throw).400
PATH_TRAVERSALsafeJoin refused a segment that would escape the base directory.400
BODY_TOO_LARGERequest body / JSON payload exceeded the configured limit.413
REQUEST_TIMEOUTtimeout() hit the deadline.504
WEBHOOK_INVALIDWebhook signature verification failed (raised by adapters).401
HONEYPOT_TRIGGEREDHoneypot field was filled — likely a bot.200 (silent drop)

Which code comes from where?

ModuleCodes it emits
csrf.generateINVALID_ARGUMENT (short / missing secret)
csrf.verify / verifyForSessionnever throws for auth outcomes — return false
headersINVALID_ARGUMENT (bad HSTS preload eligibility, invalid CSP source, ALLOW-FROM frameguard)
corsINVALID_ARGUMENT (credentials: true + reflect-any, bad maxAge, non-2xx optionsSuccessStatus)
safeRedirectINVALID_ARGUMENT (bad defaultTo, non-string allowedHosts entries) — the input never throws
rateLimit.*INVALID_ARGUMENT (bad options), no throws from check() itself
HelpersSee table above — code depends on the specific helper
Middleware adaptersWrap the above; adapter errors are INVALID_ARGUMENT (bad options, missing cookie plugin for CSRF)

Suggested HTTP status mappings

Adapters use this table already; roll your own error handler when using the primitives directly:

function toHttpStatus(err) { if (!(err instanceof SecurityError)) return 500; switch (err.code) { case ErrorCode.INVALID_ARGUMENT: return 400; case ErrorCode.CSRF_MISSING: case ErrorCode.CSRF_MISMATCH: case ErrorCode.CSRF_MALFORMED: case ErrorCode.CSRF_TAMPERED: case ErrorCode.ORIGIN_DENIED: return 403; case ErrorCode.WEBHOOK_INVALID: return 401; case ErrorCode.RATE_LIMITED: return 429; case ErrorCode.BODY_TOO_LARGE: return 413; case ErrorCode.REQUEST_TIMEOUT: return 504; case ErrorCode.PATH_TRAVERSAL: return 400; default: return 500; } }

Handling patterns

Config errors — die at boot:

try { await app.register(securityPlugin, options); } catch (err) { if (err instanceof SecurityError && err.code === ErrorCode.INVALID_ARGUMENT) { console.error('security misconfigured:', err.message); process.exit(1); } throw err; }

Verification outcomes — branch, don’t throw:

// CSRF, checkOrigin, safeRedirect, cors — all return booleans / decisions if (!csrf.verify(cookie, header, secret)) return res.status(403).end(); if (!checkOrigin(req, { allowedOrigins })) return res.status(403).end(); const { safe, url } = safeRedirect(next, { allowedHosts }); res.redirect(url); // safe or not, url is always safe

Adapter errors — propagate to your framework’s handler:

// Nothing to do — Fastify / Express / Hono / Elysia all catch async // throws and route them to their error handlers. app.setErrorHandler((err, req, reply) => { if (err instanceof SecurityError) { return reply.code(toHttpStatus(err)).send({ error: err.code }); } return reply.code(500).send({ error: 'InternalError' }); });
Last updated on