mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-20 09:09:35 +02:00
fix(html-view): apply the dot and extension rules to the resolved path
The serving route checked hidden segments and the asset allowlist on the requested name only; after realpath it checked containment and the blocklist. A symlink `cfg.json -> .git/config` inside the page's folder was therefore served as 200 application/json. The dot rule now also runs on the segments of relative(rootDir, resolvedPath), and the extension allowlist on the resolved file name, which also types the response. Both realpath calls on the request path (the mint of an out-of-workspace file and the serving route) are now `await fs.realpath` instead of realpathSync, so a slow mount stalls one request, not the event loop. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ded7911d1e
commit
fe50ad0be3
4 files changed
+31
-11
No files matched your search
@@ -373,7 +373,7 @@ A file path an agent prints is a link on both surfaces it can appear on, and cli
|
||||
|
||||
⚠️ **The terminal routes an out-of-workspace path to the preview, not the log viewer.** The log viewer spawns `tail -f` and allows only the workspace, `/var/log` and `~/logs`, so an external `.log`/`.json`/code path answered `Path must be within working directory or allowed log directories` while the SAME path clicked in the response viewer previewed fine. `activate()` now checks `_isExternalPreviewPath` alongside `previewsInFileViewer`. In-workspace text keeps the tail viewer, which is the point of it (live follow), except `.html`/`.htm`, which always go to the preview so they render as a page (see below); nothing widened `file-stream-manager`'s allowlist, so no `tail -f` is spawned on an arbitrary host path.
|
||||
|
||||
**Text reuses the edit-mode allowlist; markup stays download-only.** `TEXT_ATTACHMENT_EXTENSIONS` IS `EDITABLE_EXTENSIONS` (`config/file-editing.ts`) rather than a second curated list that would drift from it: if the viewer would open a file for editing inside the workspace, the same file outside it can be read. The justification for widening is that the agent in the session can already `cat` any of these and the picker already previews them, so the suffix was never the confidentiality gate; the path guard is (sensitive-file blocklist, `/root` and `/etc` trees, realpath first). ⚠️ Two consequences had to be handled at the same time: `~/.codeman*/state.json` joined `isSensitivePath` (it persists `SessionState.envOverrides`, and the env allowlist admits key-shaped names like `GEMINI_API_KEY`, so it can hold a live credential), and `html`/`htm` joined `svg` in `serveRawFile`'s **download-only** branch so that widening what can be READ never widens what can RUN on our own origin. Text with no dedicated MIME entry goes out as inert `text/plain; charset=utf-8` + `nosniff`, matching the picker. ⚠️ **The one place HTML renders is `/html-view/<cap>/...`** (`html-view-routes.ts`, `html-view-capabilities.ts`), never these routes: `POST /api/sessions/:id/html-view` mints a capability for the HTML file's own directory, and every response carries `Content-Security-Policy: sandbox` WITHOUT `allow-same-origin`, so the page is opaque-origin even as a top-level tab and cannot read Codeman or drive its API. The directory is served recursively to the page's scripts (non-dot, asset-allowlisted, realpath-confined, blocklist applied, GET/HEAD only), which is why minting is refused for an HTML file in a hidden directory or directly in a broad root (`/`, home, `/tmp`, `/var/tmp` and `$TMPDIR` (named literally, since macOS sets `TMPDIR` in every terminal), the cases and user-space roots, a drive's mount point such as WSL's `/mnt/d`) or in any directory above one of them (`/home`, `/Users`, macOS's `/private`; `isBroadHtmlViewRoot`). The preview renders it by default; the **Page** pill (`htmlRendered`, per-device like `mdRendered`) and any refused mint fall through to the source view with its Edit button. Tests: `test/routes/html-view-routes.test.ts`, the html-view block in `test/webview-auth-exemption.test.ts`, `test/webview-capability-revocation.test.ts`. The by-id text preview is bounded like the workspace one: a `Range` request for the first 512KB (a real partial read, not a discarded 50MB download) plus a 500-line cap, with the footer saying so.
|
||||
**Text reuses the edit-mode allowlist; markup stays download-only.** `TEXT_ATTACHMENT_EXTENSIONS` IS `EDITABLE_EXTENSIONS` (`config/file-editing.ts`) rather than a second curated list that would drift from it: if the viewer would open a file for editing inside the workspace, the same file outside it can be read. The justification for widening is that the agent in the session can already `cat` any of these and the picker already previews them, so the suffix was never the confidentiality gate; the path guard is (sensitive-file blocklist, `/root` and `/etc` trees, realpath first). ⚠️ Two consequences had to be handled at the same time: `~/.codeman*/state.json` joined `isSensitivePath` (it persists `SessionState.envOverrides`, and the env allowlist admits key-shaped names like `GEMINI_API_KEY`, so it can hold a live credential), and `html`/`htm` joined `svg` in `serveRawFile`'s **download-only** branch so that widening what can be READ never widens what can RUN on our own origin. Text with no dedicated MIME entry goes out as inert `text/plain; charset=utf-8` + `nosniff`, matching the picker. ⚠️ **The one place HTML renders is `/html-view/<cap>/...`** (`html-view-routes.ts`, `html-view-capabilities.ts`), never these routes: `POST /api/sessions/:id/html-view` mints a capability for the HTML file's own directory, and every response carries `Content-Security-Policy: sandbox` WITHOUT `allow-same-origin`, so the page is opaque-origin even as a top-level tab and cannot read Codeman or drive its API. The directory is served recursively to the page's scripts (non-dot, asset-allowlisted, realpath-confined, blocklist applied, GET/HEAD only; the dot and extension rules run on the requested name AND on the symlink-resolved target, or a `cfg.json -> .git/config` link would serve the git config), which is why minting is refused for an HTML file in a hidden directory or directly in a broad root (`/`, home, `/tmp`, `/var/tmp` and `$TMPDIR` (named literally, since macOS sets `TMPDIR` in every terminal), the cases and user-space roots, a drive's mount point such as WSL's `/mnt/d`) or in any directory above one of them (`/home`, `/Users`, macOS's `/private`; `isBroadHtmlViewRoot`). The preview renders it by default; the **Page** pill (`htmlRendered`, per-device like `mdRendered`) and any refused mint fall through to the source view with its Edit button. Tests: `test/routes/html-view-routes.test.ts`, the html-view block in `test/webview-auth-exemption.test.ts`, `test/webview-capability-revocation.test.ts`. The by-id text preview is bounded like the workspace one: a `Range` request for the first 512KB (a real partial read, not a discarded 50MB download) plus a 500-line cap, with the footer saying so.
|
||||
|
||||
**Media is single-sourced across the two preview paths.** `VIDEO_ATTACHMENT_EXTENSIONS` / `AUDIO_ATTACHMENT_EXTENSIONS` live in `attachment-registry.ts` and are imported by `file-content`'s media classification, so a clip plays identically whether it is in the workspace or reached by id from outside it. They diverged first: the workspace path had its own inline sets and the registry allowlist had no media at all, so a video an agent wrote to `/tmp` was refused as an unsupported type while the same file inside the repo played. ⚠️ Three things have to line up for a player rather than a dead frame: the extension in the allowlist, a **real MIME entry** in `MIME_TYPES` (a `<video>` refuses to decode `application/octet-stream`, which presents as a player that renders and then does nothing), and the range-aware body (`serveRawFile` → `sendFileBody`) that makes the scrub bar work. `getAttachmentType()` returns the `video`/`audio` members of `AttachmentDetectedType` for them; the attachment card has no per-type CSS and its thumbnail falls back to the type label, since `generateFirstPageThumbnail` has no media branch and answers 204. ⚠️ The image-watcher keeps its OWN narrow detection list (`png/pdf/docx/pptx`), so this does not start popping cards for every video an agent writes.
|
||||
|
||||
|
||||
Reference in new issue
Block a user