32 lines
4.6 KiB
Markdown
32 lines
4.6 KiB
Markdown
# 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 --json` reported 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](https://nextjs.org/docs/app/guides/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.
|