Commit Graph
2012 Commits
Author SHA1 Message Date
Codeman maintainer de06cb12d1 fix(i18n): drop the duplicate zh-CN Attach key (#606 merge-time fix)
ZH_CN already had `Attach: '附加'` (the tile grid's and remote discovery's
Attach buttons). #606 added a second `Attach: '添加附件'` later in the same
object literal, which won and turned every Attach into "add attachment".
The duplicate is removed; 附加 covers the phone toolbar button too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:25:49 +02:00
Codeman maintainer eaa9044520 fix(uploads): drain a refused video body before answering (#606 merge-time fix)
The ?kind=video branch called req.file(), which pipes the request into
busboy, then answered 400 (unsupported container) or 403 (upload dir not a
regular directory) without reading part.file. The server then stopped
reading the body, and a browser fetch, which reads the response only after
its upload is sent, hung on "Uploading 1/1 file" for an Android 3gp or an
.mkv. Both early answers now consume the part first, as the mislabeled
container path already did. (Reviewer-verified in Chromium: hang before,
400 in about 0.2 s after; inject and a Node client do not reproduce the hang.)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:25:07 +02:00
Codeman maintainer 6c7d63d45e Merge pull request #606: feat(mobile): phone Attach button and camera-roll video uploads
Conflicts resolved against #605 (and #602's test mock):
- CLAUDE.md Prompt uploads: #606's rewrite is kept and #605's bounded
  workspace probe sentence follows the remote (SSH) refusal.
- test/routes/session-routes.test.ts: the remote-pane-history and
  bounded-path-probe mocks are kept beside #606's _resetPasteRateBuckets
  import, and both PRs' paste-image tests are kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:21:52 +02:00
Codeman maintainer 6bbfc44054 Merge pull request #605: fix(uploads): probe the workspace bounded before a prompt upload
Conflict resolved in test/routes/session-routes.test.ts: #602's
remote-pane-history mock and #605's bounded-path-probe mock are both kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:20:21 +02:00
Codeman maintainer 3d341f46a4 fix(i18n): zh-CN for the working folder row and its copy toasts (#593 merge-time fix)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:19:17 +02:00
Codeman maintainer f0b762e745 fix(session-options): label a remote or Docker session's working folder correctly (#593 merge-time fix)
The row said the path is "on the machine hosting Codeman" for every session.
A remote SSH session's workingDir is the path on the remote host, and a
Docker session's CLI runs inside the container the host folder is mounted
into. openSessionOptions now sets the description from session.remote /
session.docker (the id it targets was added with the layout fix).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:19:17 +02:00
Codeman maintainer 771c23e261 fix(session-options): stack the working folder row so long paths fit (#593 merge-time fix)
The row was a plain flex `.set-row` with a non-shrinking actions column, so
the <code> took its full max-content width: the label column collapsed to
0px, the path overlapped the description and the Copy button left the row
(off-screen on a phone). It now uses the stacked `set-row-block` layout the
Session color row already uses, and the path shrinks and wraps.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:19:06 +02:00
Codeman maintainer 0ff9b798af Merge pull request #593: feat(session-options): show the session's working folder with a copy button 2026-10-13 18:19:01 +02:00
Codeman maintainer 33c6cf5d20 chore(gesture): revert the stray gesture bundle path comment (#591 merge-time fix)
#591 rebuilt the committed gesture bundle in a worktree whose node_modules sat
in a sibling directory, so its esbuild source comment named that local path.
Nothing in the PR touches the gesture source; the bundle goes back to master's
bytes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:18:22 +02:00
Codeman maintainer 9b9752d997 Merge pull request #591: fix(sessions): restore a session's colour, pin and image watcher after a restart 2026-10-13 18:18:22 +02:00
Codeman maintainer 60fd71470c Merge pull request #590: fix(cases): show the real reason when Link Existing rejects a case 2026-10-13 18:17:49 +02:00
Codeman maintainer 836402c2c4 fix(terminal): a replay strips the 8-bit C1 form of OSC 52 too (#611 merge-time fix)
xterm.js still parses OSC 52 introduced by C1 OSC (\x9d) and ended by C1 ST
(\x9c). OSC52_STRIP_RE matched only the 7-bit forms, so a direct-PTY session
that emitted the C1 form re-copied old text on every replay.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:17:18 +02:00
Codeman maintainer f073742cf7 fix(terminal): an OSC 52 copy hands focus back to the terminal (#611 merge-time fix)
When navigator.clipboard is missing (plain HTTP, install.sh's LAN option) or
refuses the write (an unfocused window), _copyText falls back to a hidden
textarea whose select() takes focus, and removing it left focus on <body>:
the next keystrokes after Claude Code's /copy went nowhere. The handler now
restores the element focused before the copy, as Auto Copy does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:16:56 +02:00
Codeman maintainer f3d7a86c16 Merge pull request #611: feat(terminal): copy OSC 52 clipboard writes from the PTY into the browser clipboard 2026-10-13 18:16:20 +02:00
Codeman maintainer 34b867025d fix(tabs): Escape from a flat-rail web-tab menu returns focus to the row (#582 merge-time fix)
#582 made the web-tab actions menu reachable on the flat rail (ContextMenu or
Shift+F10 on a role=tab row), but closeTabGroupMenu only looked for a tree
item or a Focus shortcut, so Escape dropped focus to <body>. A `.session-tab`
trigger now counts too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:15:21 +02:00
Codeman maintainer 5d31e42db0 fix(tabs): Follow parent again keeps a Focus pin (#528/#582 integration)
#528's browser followParent operation rebuilt the target ref as
{ kind, id } to drop its manual placement, which also dropped #582's
`focus` flag. The browser PUTs whole layouts, so the pin was erased on the
next save. The operation now copies the ref and removes only `placement`.
#528's row-menu test also expects #582's 'Add to Focus' entry now.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:15:02 +02:00
Codeman maintainer a6ce52d3e7 Merge pull request #582: feat(tabs): pin tabs to a Focus section on the vertical rail
Conflicts resolved against #528:
- CLAUDE.md Owner tab layouts: #582's Focus sentences and #528's
  tabGroupId / spawnedTabsFollowParent sentences are both kept.
- i18n.js: both features' ZH_CN entries kept.
- tab-layout-browser.js: the section-model JSDoc documents #528's
  `options.parents` and #582's `focusRefs` return field.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:13:03 +02:00
Codeman maintainer a38eab49cd Merge pull request #528: feat(tabs): place new, child and web tabs into groups
Conflict resolved in docs/api-reference.md: #607's prompt cache section and
#528's tabGroupId section are both kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:10:37 +02:00
Codeman maintainer 9c56f7823d fix(preview): a shell prompt's trailing $ or # stays out of its folder link (#604 merge-time fix)
The folder link pattern treats `:` as a boundary and allows `$`/`#` in a path,
so every default bash prompt (`user@host:~/proj$`, `root@c:/workspace#`)
became a folder link to a path that does not exist, and clicking it showed
"Folder not found". folderLinkPath now strips them, so the link opens the
prompt's cwd.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:09:08 +02:00
Codeman maintainer 076f62fa1b fix(preview): refuse a bare ~ folder in a Docker case too (#604 merge-time fix)
The Docker refusal on POST /api/sessions/:id/folders tested only `~/`, but
listFolder also expands a bare `~` to this host's home, so a Docker case
could list and serve the host home through it (the preview's "Show folder"
for a `~/x.png` link reached it with no typing). A bare `~` now gets the same
400.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:08:38 +02:00
Codeman maintainer 90034d07d7 Merge pull request #604: feat(preview): open ~/, relative and hard-wrapped paths; folder grid 2026-10-13 18:08:12 +02:00
Codeman maintainer 62227145dc Merge pull request #607: feat(telemetry): read out Claude Code's prompt cache state per session 2026-10-13 18:07:15 +02:00
Codeman maintainer 2e65daff83 style(terminal): re-wrap the _maybePageCliTranscript JSDoc to 120 columns (#527 merge-time fix)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:06:35 +02:00
Codeman maintainer 0e11012470 Merge pull request #527: fix(terminal): page a hollow Codex transcript with PageUp/PageDown from the first wheel event
Conflict resolved in terminal-tile.js's @dependency header: #601's
SHELL_LOAD_BASE_QUERY/SHELL_LOAD_QUERY and #527's pageKeysForGesture and
app.tabAlerts are all listed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:06:24 +02:00
Codeman maintainer 5b8313988f fix(terminal): no paneHistoryLines for a remote session (#583/#602 integration)
A remote session's local pane hosts the remote tmux client in the alternate
screen, so its history_size is always 0. #583 read that as "nothing to load"
and hid the partial-history notice, but with #602 a full=1 load reads the real
scrollback from the remote tmux. The route now leaves the field out for remote
sessions, so their notice keeps the byte-based wording and its Load full
history button.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:05:30 +02:00
Codeman maintainer 239f15437b Merge pull request #609: fix(remote): close the row gaps at the join in a remote history load
Conflict resolved in tmux-manager.ts: #609's screen-aware
joinScrollbackAndFrame keeps #602's merge-time SGR reset before the frame,
and #609's join tests expect it. The #601/#602 route test now mocks the
remote read in #609's { scrollback, screen } shape.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:04:05 +02:00
Codeman maintainer f78b820302 fix(terminal): a remote shell load keeps the remote scrollback (#601/#602 integration)
A remote session's local pane hosts the remote tmux client, so it is always
in the alternate screen. #601's alt=recording fallback therefore threw away
the scrollback #602 had just spliced in from the remote tmux on every remote
shell load. The fallback now applies only when no remote scrollback was
read (the read failed, or the session is not remote).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:02:36 +02:00
Codeman maintainer f9f00bdb5e Merge pull request #602: fix(remote): load a remote session's scrollback from the remote tmux
A full-history load of a remote SSH session reads the scrollback from the
remote tmux in one bounded ssh round trip and splices it above the local
frame. A transport failure (ssh 255, timeout, unparseable output) starts a
30 s per-host back-off; a missing remote session is per-session only.

Conflict resolved in session-routes.ts: #602's remote scrollback read runs
before #601's capture helper and its alternate-screen fallback.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:01:15 +02:00
Codeman maintainer 78ed6bf004 docs(terminal): only a full=1 pass without alt=recording is never mux-visible (#601 merge-time fix)
The geometry-retry comment in selectSession and the #435 invariant said the
route never answers full=1 with mux-visible; a shell load's alt=recording on
an alternate-screen pane now is. The conclusion holds (a useFullHistory pass
never sends it), so only the stated reason changes. Also lists
SHELL_LOAD_BASE_QUERY in terminal-tile.js's @dependency header.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:59:20 +02:00
Codeman maintainer 36f8afdaba Merge pull request #601: fix(terminal): load a shell from tmux's rendered history, not the byte recording
Conflicts resolved against #583 and #588:
- One history-depth field: #583's `historyLines` / `capturedHistoryLines`
  (reported as `paneHistoryLines`) is kept, and #601's lines cut now reads it
  instead of a second `capturedHistorySize`. #601's `alternateOn` /
  `capturedAlternateOn` joins it, and the one display-message format string
  ends `#{history_size} #{alternate_on}`.
- selectSession keeps #588's full=1 switch for panes with no scrollback, gated
  off remote and Docker sessions, and #601's SHELL_LOAD_QUERY for shells.
- computeHistoryTruncationNotice names a line count when paneHistoryLines is
  known (#583) and otherwise uses #601's remainder wording.
- docs: api-reference lists both historyLinesCut and paneHistoryLines; the
  notice invariant keys on history_size while the shell's automatic load is
  the one place alternate_on decides.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:59:07 +02:00
Codeman maintainer 1855d9423b fix(terminal): remote and Docker tabs keep the byte tail on a switch (#588 merge-time fix)
A remote (ssh) or Docker session's local pane hosts a nested tmux client in
the alternate screen, so it always reports paneHistoryLines 0 while its byte
tail carries the real scrolled-off output. The new full=1 switch therefore
replaced their scrollback with the visible frame on every revisit. Those
sessions now keep the tail; the pinned test string and the invariants note
say so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:49:43 +02:00
Codeman maintainer 7ba1e51c51 Merge pull request #588: perf(terminal): tab switch to a fullscreen Claude tab loads only its current screen
Conflict resolved in config/test-suites.ts: both new browser test globs kept.
2026-10-13 17:49:16 +02:00
Codeman maintainer ee038ad83c fix(terminal): the history notice waits for the pull already in flight (#583 merge-time fix)
_smoothScrollBy and the touch paths call _maybeLoadMoreHistoryOnScroll on
every frame. Once the viewport sat at row 0, the next frame hit the real
pull's in-flight early return, so its reveal ran at once and the notice
showed the pre-pull tail state for the whole pull. A later frame now leaves
the reveal to the call that started the pull.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:48:11 +02:00
Codeman maintainer f2d5af2b4c Merge pull request #583: fix(terminal): partial-history notice only when there is history to load 2026-10-13 17:47:37 +02:00
Codeman maintainer 50a1b58d2b Merge pull request #613: Give the codeman agent skill to every agent CLI, not just Claude Code 2026-10-13 17:42:58 +02:00
Codeman maintainer 9e8a851a2c fix(spreadsheet-preview): filter and index a shared worksheet rels list once (#612 merge-time fix)
ExcelJS's worksheet pattern is unanchored, so several parsed worksheets can
share one sheetNo and therefore one rels list. repairExcelJsModel filtered and
indexed that list once per worksheet, which let a crafted admitted file run
past the 20 s preview timeout. Each distinct list is now filtered and indexed
once per sheetNo, and the relationship Type strings are built once.
SPREADSHEET_ASSET_VERSION is refreshed for the edited core.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:42:52 +02:00
Codeman maintainer ba5649bcd4 feat(html-view): open network access for rendered pages (owner decision)
Owner decision 2026-10-13 (#547): a rendered .html page behaves like a
file opened from disk in a browser. It may load scripts, styles, fonts,
images and media from any host and make fetch, XHR, WebSocket, beacon
and form requests to any host.

The /html-view Content-Security-Policy is therefore the `sandbox`
directive alone (HTML_VIEW_CSP, now exported and documented): the same
sandbox flags as before, never `allow-same-origin`, and no fetch
directive. It replaces Codeman's own `default-src 'self'` policy on these
responses. Everything that confines a page stays: the opaque origin (no
Codeman cookies or document, API writes with `Origin: null` refused, API
reads not readable cross-origin), and the capability serving only the
page's own folder under every path, symlink, dot, extension and
confinement rule.

A new test builds the route behind registerSecurityHeaders, as in
production, and pins the exact header: one `sandbox` directive with the
current flags, no `allow-same-origin`, no `-src` directive. The security
architecture, architecture invariants, API reference, CLAUDE.md and the
Working With Files wiki page now say what a page can do (reach the
network, read its own folder) and cannot do (Codeman's origin, cookies,
API, other folders), and that this is the owner's decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:39:36 +02:00
Codeman maintainer 1c19641280 style(html-view): prettier-clean the resolved-path dot check
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:20:18 +02:00
Codeman maintainer 79b9181932 feat(file-preview): open markdown clicked in the terminal in the rendered viewer
An in-workspace .md path clicked in the terminal went to the log viewer
(tail -f, a "streaming" header over raw text), because md was kept out
of FILE_PREVIEW_EXTENSIONS on purpose; every other surface already
rendered it. Owner decision 2026-10-13: markdown is a document to read,
so md and markdown join FILE_PREVIEW_EXTENSIONS and a click or tap opens
the file preview, rendered by default with the MD pill for source. The
main pane, every grid tile and the split's Pane B share the link
provider, so all of them route the same way. Other in-workspace text
(.log, .txt, .json, code) keeps the log viewer and its live follow.

FILE_PATH_LINK_PATTERN now also links `.markdown` and `.htm`, which
were not clickable at all.

Tests: file-preview-markdown.test.ts now asserts md and markdown ARE
routed to the preview (and plain text is not); terminal-touch-tap pins a
tapped .md, .markdown and .html opening the preview while .log, .txt and
.json open the log viewer, and a tile-registered provider routing the
same way against the tile's own session. Docs: CLAUDE.md, the
architecture invariants and the wiki (Working With Files, Mobile Guide).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:19:02 +02:00
Codeman maintainer 8f62faf130 refactor(file-preview): one rendersAsPage() for the terminal and the preview
What counts as an HTML page lived in three places: HTML_VIEW_EXTENSIONS
on the server, a /\.html?$/ regex in the terminal link provider and a
HTML_PAGE_EXTS set in panels-ui.js. The two frontend copies are now one
rendersAsPage() next to previewsInFileViewer() in constants.js, used by
the link provider (and through it every grid tile and the split's Pane
B, which share registerFilePathLinkProvider) and by openFilePreview()
and its toolbar. A new test pins it equal to the server's list.

toggleFilePreviewPage() now returns the preview load, so a caller (and
the coming frontend test) can await the flip.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:14:53 +02:00
Codeman maintainer 7b4832b3e4 feat(admin): count revoked html-view capabilities in the logout audit line
The admin forced logout already revoked the user's rendered-HTML
capabilities but recorded only the cookie sessions and web-tab
capabilities it retired. The `user.logout` audit entry now carries
`revokedHtmlViews` next to `revokedWebviews`, and the revocation test
reads it back from admin-audit.jsonl.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:14:00 +02:00
Codeman maintainer 7dd78d9d71 fix(sw): leave rendered HTML pages to the network
The fetch handler skipped only /api/, so a popped-out /html-view/ page (a
top-level navigation the worker intercepts) was put into CacheStorage
despite its `Cache-Control: no-store`, and the offline fallback could
serve it after logout had revoked the capability. /html-view/ is now
skipped like /api/. The new test runs the shipped sw.js against a stub
worker scope and drives its real fetch listener.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:13:29 +02:00
Codeman maintainer 06f1e5ddc8 fix(html-view): answer the rendered page's CORS preflight
A sandboxed page is opaque-origin, so a fetch() of its own data file with
a non-safelisted header preflights with `Origin: null`. The auth hook
refused the OPTIONS (the html-view exemption admitted GET and HEAD only)
and, without a password, the security-headers hook answered a bare 204
with no Access-Control-Allow-Origin, so the fetch failed either way.

The exemption now admits OPTIONS for a live capability, and
registerSecurityHeaders answers it itself with Allow-Origin `*`,
Allow-Methods `GET, HEAD` and Allow-Headers `*` (no credentials are
involved). An unknown or revoked capability still gets the 401, and
writes on the capability path are still refused.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:12:45 +02:00
Codeman maintainer 4544379b3b style(auth): put hasValidWebviewCapability's JSDoc back above it
The html-view helper had been inserted between the web-tab function's
JSDoc and the function, so that doc block described the wrong function.
The helper now sits after hasValidWebviewCapability with its own doc.
No behaviour change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:12:02 +02:00
Codeman maintainer fa23f8be2a feat(html-view): answer Range requests through the shared sendFileBody()
A rendered page's own <video> or <audio> could not seek, because the
route streamed a plain 200 without Accept-Ranges. sendFileBody() and
localFileSource() are now exported from file-routes.ts and the serving
route uses them, so it advertises Accept-Ranges and answers a Range with
206 + Content-Range (or 416), exactly like file-raw, the attachment raw
route and /api/download. The body sender hijacks the reply and copies
every header set before it (the sandbox CSP, CORS, no-store), which the
new test pins on a 206.

Media types stay in ASSET_TYPES: with Range support they work, and they
are served under the same sandbox CSP as everything else.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:11:50 +02:00
Codeman maintainer bc940a3953 fix(html-view): require the resolved file of a mint to be HTML
The mint checked the extension of the requested path, then built the page
URL from the symlink-resolved file name. A `report.html -> notes.txt` link
minted a page URL for the text file. The resolved name now has to be
.html or .htm as well, the mint-side counterpart of the serving route's
resolved-path extension check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:10:39 +02:00
Codeman maintainer 432faf7254 refactor(html-view): validate the mint body with a Zod schema
POST /api/sessions/:id/html-view read `path` off an unchecked cast. It now
goes through HtmlViewOpenSchema in schemas.ts (a non-empty string of at
most 4096 characters without NUL or line breaks, no other keys) via
parseBody, like the other file routes, and keeps its "Missing path
parameter" message for an absent path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:10:24 +02:00
Codeman maintainer 69aa5724e7 fix(html-view): scope the hidden-directory rule below the workspace root
The mint refused an HTML file whose absolute path had any dot segment, so
a workspace that itself lives under a hidden folder (~/.local/src/app)
could never render one of its own pages. For a file inside the workspace
the rule now covers the path below the workspace root, which is all the
page's tree can reach; outside the workspace it still covers the whole
path. A hidden directory below the workspace root is still refused.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:09:28 +02:00
Codeman maintainer 876dbca074 fix(html-view): re-check workspace confinement on every served request
The serving route checked the blocklist per request but not
attachmentConfineToWorkspace, while the attachment serve path
(file-routes.ts) re-checks it every time. A capability minted for a
folder outside the workspace before an admin turned confinement on kept
serving that folder for the rest of its TTL.

The capability record now says whether its folder was admitted from
outside the workspace, and the serving route refuses such a record while
confinement is on. The flag is part of the reuse key, so an
out-of-workspace mint never picks up a capability recorded as
in-workspace for the same folder.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:09:00 +02:00
Codeman maintainer fe50ad0be3 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>
2026-10-13 17:08:13 +02:00