4.6 KiB
Security report verification — 2026-09-13
The supplied review describes commit baeb54ae plus a separate port for another hotel. Its “Fixed” labels were not evidence that the changes existed in EpicNext-Cms. This verification inspected canonical main at 52f6d149 and the corrective changes prepared here. No exploit or authenticated mutation was performed against production.
| Supplied finding | Verified state in baseline | Correction / remaining boundary |
|---|---|---|
| 1. Logo authorization/upload | Confirmed missing action permission and per-file validation | Require settings edit before input or storage access; bounded decoded raster uploads |
| 2. Favicon authorization/delete | Confirmed missing action permission; SVG accepted | Same permission boundary for create/delete, bounded raster/ICO validation |
| 3. Active uploaded SVG | Confirmed SVG served inline without route CSP | Route CSP sandbox and nosniff on success/errors; existing SVG served as attachment |
| 4. Client IP spoofing | Confirmed direct trust in caller-controlled x-real-client-ip |
Shared validated resolver ignores that header. Forwarded headers still require trusted ingress that overwrites them and prevents direct public origin access |
| 5. Email token action exports | Confirmed token helpers in a use server module |
Move token creation/validation and delivery to a server-only module. Registration and verification call it internally |
| 6. Locale cookie | Confirmed missing allowlist | Supported locales only, validate before reading/writing cookies |
| 7. Email header injection | Confirmed unsanitized values in sendmail headers | Reject control characters before any mail transport or file fallback; includes configured sender |
| 8. Gateway CORS | Supplied gateway path is outside this repository | Read-only GET to our /api/health with an unrelated Origin returned a fixed https://epicnabbo.nl allow-origin and no allow-credentials. This does not reproduce the report on that route, nor certify every host/route |
| 9. Token abilities | Confirmed abilities and owner type not checked by bearer authentication | Enforce User owner type and explicit endpoint abilities; existing wildcard user tokens remain supported |
| 10. Broad script CDN | Confirmed unrestricted jsDelivr script source, without a source-code consumer | Remove the broad script source; retain required captcha/analytics sources and nonce |
The additional withNitroStaff code and its tests mentioned in the supplied port do not exist in this checkout; they were not assumed to have been reviewed or imported.
Evidence and limits
- Regression tests exercise authorization before I/O, actual file decoding, SVG/error response headers, token-boundary exports, token abilities, forged derived-IP headers across consumers, locale values, and mail header control characters.
- An updated
pnpm audit --jsonreported zero known advisories. This is a dependency database result, not proof that application code has no vulnerabilities. - Next.js treats exported Server Actions as public endpoints; unused actions can also be removed by the compiler. The email refactor removes the action boundary entirely instead of relying on whether a specific build exports an unused helper. See Next.js data security.
- The framework also has its own Server Action body limit. The logo defect was absence of application-level file validation, not evidence of literally unlimited bytes through every deployment layer.
- No live database, user accounts, uploaded files, or gateway configuration were modified during verification. These changes do not constitute a penetration test or an audit of the emulator, host, or all CMS endpoints.
- No nginx/Traefik ingress configuration is versioned here. The deployment guide records the forwarding-header trust requirement. That external boundary remains unverified.
Follow-up identified during verification
The separate comment review is now implemented: both the website form and REST API use one submission service, require a published article whose publication time is due, apply the same moderation, and share a five-attempt/30-second per-user quota. The publication check locks the current article in the insertion transaction. Regression tests cover both entrypoints; real MariaDB/Redis coverage includes publication eligibility, word filtering and alternating submissions. Moderation retains its existing fail-open behavior on service outages. Form input beyond 255 characters is now rejected instead of truncated, and temporary API storage failures return 503. Real integration execution remains a required CI check.