- dispo workbook imports are pinned to DISPO_IMPORT_DIR (default ./imports):
tRPC input rejects absolute paths and .. segments, runtime reader
re-validates containment via path.relative. Closes a path-traversal
class that reached ExcelJS CVEs through admin/compromised tokens.
- image validator now checks the full 8-byte PNG magic, enforces PNG IEND
and JPEG EOI trailers, scans the decoded buffer for markup polyglot
markers (<script, <svg, <iframe, javascript:, onerror=, ...), and
explicitly rejects SVG. Provider-generated covers (DALL-E, Gemini) run
through the same validator before persistence — an untrusted upstream
cannot smuggle a stored-XSS payload past us.
- added image-validation.test.ts and tightened documentation.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Auth.js authorize/signOut: await createAuditEntry on every branch so auth
events land in the audit store before the JWT is minted / session closes.
Previously these were fire-and-forget and would be dropped under DB load.
- Assistant chat: make appendPromptInjectionGuard async and await its own
SecurityAlert audit; add auditUserPromptTurn() that records every user
message turn as an AssistantPrompt entry containing conversationId, length,
SHA-256 fingerprint, pageContext and whether the injection guard fired.
Raw prompt text is intentionally not stored — the hash lets a responder
correlate a chat transcript with a forensic request without the audit
store accumulating a plain-text corpus of everything users typed.
- Replace bare crypto.* with explicit node:crypto imports.
- Document the retention posture in docs/security-architecture.md §6.
Fixes gitea #55.
Client-side validators (reset-password, invite-accept, first-admin setup,
user-create modal) previously checked password.length < 8 while every
server-side Zod schema required .min(12). External API consumers (or a
confused browser UI) could get past the client check but fail at the tRPC
boundary — or worse, quietly under-enforce policy compared to what
admins expect.
Fix: introduce PASSWORD_MIN_LENGTH (12) and PASSWORD_MAX_LENGTH (128) in
@capakraken/shared and import them from every pre-submit client validator
and every server Zod schema. Single source of truth; drift becomes a
compile error rather than a security finding.
Also hardens the AUTH_SECRET runtime check: in addition to the existing
placeholder-blacklist, production startup now rejects secrets shorter
than 32 chars OR with Shannon entropy below 3.5 bits/char. That covers
low-entropy-but-long values like "aaaa..." (38 chars, entropy 0) which
would have passed the previous checks.
Documented the rotation process for AUTH_SECRET + POSTGRES_PASSWORD in
docs/security-architecture.md §3.
Verified:
- pnpm test:unit — 396 files / 1922 tests passed
- pnpm --filter @capakraken/web exec tsc --noEmit — clean
- pnpm --filter @capakraken/api exec tsc --noEmit — clean
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Browser code never calls OpenAI/Azure/Gemini directly; all AI traffic is
server-side tRPC. connect-src is now locked to 'self'. Added object-src 'none',
frame-src 'none', media-src 'self', and worker-src 'self' blob:. style-src
keeps 'unsafe-inline' for React + @react-pdf/renderer (documented residual
risk — script-src is nonce-based so CSS injection cannot escalate to JS).
Added three regression tests covering connect-src no-wildcards, object/frame-src
'none', and worker-src scope.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
checkPromptInjection now NFKD-normalises, strips zero-width / combining
chars, and folds common Cyrillic / Greek homoglyphs before matching. 10
documented bypass examples (fullwidth, ZWJ, ZWSP, soft-hyphen, Cyrillic
е/о, combining marks, LRM, BOM) are covered by unit tests. Security
docs explicitly mark the guard as defense-in-depth — real boundary is
per-tool requirePermission.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>