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.requestedAtView on GitHub (pinned to 0d1aed942f)
Solutions
- Use the ownerId exactly as returned by the watch starter (its generated id is guaranteed safe).
- Sanitize or regenerate the ownerId: use only [A-Za-z0-9._-] and reject '/' , '\\', and '..'.
- 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
- Only use ids minted by the watch starter.
- Validate any externally supplied id against isSafeWatchOwnerId before filesystem use.
- Never interpolate untrusted strings into file paths.
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
- Clone target must be a subdirectory of ${CLONE_ROOT}
- Invalid upload path
- Upload path must not contain traversal segments
- Path must not contain null bytes
- Path traversal denied
AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-09-08).
Data as JSON: /api/errors/1ced0817d5e9930c.
Report an issue: GitHub.