Files
Codeman/src/web/sensitive-path.ts
T
Aamer Akhter f1c64994ad COD-37 add server-side attachment pipeline (registry, magic-link, path guard)
Adds the foundation for serving local files to the browser as live external
attachments with a stable id, so requests never carry arbitrary absolute paths.

- attachment-registry: in-memory, session-scoped registry. registerExternalAttachment
  validates an absolute path, resolves symlinks, enforces the path guard, and mints
  an `att_<uuid>` id; records are cleared when the session is removed.
- attachment path guard: a configurable blocklist (secret locations + /root,/etc
  trees, extendable via attachmentBlockedPaths / CODEMAN_ATTACHMENT_BLOCKED_PATHS)
  plus an optional, default-off workspace-confinement mode. Shares one
  sensitive-path blocklist (web/sensitive-path.ts) with /api/download, which is
  refactored to use the extracted module instead of an inline copy.
- terminal magic links: the session scans output for codeman://attach?path=... and
  emits `attachmentRequested`; the web server registers the file and broadcasts an
  `attachment:detected` SSE event. `codeman attach <path>` (CLI) prints the magic
  link or POSTs directly when a session id is known.
- image watcher: detects png/pdf/docx/pptx dropped into a session's working dir and
  emits `attachment:detected`.
- routes: POST /api/sessions/:id/attachments (register) and
  GET /api/sessions/:id/attachments/:attachmentId/raw (serve), both re-checking the
  guard before streaming.

Document previews/thumbnails and the attachment-history drawer build on this
foundation and land separately.

Verified: tsc --noEmit, lint, format, frontend-syntax, full test:ci (2846 passed),
and a server boot smoke (/api/status 200).
2026-06-11 10:27:09 +02:00

42 lines
1.6 KiB
TypeScript

/**
* @fileoverview Shared sensitive-path blocklist.
*
* A small defense-in-depth blocklist of absolute paths that must never be
* served to the browser regardless of how the path was obtained (workspace
* download, cross-workspace attachment registration, raw/preview serving).
*
* This is intentionally a BLOCKLIST, not a workspace-confinement check:
* cross-workspace attachment is a supported feature (codeman-publish skill +
* the automated review-card loop attaching files under ~/.codeman/), so a
* strict session-workspace boundary would break legitimate use. The blocklist
* rejects well-known secret locations (system password files, SSH keys, cloud
* credentials, dotenv files) while leaving ordinary cross-workspace files
* attachable.
*
* Callers MUST resolve symlinks (realpath) BEFORE calling isSensitivePath so a
* symlink pointing at a sensitive target is also caught.
*/
import { homedir } from 'node:os';
const SENSITIVE_PATTERNS: RegExp[] = [
/^\/etc\/shadow$/,
/^\/etc\/gshadow$/,
/^\/etc\/master\.passwd$/,
new RegExp(`^${homedir().replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\/\\.ssh\\/`),
/\/\.env$/,
/\/\.env\./,
/\/credentials(\.json|\.yml|\.yaml|\.xml)?$/i,
/\/\.aws\/credentials$/,
/\/\.gcloud\/credentials\.db$/,
/\/\.docker\/config\.json$/,
];
/**
* Returns true if the given ABSOLUTE, symlink-resolved path matches the
* sensitive-file blocklist and must not be served to the browser.
*/
export function isSensitivePath(absPath: string): boolean {
return SENSITIVE_PATTERNS.some((pattern) => pattern.test(absPath));
}