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:
@@ -11,6 +11,8 @@
|
||||
* truth.
|
||||
*/
|
||||
|
||||
import { createHash } from 'node:crypto';
|
||||
|
||||
import { and, eq, isNull } from 'drizzle-orm';
|
||||
|
||||
import { db } from '@/lib/db';
|
||||
@@ -47,6 +49,16 @@ export interface PresignOpts {
|
||||
* slug, so this is the matching enforcement.
|
||||
*/
|
||||
portSlug?: string;
|
||||
/**
|
||||
* Optional per-port upload byte cap for presigned uploads. Embedded in
|
||||
* the filesystem proxy token (`b` field) and enforced by the proxy PUT
|
||||
* handler, so a token minted against a 15 MB-capped port can't be used
|
||||
* to write a 50 MB object (audit M17). S3 presigned PUTs can't sign a
|
||||
* content-length-range on this path, so the cap there is re-checked
|
||||
* server-side at register time. When unset, the proxy falls back to the
|
||||
* global `MAX_FILE_SIZE` ceiling.
|
||||
*/
|
||||
maxBytes?: number;
|
||||
}
|
||||
|
||||
export interface StorageBackend {
|
||||
@@ -209,7 +221,12 @@ async function loadStorageConfig(): Promise<StorageConfigSnapshot> {
|
||||
* client is held in memory until the next mismatch.
|
||||
*/
|
||||
function fingerprint(cfg: StorageConfigSnapshot): string {
|
||||
return JSON.stringify(cfg);
|
||||
// L17(c): hash the serialized config rather than holding the decrypted S3
|
||||
// access key verbatim in a process-lifetime string. A SHA-256 digest still
|
||||
// changes whenever any field (including the secret material) rotates, so
|
||||
// the cache-invalidation semantics are unchanged, but the cleartext secret
|
||||
// no longer lingers in the cache key.
|
||||
return createHash('sha256').update(JSON.stringify(cfg)).digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user