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>
This commit is contained in:
Codeman maintainer committed 2026-10-13 17:09:00 +02:00
1 parent fe50ad0be3
commit 876dbca074
4 files changed
+40 -8

No files matched your search

+5 -1
View File
@@ -201,7 +201,7 @@ export function registerHtmlViewRoutes(app: FastifyInstance, ctx: SessionPort):
return;
}
const cap = htmlViewCapabilities.mint(dirname(resolvedPath), ownerFor(req));
const cap = htmlViewCapabilities.mint(dirname(resolvedPath), ownerFor(req), !inWorkspace);
return {
success: true,
data: { url: `${HTML_VIEW_PREFIX}/${cap}/${encodeURIComponent(basename(resolvedPath))}` },
@@ -238,6 +238,10 @@ export function registerHtmlViewRoutes(app: FastifyInstance, ctx: SessionPort):
if (!contentType) return notFound();
const guard = await loadAttachmentGuardConfig();
if (isBlockedAttachmentPath(resolvedPath, guard.blockedTrees)) return notFound();
// Re-checked per request, like the attachment serve path (file-routes.ts): a
// page minted from outside the workspace before an admin switched confinement
// on stops serving once it is on, instead of living out its capability.
if (record.outsideWorkspace && guard.confineToWorkspace) return notFound();
const stat = await fs.stat(resolvedPath).catch(() => null);
if (!stat?.isFile() || exceedsDownloadLimit(stat.size)) return notFound();