mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
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>
This commit is contained in:
1 parent
c1fde8e639
commit
ba5649bcd4
7 files changed
+70
-12
No files matched your search
@@ -12,6 +12,7 @@
|
||||
* opened as a top-level tab: it can run its own scripts but cannot read the Codeman
|
||||
* document, its cookies or its API. That is what makes rendering safe at all; see
|
||||
* html-view-capabilities.ts for why the route authenticates on a capability.
|
||||
* Network access is deliberately OPEN (owner decision, see {@link HTML_VIEW_CSP}).
|
||||
*
|
||||
* Path policy: the HTML file itself is admitted like the file preview admits a file
|
||||
* (inside the session workspace, or outside it under the attachment guard), and the
|
||||
@@ -76,10 +77,23 @@ const ASSET_TYPES: Record<string, string> = {
|
||||
};
|
||||
|
||||
/**
|
||||
* The sandbox every response is served under. No `allow-same-origin`: that one
|
||||
* token is the difference between "a page" and "a page that can drive Codeman".
|
||||
* The whole Content-Security-Policy every response is served under: the `sandbox`
|
||||
* directive and nothing else. It REPLACES Codeman's own policy (`default-src 'self'`
|
||||
* and the rest, set earlier by registerSecurityHeaders). No `allow-same-origin`:
|
||||
* that one token is the difference between "a page" and "a page that can drive
|
||||
* Codeman".
|
||||
*
|
||||
* ⚠️ Network access is OPEN, by the owner's decision (2026-10-13, #547): a rendered
|
||||
* page behaves like a file opened from disk in a browser, so there is deliberately
|
||||
* no fetch directive (`default-src`, `script-src`, `connect-src`, ...). It loads
|
||||
* scripts, styles, fonts, images and media from anywhere (an agent's dashboard
|
||||
* pulling Chart.js from a CDN) and may fetch, XHR, open WebSockets, send beacons and
|
||||
* post forms anywhere. What confines it is the opaque origin (no Codeman cookies, no
|
||||
* Codeman API) and the capability serving only its own folder. Do not add a fetch
|
||||
* directive, or `allow-same-origin`, without the owner. test/routes/html-view-routes.test.ts
|
||||
* pins the exact value.
|
||||
*/
|
||||
const SANDBOX_CSP = 'sandbox allow-scripts allow-forms allow-popups allow-modals allow-downloads';
|
||||
export const HTML_VIEW_CSP = 'sandbox allow-scripts allow-forms allow-popups allow-modals allow-downloads';
|
||||
|
||||
function extensionOf(path: string): string {
|
||||
return extname(path).toLowerCase().replace(/^\./, '');
|
||||
@@ -254,7 +268,7 @@ export function registerHtmlViewRoutes(app: FastifyInstance, ctx: SessionPort):
|
||||
const stat = await fs.stat(resolvedPath).catch(() => null);
|
||||
if (!stat?.isFile() || exceedsDownloadLimit(stat.size)) return notFound();
|
||||
|
||||
reply.header('Content-Security-Policy', SANDBOX_CSP);
|
||||
reply.header('Content-Security-Policy', HTML_VIEW_CSP);
|
||||
reply.header('Content-Type', contentType);
|
||||
reply.header('Cache-Control', 'no-store');
|
||||
reply.header('Referrer-Policy', 'no-referrer');
|
||||
|
||||
Reference in new issue
Block a user