mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01: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
@@ -28,7 +28,7 @@ import type { FastifyInstance } from 'fastify';
|
||||
import { createReadStream, realpathSync } from 'node:fs';
|
||||
import fs from 'node:fs/promises';
|
||||
import { homedir, tmpdir } from 'node:os';
|
||||
import { basename, dirname, extname, isAbsolute, resolve, sep } from 'node:path';
|
||||
import { basename, dirname, extname, isAbsolute, relative, resolve, sep } from 'node:path';
|
||||
import { ApiErrorCode, createErrorResponse } from '../../types.js';
|
||||
import { isBlockedAttachmentPath, isUnderTree, loadAttachmentGuardConfig } from '../../config/attachment-guard.js';
|
||||
import { HTML_VIEW_PREFIX, htmlViewCapabilities } from '../../html-view-capabilities.js';
|
||||
@@ -162,7 +162,7 @@ export function registerHtmlViewRoutes(app: FastifyInstance, ctx: SessionPort):
|
||||
return;
|
||||
}
|
||||
try {
|
||||
resolvedPath = realpathSync(body.path);
|
||||
resolvedPath = await fs.realpath(body.path);
|
||||
} catch {
|
||||
reply.code(404).send(createErrorResponse(ApiErrorCode.NOT_FOUND, 'File not found'));
|
||||
return;
|
||||
@@ -215,22 +215,27 @@ export function registerHtmlViewRoutes(app: FastifyInstance, ctx: SessionPort):
|
||||
if (!record) return notFound();
|
||||
|
||||
// Fastify has already percent-decoded the wildcard. Hidden files and
|
||||
// directories are never served, whatever their extension.
|
||||
// directories are never served, whatever their extension. This is the cheap
|
||||
// reject on the requested name; the same two rules run again on the resolved
|
||||
// path below.
|
||||
const segments = (rest || '').split('/').filter(Boolean);
|
||||
if (segments.length === 0 || segments.some((s) => s.startsWith('.'))) return notFound();
|
||||
|
||||
const ext = extensionOf(segments[segments.length - 1]);
|
||||
const contentType = ASSET_TYPES[ext];
|
||||
if (!contentType) return notFound();
|
||||
if (!ASSET_TYPES[extensionOf(segments[segments.length - 1])]) return notFound();
|
||||
|
||||
let resolvedPath: string;
|
||||
try {
|
||||
resolvedPath = realpathSync(resolve(record.rootDir, segments.join(sep)));
|
||||
resolvedPath = await fs.realpath(resolve(record.rootDir, segments.join(sep)));
|
||||
} catch {
|
||||
return notFound();
|
||||
}
|
||||
// Symlinks are resolved above, so this is the real containment check.
|
||||
// Symlinks are resolved above, so this is the real containment check, and the
|
||||
// dot and extension rules have to hold for where a link LANDS as well: a
|
||||
// `cfg.json -> .git/config` inside the page's folder passes both on its own name.
|
||||
if (!isUnderTree(resolvedPath, record.rootDir)) return notFound();
|
||||
if (relative(record.rootDir, resolvedPath).split(sep).some((s) => s.startsWith('.'))) return notFound();
|
||||
// Typed by what the bytes are, the resolved name.
|
||||
const contentType = ASSET_TYPES[extensionOf(resolvedPath)];
|
||||
if (!contentType) return notFound();
|
||||
const guard = await loadAttachmentGuardConfig();
|
||||
if (isBlockedAttachmentPath(resolvedPath, guard.blockedTrees)) return notFound();
|
||||
|
||||
|
||||
Reference in new issue
Block a user