Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@
# category and are excluded automatically β€” no entry here needed.
# --severity HIGH,CRITICAL Only fixable HIGH/CRITICAL issues block the pipeline.
#
# Base image strategy:
# Both node:24.2.0-bookworm-slim and gcr.io/distroless/nodejs24-debian12:nonroot
# are pinned by digest. The weekly update-base-images.yml workflow rotates digests
# automatically, pulling in security patches as soon as Google publishes them.
#
# npm dependency status (last verified: 2026-03-28):
# npm audit --omit=dev reports 0 vulnerabilities.
# Resolved in this pass (all were MODERATE, none HIGH/CRITICAL):
# - fastify@<=5.8.2 (GHSA-444r-cwp2-x5xf) β†’ patched by upgrade to 5.8.3+
# - brace-expansion@4.0.0-5.0.4 (GHSA-f886-m6hf-6m8v) β†’ patched via lockfile
# - yaml@2.0.0-2.8.2 (GHSA-48c2-rrv3-qjmp) β†’ patched via lockfile
#
# Entries in this file are for the narrow category of CVEs that:
# a) Have a "fixed version" listed in Trivy (so --ignore-unfixed does NOT filter them), AND
# b) Have been assessed as non-exploitable or non-applicable in this context.
Expand All @@ -27,3 +39,18 @@
# Add an explicit entry here ONLY if a "fixed version" appears in Trivy's report
# and we still need to suppress it for a documented reason.
# ─────────────────────────────────────────────────────────────────────────────

# ─── DISTROLESS RUNTIME NOTES ────────────────────────────────────────────────
# gcr.io/distroless/nodejs24-debian12:nonroot contains only:
# - Node.js 24 runtime (libssl via OpenSSL, not BusyBox β€” distroless is Debian, NOT Alpine)
# - Minimal glibc from Debian 12 bookworm
# - No shell, no package manager, no curl, no BusyBox
# ANY reported BusyBox CVEs in a scan of this image indicate a stale Trivy DB
# or a scan of the wrong image. BusyBox is not present in Debian-based distroless.
#
# OS-level CVEs (OpenSSL, glibc) for this image are handled by:
# 1. --ignore-unfixed flag in both pr.yml and deploy.yml (filters unfixed Debian CVEs)
# 2. Weekly digest rotation (update-base-images.yml) pulling latest Google patches
# 3. SARIF upload to GitHub Security tab for historical tracking
# ─────────────────────────────────────────────────────────────────────────────

2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"@types/jsonwebtoken": "^9.0.10",
"bullmq": "^5.70.4",
"dotenv": "^17.3.1",
"fastify": "^5.8.2",
"fastify": "^5.8.3",
"fastify-plugin": "^5.1.0",
"fastify-type-provider-zod": "^6.1.0",
"ioredis": "^5.10.0",
Expand Down
22 changes: 21 additions & 1 deletion apps/api/src/plugins/security/ratelimit.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,27 @@ return count

const rateLimitPlugin: FastifyPluginAsync = async (fastify: FastifyInstance) => {
if (!shouldStartWorkers()) {
fastify.log.info("security-rate-limit plugin SKIPPED (WORKERS_ENABLED=false β€” Redis not provisioned)");
// Redis is not provisioned (WORKERS_ENABLED=false).
// Fall back to in-memory rate limiting so the API is never entirely
// unprotected. In-memory limits are intentionally lower than the
// Redis-backed tier because the counter is per-process (not shared
// across replicas), making per-user tracking less accurate.
fastify.log.warn(
"security-rate-limit plugin using in-memory fallback (Redis not provisioned) β€” limits: 200 req/min",
);
await fastify.register(fastifyRateLimit, {
global: true,
hook: "preHandler",
max: 200,
timeWindow: "1 minute",
allowList: ["127.0.0.1", "::1"],
errorResponseBuilder: (_request, context) => ({
success: false,
error: "Too many requests",
retryAfter: context.after,
}),
});
fastify.log.info("security-rate-limit plugin registered (in-memory fallback, 200 req/min)");
return;
}

Expand Down
Loading
Loading