security: hardening release v1.2.0#3
Merged
Merged
Conversation
The Helm chart was unmaintained and not production-hardened (no securityContext, no resource limits, no NetworkPolicy, plaintext secrets in values.yaml). Recommended deployment is Docker Compose. K8s users can write a fresh chart against the v1.2.0 images.
…, mapped IPv6 Adds CIDR-based matching for previously uncovered ranges: - IPv4: 0.0.0.0/8, 100.64.0.0/10 (CGNAT), 198.18.0.0/15 (benchmark), 224.0.0.0/4 (multicast), 240.0.0.0/4 (reserved), TEST-NETs. - IPv6: ::1, fc00::/7 (ULA), fe80::/10 (link-local), ff00::/8 (multicast), 2001:db8::/32 (documentation), and IPv4-mapped addresses which are re-evaluated against the IPv4 blocklist. Adds RECON_ALLOW_PRIVATE_IPS=1 escape hatch for local testing. Adds SsrfBlockedError class for typed error handling. assertPublicHost now returns the resolved IP for callers to pin against.
…ct revalidation New utility wrapping axios with: - Protocol allowlist (http/https only) - DNS resolution + assertPublicHost on the resolved IP - Connection pinning to the validated IP (defeats DNS rebinding) - Manual redirect handling with full re-validation on every Location - Max body size (default 10 MiB), max redirects (default 5), total timeout (default 30s), connect timeout (default 5s) All handlers that perform outbound HTTP will be migrated to safeFetch in subsequent commits. RECON_ALLOW_PRIVATE_IPS=1 escape hatch supported.
…RF guard)
Migrates 14 handlers from raw axios.get() to safeFetch:
archives, cookies, firewall, headers, http-security, legacy-rank,
linked-pages, quality, rank, robots-txt, sitemap, social-tags,
tech-stack, threats.
Each handler now:
- Validates URL protocol before fetching.
- Resolves DNS and rejects private/internal IPs.
- Pins connection to the validated IP (DNS rebinding defense).
- Re-validates redirect targets before following.
- Returns { error: 'Blocked: ...' } on SsrfBlockedError instead of leaking
the underlying network error.
Adds per-handler SSRF integration tests using mocked dns.lookup.
The threats handler aggregated sub-handler errors, which wrapped
SsrfBlockedError into composite strings. Adds a top-level
assertPublicHost gate so SSRF rejection returns the canonical
{ error: 'Blocked: target resolves to private address' } message
before any sub-handler runs.
Tightens the threats.test.ts SSRF assertion to verify the canonical
error message pattern, preventing regressions where SSRF protection
silently stops working in this handler.
Adds URL validation (protocol allowlist + assertPublicHost) before invoking Chromium. The application-layer guard is the primary SSRF defense — even if Chromium itself follows a redirect, the only URLs it sees are pre-validated. Replaces --no-sandbox with --disable-setuid-sandbox + --disable-dev-shm-usage. The container now requires CAP_SYS_ADMIN + seccomp=unconfined so Chromium can create its user namespace sandbox; this is documented inline. Adds chrome-sandbox setuid fixup in api Dockerfile (conditional on the helper being shipped by the chromium package).
CORS no longer defaults to wildcard '*' — must be explicitly configured via API_CORS_ORIGIN as a comma-separated allowlist. Empty/unset disables CORS entirely. Rate limit default tightened to 200 / 1 minute (was 100 / 10 minutes, which is roughly the same average but with much larger burst tolerance). Per-route overrides for auth endpoints are added by the pro overlay.
Adds Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and an opt-in HSTS header (gated by HSTS_HEADER env, empty by default for non-TLS deployments). CSP allows 'unsafe-inline' on style-src only — required for Tailwind's atomic class system. script-src is strict 'self', which blocks injected inline JS even if XSS surfaces appear in future.
api: USER 10001, mkdir/chown /app and /home/app, cap_drop ALL, cap_add SYS_ADMIN only for Chromium namespace sandbox, read-only rootfs with tmpfs for /tmp and /home/app/.cache. web: nginx user (UID 101), rebind listen to 8080 (unprivileged), update compose port mapping accordingly, cap_drop ALL, read-only rootfs with tmpfs for /var/cache/nginx and /var/run. cli: USER 10001 (alpine), cap_drop ALL. All three images now reject privilege escalation via no-new-privileges.
Adds id-token: write permission required by cosign keyless OIDC. After the existing docker push step, the workflow now: - Signs each pushed image (api, web, cli) with cosign keyless using the GitHub Actions OIDC identity. Signatures are stored in the GHCR signature mirror and verifiable via cosign verify --certificate-identity. - Generates an SPDX-JSON SBOM via syft for each image. - Attaches the SBOM artifact to the GitHub release. Existing Trivy scans remain in the docker-build job.
Bumps all packages to 1.2.0 and writes the v1.2.0 release notes documenting the security hardening changes. Adds a SECURITY section to the README pointing operators at the new defaults and verification instructions for cosign-signed images.
Resolves: - axios CVE-2025-62718 (CRITICAL) - axios CVE-2026-40175 (CRITICAL) - basic-ftp GHSA-6v7q-wjvx-w8wg (HIGH CRLF injection) Unblocks the v1.2.0 release pipeline which the Trivy filesystem scan was rejecting on these three advisories.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Security hardening release. See
.releases/1.2.0.mdfor the full changelog.What changes
safeFetch(DNS pinning, redirect re-validation, IP allowlist incl. CGNAT/multicast/IPv6).safeFetch.--no-sandbox, switch to user-namespace sandbox.cap_drop ALL,read_onlyrootfs,no-new-privileges.*to off-by-default explicit allowlist.Breaking changes
Test plan