paseo-wiki-viewer 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server/viewer.ts +18 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paseo-wiki-viewer",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Paseo plugin: browse a workspace in wiki-viewer from the Explorer sidebar, and open chat file links there",
5
5
  "keywords": [
6
6
  "paseo",
package/server/viewer.ts CHANGED
@@ -328,6 +328,23 @@ function proxy(req: IncomingMessage, res: ServerResponse, port: number, url: URL
328
328
  req.pipe(upstream);
329
329
  }
330
330
 
331
+ // The viewer reads `_root/<base64url-dir>` asset paths as a replacement for
332
+ // ?root=, so a path sentinel may only name the grant's own root.
333
+ export function rootSentinelsMatch(pathname: string, root: string): boolean {
334
+ let segments: string[];
335
+ try {
336
+ segments = pathname.split("/").map((segment) => decodeURIComponent(segment));
337
+ } catch {
338
+ return false;
339
+ }
340
+ return segments.every((segment, i) => {
341
+ if (segment !== "_root") return true;
342
+ const encoded = segments[i + 1];
343
+ if (!encoded) return false;
344
+ return Buffer.from(encoded.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8") === root;
345
+ });
346
+ }
347
+
331
348
  function text(res: ServerResponse, status: number, body: string) {
332
349
  res.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
333
350
  res.end(body);
@@ -366,6 +383,7 @@ export function createWikiService(readConfig: () => Promise<ViewerConfig>) {
366
383
  grants.delete(grant);
367
384
  return text(res, 403, "wiki grant is no longer valid");
368
385
  }
386
+ if (!rootSentinelsMatch(url.pathname, entry.root)) return text(res, 403, "outside the granted workspace");
369
387
  if (!activeViewer) return text(res, 503, "wiki viewer is not running");
370
388
  entry.expiresAt = Date.now() + GRANT_TTL_MS;
371
389
  proxy(req, res, activeViewer.port, url, entry.root, grant);