thedotmack/claude-mem · warning

Rejected path traversal attempt in watch.context.path

Error message

Rejected path traversal attempt in watch.context.path

What it means

claude-mem injects an AGENTS.md-style context file into observer requests, reading the path from watch.context.path in the transcript watch config. Before fetching, the processor resolves that path and requires it to stay inside one of two allowed roots: the project cwd or the claude-mem DATA_DIR. If the resolved path escapes both roots, this security warning is logged and context injection is skipped for that cycle.

Source

Thrown at src/services/transcripts/processor.ts:376

    if (shouldSuppressNativeCodexAgentsContext(watch)) return;

    const workerReady = await ensureWorkerRunning();
    if (!workerReady) return;

    const cwd = session.cwd ?? watch.workspace;
    if (!cwd) return;

    const context = getProjectContext(cwd);
    const projectsParam = context.allProjects.join(',');

    const contextUrl = `/api/context/inject?projects=${encodeURIComponent(projectsParam)}&platformSource=${encodeURIComponent(session.platformSource)}`;
    const agentsPath = expandHomePath(watch.context.path ?? `${cwd}/AGENTS.md`);

    const resolvedAgentsPath = path.resolve(agentsPath);
    const allowedRoots = [path.resolve(cwd), path.resolve(DATA_DIR)];
    const isPathSafe = allowedRoots.some(root => resolvedAgentsPath.startsWith(root + path.sep) || resolvedAgentsPath === root);
    if (!isPathSafe) {
      logger.warn('SECURITY', 'Rejected path traversal attempt in watch.context.path', {
        original: watch.context.path,
        resolved: resolvedAgentsPath,
        allowedRoots
      });
      return;
    }

    let response: Awaited<ReturnType<typeof workerHttpRequest>>;
    try {
      response = await workerHttpRequest(contextUrl);
    } catch (error: unknown) {
      logger.warn('TRANSCRIPT', 'Failed to fetch AGENTS.md context', {
        error: error instanceof Error ? error.message : String(error)
      });
      return;
    }

    if (!response.ok) return;

View on GitHub (pinned to 8bc631a71a)

Solutions

  1. Remove the context.path key so the default `${cwd}/AGENTS.md` is used
  2. Point context.path at a file inside the current project directory
  3. If the file must live elsewhere, copy or symlink it under the project root or DATA_DIR and reference that location

Example fix

// config watch entry - before
{ "name": "codex", "context": { "path": "~/shared/AGENTS.md" } }

// after (default resolves inside cwd)
{ "name": "codex" }
Defensive patterns

Strategy: validation

Validate before calling

import path from 'node:path';

function isContextPathSafe(cwd: string, dataDir: string, configured?: string): boolean {
  const resolved = path.resolve(configured ?? path.join(cwd, 'AGENTS.md'));
  const roots = [path.resolve(cwd), path.resolve(dataDir)];
  return roots.some(r => resolved === r || resolved.startsWith(r + path.sep));
}

// before starting the watcher / shipping config:
if (!isContextPathSafe(process.cwd(), DATA_DIR, watch.context?.path)) {
  throw new Error(`watch '${watch.name}' context.path escapes allowed roots`);
}

Prevention

When it happens

Trigger: A watch entry sets context.path to an absolute path outside the project (e.g. /etc/agents.md or another repo), a relative path whose ../ segments climb out of cwd, or a ~/ path that expandHomePath expands outside both the project and DATA_DIR.

Common situations: Sharing a transcript watch config between machines or projects where cwd differs; pointing context.path at a central dotfiles repo; renaming/moving the project so a previously valid absolute path no longer falls under cwd.

Related errors


AI-assisted analysis of thedotmack/claude-mem@8bc631a71a (2026-08-20). Data as JSON: /api/errors/664f3ee6a6f1db6e. Report an issue: GitHub.