(1) workbookPathSchema accepts any absolute path ending in .xlsx; consumer uses path.resolve() without base-directory check. Admin can trigger parsing of arbitrary .xlsx anywhere the process can read. ExcelJS has had zip-slip/XXE CVEs. (2) validateImageDataUrl checks only first 18 bytes — polyglot PNG/SVG files pass magic-byte check but contain HTML/SVG tail → stored XSS if ever served without strict MIME.
Evidence
packages/application/src/use-cases/dispo-import/read-workbook.ts:60-85 — path.resolve without allowlist
packages/api/src/lib/image-validation.ts — only 24-base64-char prefix check
(1) Admin (or compromised admin) can reach parser CVE on arbitrary filesystem. (2) Stored XSS via polyglot image.
Proposed Fix
(1) Require import path relative to configured IMPORT_DIR via path.relative(IMPORT_DIR, resolved).startsWith('..') check. Reject absolute paths from client. (2) Re-encode all uploaded images through sharp to canonical PNG/JPEG; never round-trip raw data URL. Apply validation to Gemini-generated covers too.
Acceptance Criteria
Dispo import: absolute path from client → 400
Image upload: re-encoded via sharp; tail HTML stripped
Resolved in commit c4b01c1bfc41605009a33910e458e03c51a33155 on branch `security/audit-2026-04-17`.
## What changed
**Dispo workbook path allowlist**
- New `DISPO_IMPORT_DIR` env var (defaults to `./imports`) pins the root for workbook reads.
- tRPC input schema (`workbookPathSchema`) rejects absolute paths and any segment equal to `..`.
- `read-workbook.ts` re-validates containment at runtime using `path.relative`.
- Closes the path-traversal class that would have let an admin (or compromised admin token) point ExcelJS at arbitrary files on disk.
**Image upload / provider-image polyglot defence**
- `validateImageDataUrl` now checks the full 8-byte PNG magic, enforces PNG IEND and JPEG FFD9 trailers, scans the decoded buffer for markup polyglot markers (`<script`, `<svg`, `<iframe`, `<object`, `<embed`, `<html`, `<!doctype`, `javascript:`, `onerror=`, `onload=`), and explicitly rejects SVG.
- Provider-generated covers (DALL-E, Gemini) run through the same validator before persistence.
- Bounded decoder (16 MB max) for validation.
## Tests
- New `packages/api/src/__tests__/image-validation.test.ts` with 10 cases.
- Application/API/web suites green (195 / 1932 / 1286).
## Docs
- `docs/security-architecture.md` §5 updated. `.env.example` documents `DISPO_IMPORT_DIR`.
Closing.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
(1)
workbookPathSchemaaccepts any absolute path ending in.xlsx; consumer usespath.resolve()without base-directory check. Admin can trigger parsing of arbitrary.xlsxanywhere the process can read. ExcelJS has had zip-slip/XXE CVEs. (2)validateImageDataUrlchecks only first 18 bytes — polyglot PNG/SVG files pass magic-byte check but contain HTML/SVG tail → stored XSS if ever served without strict MIME.Evidence
packages/application/src/use-cases/dispo-import/read-workbook.ts:60-85 — path.resolve without allowlistpackages/api/src/lib/image-validation.ts — only 24-base64-char prefix checkpackages/api/src/router/project-cover.ts:117 — Gemini-generated covers skip validateImageDataUrl entirelyImpact
(1) Admin (or compromised admin) can reach parser CVE on arbitrary filesystem. (2) Stored XSS via polyglot image.
Proposed Fix
(1) Require import path relative to configured IMPORT_DIR via
path.relative(IMPORT_DIR, resolved).startsWith('..')check. Reject absolute paths from client. (2) Re-encode all uploaded images throughsharpto canonical PNG/JPEG; never round-trip raw data URL. Apply validation to Gemini-generated covers too.Acceptance Criteria
Parent Epic: #1
Source: Full-Codebase Security Audit 2026-04-16 (B-7, B-9)
Resolved in commit
c4b01c1bfcon branchsecurity/audit-2026-04-17.What changed
Dispo workbook path allowlist
DISPO_IMPORT_DIRenv var (defaults to./imports) pins the root for workbook reads.workbookPathSchema) rejects absolute paths and any segment equal to...read-workbook.tsre-validates containment at runtime usingpath.relative.Image upload / provider-image polyglot defence
validateImageDataUrlnow checks the full 8-byte PNG magic, enforces PNG IEND and JPEG FFD9 trailers, scans the decoded buffer for markup polyglot markers (<script,<svg,<iframe,<object,<embed,<html,<!doctype,javascript:,onerror=,onload=), and explicitly rejects SVG.Tests
packages/api/src/__tests__/image-validation.test.tswith 10 cases.Docs
docs/security-architecture.md§5 updated..env.exampledocumentsDISPO_IMPORT_DIR.Closing.