abhigyanpatwari/GitNexus · error

watch ownerId is not a safe filename component

Error message

watch ownerId is not a safe filename component

What it means

stopRequestPath builds a filesystem path containing the ownerId (watch.stop.<ownerId>.json). Because ownerId becomes a filename component, it must pass isSafeWatchOwnerId; otherwise it could escape the directory (path traversal) or contain separators. An unsafe ownerId is rejected with this error before any file is touched.

Source

Thrown at gitnexus/src/core/auto-sync/starter.ts:528

}

async function readStatusFile(statusPath: string): Promise<WatchStatusRecord | undefined> {
  try {
    const parsed = JSON.parse(await fs.readFile(statusPath, 'utf-8')) as WatchStatusRecord;
    return parsed && typeof parsed === 'object' ? parsed : undefined;
  } catch (err: unknown) {
    if ((err as NodeJS.ErrnoException).code === 'ENOENT') return undefined;
    return {
      state: 'error',
      message: `unable to read status file: ${(err as Error).message}`,
      updatedAt: new Date().toISOString(),
    };
  }
}

function stopRequestPath(paths: AutoSyncWatchPaths, ownerId: string): string {
  if (!isSafeWatchOwnerId(ownerId)) {
    throw new Error('watch ownerId is not a safe filename component');
  }
  return path.join(path.dirname(paths.pidPath), `watch.stop.${ownerId}.json`);
}

async function readStopRequest(filePath: string): Promise<WatchStopRequestRecord | undefined> {
  try {
    const parsed = JSON.parse(await fs.readFile(filePath, 'utf-8')) as WatchStopRequestRecord;
    if (
      parsed &&
      typeof parsed === 'object' &&
      Number.isInteger(parsed.pid) &&
      parsed.pid > 0 &&
      typeof parsed.ownerId === 'string' &&
      parsed.ownerId &&
      typeof parsed.processStartTime === 'string' &&
      parsed.processStartTime &&
      typeof parsed.requestedAt === 'string' &&
      parsed.requestedAt

View on GitHub (pinned to 0d1aed942f)

Solutions

  1. Use the ownerId exactly as returned by the watch starter (its generated id is guaranteed safe).
  2. Sanitize or regenerate the ownerId: use only [A-Za-z0-9._-] and reject '/' , '\\', and '..'.
  3. Validate the id with isSafeWatchOwnerId before calling the stop API.

Example fix

// before
await stopAutoSyncWatch(paths, userInput.id); // e.g. '../evil'

// after
if (!isSafeWatchOwnerId(userInput.id)) {
  throw new Error('ignoring unsafe watch ownerId');
}
await stopAutoSyncWatch(paths, userInput.id);
Defensive patterns

Strategy: validation

Validate before calling

import { isSafeWatchOwnerId } from './auto-sync/starter';
if (!isSafeWatchOwnerId(ownerId)) {
  throw new Error('rejecting unsafe watch ownerId');
}

Type guard

const isSafeOwnerId = (id: string): boolean => /^[A-Za-z0-9._-]+$/.test(id) && !id.includes('..');

Try / catch

try {
  await stopAutoSyncWatch(paths, ownerId);
} catch (err) {
  if (String(err.message).includes('not a safe filename component')) {
    logger.warn('malformed ownerId, ignoring stop request');
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling stopAutoSyncWatch, request, or cleanupWatchFiles (which call stopRequestPath) with an ownerId containing path separators, '..' , or other characters rejected by isSafeWatchOwnerId.

Common situations: Passing a user-supplied or externally deserialized watch id straight into the stop API; an ownerId read from a corrupted/hand-edited state file; forgetting to generate ids via the library's safe generator.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-09-08). Data as JSON: /api/errors/1ced0817d5e9930c. Report an issue: GitHub.