{"record":{"id":"1cf08fa423df6545","repo":"ruvnet/ruflo","slug":"refusing-symlink-file","errorCode":null,"errorMessage":"refusing symlink: ${file}","messagePattern":"refusing symlink: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/flywheel-transaction.ts","lineNumber":176,"sourceCode":"function stateDir(root: string): string {\n  return path.join(root, ...STATE_DIR);\n}\n\nfunction statePath(root: string): string {\n  return path.join(stateDir(root), STATE_FILE);\n}\n\nfunction lockPath(root: string): string {\n  return path.join(stateDir(root), LOCK_FILE);\n}\n\nfunction receiptDir(root: string): string {\n  return path.join(stateDir(root), RECEIPTS_DIR);\n}\n\nfunction assertSafeFile(file: string): void {\n  try {\n    if (fs.lstatSync(file).isSymbolicLink()) throw new Error(`refusing symlink: ${file}`);\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n  }\n}\n\nfunction ensureDir(root: string): void {\n  fs.mkdirSync(receiptDir(root), { recursive: true, mode: 0o700 });\n  assertSafeFile(statePath(root));\n  assertSafeFile(lockPath(root));\n}\n\nfunction emptyState(): FlywheelTransactionState {\n  return {\n    version: STATE_VERSION,\n    activeChampionRef: null,\n    activePolicy: null,\n    activeGateVersion: null,\n    activePolicySchemaVersion: null,","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/services/flywheel-transaction.ts#L158-L194","documentation":"Thrown by assertSafeFile() when a file in the flywheel transaction state directory (.claude-flow/flywheel-v1/) is a symbolic link. This is a deliberate security hardening: an attacker who can plant a symlink can redirect atomic writes (state, lock, receipts) to an arbitrary file, corrupting it or exfiltrating data. The check uses lstatSync so it catches symlinks even when the target does not exist.","triggerScenarios":"The transaction-state.json, transaction-state.lock, or a receipt .json file under .claude-flow/flywheel-v1/ (or its receipts/ subdir) is a symlink. Triggered on every read/write/commit path via assertSafeFile(), ensureDir(), atomicWriteJson(), readFlywheelTransactionState(), and readFlywheelReceipt().","commonSituations":"A compromised or misconfigured environment where someone symlinked the state dir for sharing across projects; a backup/restore that created symlinks; a test fixture that used symlinks; an actual attack attempt.","solutions":["Remove the offending symlink: rm <path> and let the code recreate a real file.","Audit .claude-flow/flywheel-v1/ for unexpected symlinks (find . -type l).","Ensure the state directory is on a trusted filesystem with restrictive permissions (mode 0o700, set by ensureDir).","If symlinking was intentional for a shared workspace, stop — the invariant is load-bearing; use a shared volume or copy instead."],"exampleFix":"# before: .claude-flow/flywheel-v1/transaction-state.json -> /tmp/state.json (symlink)\nrm .claude-flow/flywheel-v1/transaction-state.json\n# let the next transaction recreate a real file","handlingStrategy":"validation","validationCode":"function assertNoSymlinksIn(dir: string): void {\n  for (const name of fs.readdirSync(dir)) {\n    const full = path.join(dir, name);\n    if (fs.lstatSync(full).isSymbolicLink()) {\n      throw new Error(`unexpected symlink in flywheel state dir: ${full}`);\n    }\n  }\n}\n// run before any transaction operation\nassertNoSymlinksIn(path.join(root, '.claude-flow', 'flywheel-v1'));","typeGuard":"const isRegularFile = (p: string): boolean => {\n  try { return fs.lstatSync(p).isFile(); } catch { return false; }\n};","tryCatchPattern":"try {\n  readFlywheelTransactionState(root);\n} catch (e) {\n  if (e instanceof Error && /refusing symlink/.test(e.message)) {\n    // quarantine the dir and alert — do NOT auto-delete, it may be an attack\n    throw new Error(`security: flywheel state contains a symlink — investigate ${e.message}`);\n  }\n  throw e;\n}","preventionTips":["Keep .claude-flow/flywheel-v1/ on a trusted local filesystem with mode 0o700.","Never symlink state/lock/receipt files for sharing.","Periodically audit the state dir for symlinks in CI."],"tags":["security","filesystem","symlink","hardening","receipt"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}