mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
The policy module (src/lib/documents/upload-policy.ts) is the one source of truth for what the vault accepts and how it serves: Class A (PDF/JPEG/PNG/ WebP/GIF) renders inline; Class B (Office OOXML+CFB, text/CSV/Markdown/RTF, TIFF, HEIC/HEIF, XML/JSON) is stored verbatim and leaves the origin only as an opaque attachment download; executables, HTML/XHTML/SVG, and generic archives are denied. Detection is magic-bytes first — the wire Content-Type is never trusted; containers (ZIP → OOXML, CFB → legacy Office) and extension-only text formats gate on the filename plus a no-NUL heuristic. resolveDocumentLimits() reads the admin-tunable per-file cap (clamped to the hard 100 MiB ceiling — single-shot GCM and bounded in-memory reads are load-bearing past that) and the quota (user override ?? instance default). crypto.ts gains the binary2 codec: encryptBytes/decryptBytes do AES-256-GCM over raw bytes with a versioned key-id header, skipping the base64 detour (−33 % at rest, one fewer copy). Same fail-closed discipline as the string codec. The store dispatches per row on the explicit contentCodec column: new uploads write binary2, pre-existing base64v1 rows keep decoding byte-identically, and an unknown codec throws instead of guessing. Exhaustive unit coverage: every accept class from real magic-byte fixtures, every deny case (incl. the PDF-with-HTML-head, SVG-as-image, and ZIP-renamed-docx polyglots), header-matrix source of truth, limit clamping, codec round-trips both ways, crossed-codec and unknown-codec fail-closed. Tasks: W1-T3, W1-T4.
120 lines
4.3 KiB
TypeScript
120 lines
4.3 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
import { Buffer } from "node:buffer";
|
|
import {
|
|
encryptBytes,
|
|
decryptBytes,
|
|
extractKeyIdFromBytes,
|
|
reencryptBytesToActive,
|
|
encrypt,
|
|
_resetCryptoCacheForTests,
|
|
} from "../crypto";
|
|
|
|
/**
|
|
* The binary "binary2" codec: AES-256-GCM over raw bytes with a key-id
|
|
* header, no base64 detour. Same versioned-key discipline as the string
|
|
* codec — fail-closed on unknown versions, malformed headers, and
|
|
* unconfigured key ids.
|
|
*/
|
|
|
|
const KEY_V1 =
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
const KEY_V2 =
|
|
"fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210";
|
|
|
|
function useSingleKey() {
|
|
vi.unstubAllEnvs();
|
|
vi.stubEnv("ENCRYPTION_KEYS", "");
|
|
vi.stubEnv("ENCRYPTION_ACTIVE_KEY_ID", "");
|
|
vi.stubEnv("ENCRYPTION_KEY", KEY_V1);
|
|
_resetCryptoCacheForTests();
|
|
}
|
|
|
|
function useKeyMap(active: "v1" | "v2") {
|
|
vi.unstubAllEnvs();
|
|
vi.stubEnv("ENCRYPTION_KEYS", JSON.stringify({ v1: KEY_V1, v2: KEY_V2 }));
|
|
vi.stubEnv("ENCRYPTION_ACTIVE_KEY_ID", active);
|
|
vi.stubEnv("ENCRYPTION_KEY", "");
|
|
_resetCryptoCacheForTests();
|
|
}
|
|
|
|
describe("crypto binary codec (binary2)", () => {
|
|
beforeEach(useSingleKey);
|
|
|
|
it("round-trips raw binary payloads verbatim", () => {
|
|
const payloads = [
|
|
Buffer.alloc(0),
|
|
Buffer.from([0x00, 0x01, 0xff, 0xfe]),
|
|
Buffer.from("%PDF-1.7 with ünïcode and |