fix(audit): storage cluster — M16 (presign doc/contract), M17 (per-port byte cap), M18 (replay-after-stat), L17 (mime allow-list, fingerprint hash), L22 (brochure portSlug)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-02 12:40:56 +02:00
parent 65ed90b603
commit 9305c030de
6 changed files with 132 additions and 26 deletions

View File

@@ -1,7 +1,14 @@
/**
* Returns a presigned URL the browser can use to PUT a PDF directly to the
* active storage backend. The URL is constrained by content-length-range up
* to `system_settings.berth_pdf_max_upload_mb` (default 15 MB) per §11.1.
* active storage backend. `maxBytes` (from `system_settings.berth_pdf_max_upload_mb`,
* default 15 MB per §11.1) is returned to the client as a hint and used to
* early-reject an oversized `sizeBytes` before a URL is minted.
*
* NOTE (audit M16/M17): the S3 presigned-PUT path does NOT sign a
* content-length-range or Content-Type condition, so the cap is enforced
* server-side at register time (`uploadBerthPdf` re-HEADs + magic-byte
* probes and rejects over-cap bytes). The filesystem proxy path embeds the
* cap in the HMAC token (`b` field) and enforces it in the proxy PUT.
*
* For S3 backends this is a true signed URL; for filesystem backends it's a
* CRM-internal proxy URL with an HMAC token (see `FilesystemBackend`).
@@ -67,6 +74,9 @@ export const postHandler: RouteHandler = async (req, ctx, params) => {
contentType: 'application/pdf',
expirySeconds: 900,
portSlug: ctx.portSlug,
// Embed the per-port cap in the filesystem proxy token so the proxy
// PUT enforces the advertised 15 MB (not the global 50 MB) - audit M17.
maxBytes,
});
return NextResponse.json({