Security & robustness hardening pass
Cross-cutting input-validation, isolation, and DoS-resistance fixes across the app, API, billing, queue, and infra layers. - Runtime validation (zod) for client-supplied admin actions (role/plan/ limits), series generation index, and all pg-boss queue payloads - Auth: require email verification before sign-in; reject weak/placeholder/ short BETTER_AUTH_SECRET in production - Billing: sanitize Stripe/PayPal errors (log server-side, generic to client); race-safe subscription upsert; only count "processed" webhook events as handled; verify org membership in getEffectivePlan to block plan escalation - Series generation: reserve usage up front and refund on failure; bill the owning org, not the caller's active org - Injection defenses: HTML-escape user fields in emails, strip CR/LF from subject/recipient, validate ElevenLabs voiceId before URL interpolation - Media routes: stream off disk instead of buffering whole files; rate-limit anonymous public audio/cover endpoints by client IP
This commit is contained in:
+12
-1
@@ -1,4 +1,4 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import { promises as fs, createReadStream as fsCreateReadStream } from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { StorageProvider } from "./types";
|
||||
|
||||
@@ -29,6 +29,17 @@ export class LocalStorageProvider implements StorageProvider {
|
||||
return fs.readFile(resolveSafe(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream a file (optionally an inclusive byte range) without buffering it all
|
||||
* into memory. Goes through resolveSafe so path-traversal protection holds.
|
||||
*/
|
||||
createReadStream(key: string, range?: { start: number; end: number }): NodeJS.ReadableStream {
|
||||
const full = resolveSafe(key);
|
||||
return range
|
||||
? fsCreateReadStream(full, { start: range.start, end: range.end })
|
||||
: fsCreateReadStream(full);
|
||||
}
|
||||
|
||||
async exists(key: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(resolveSafe(key));
|
||||
|
||||
@@ -9,6 +9,16 @@ export interface StorageProvider {
|
||||
put(key: string, data: Buffer | Uint8Array, contentType?: string): Promise<void>;
|
||||
/** Read the full object as a Buffer. */
|
||||
get(key: string): Promise<Buffer>;
|
||||
/**
|
||||
* Stream the object (optionally a byte range, inclusive) instead of buffering
|
||||
* it all into memory — used by the audio/asset routes to avoid memory
|
||||
* amplification under concurrent load. Optional: providers may omit it, in
|
||||
* which case callers fall back to get().
|
||||
*/
|
||||
createReadStream?(
|
||||
key: string,
|
||||
range?: { start: number; end: number }
|
||||
): NodeJS.ReadableStream;
|
||||
/** Whether an object exists at `key`. */
|
||||
exists(key: string): Promise<boolean>;
|
||||
/** Remove the object (no-op if missing). */
|
||||
|
||||
Reference in New Issue
Block a user