{"record":{"id":"c319d8a140f3d30c","repo":"nocobase/nocobase","slug":"refusing-to-remove-label-at-resolved-becau","errorCode":null,"errorMessage":"Refusing to remove ${label} at \"${resolved}\" because it is too broad.","messagePattern":"Refusing to remove (.+?) at \"(.+?)\" because it is too broad\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/core/cli/src/commands/app/shared.ts","lineNumber":37,"sourceCode":"type RemovePathOptions = {\n  retryCommand?: string;\n};\ntype NodeFileSystemError = Error & {\n  code?: unknown;\n};\n\nexport function resolveConfiguredPath(value: unknown): string | undefined {\n  return resolveConfiguredEnvPath(value);\n}\n\nfunction assertSafeRemovalPath(target: string, label: string): void {\n  const resolved = path.resolve(target);\n  const cwd = path.resolve(process.cwd());\n  const home = path.resolve(os.homedir());\n  const root = path.parse(resolved).root;\n\n  if (resolved === root || resolved === cwd || resolved === home) {\n    throw new Error(`Refusing to remove ${label} at \"${resolved}\" because it is too broad.`);\n  }\n}\n\nfunction getErrorCode(error: unknown): string | undefined {\n  if (!(error instanceof Error)) {\n    return undefined;\n  }\n  const { code } = error as NodeFileSystemError;\n  return typeof code === 'string' ? code : undefined;\n}\n\nfunction isPermissionDeniedError(error: unknown): boolean {\n  const code = getErrorCode(error);\n  return code === 'EACCES' || code === 'EPERM';\n}\n\nfunction formatOriginalError(error: unknown): string {\n  const message = error instanceof Error ? error.message : String(error);","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/nocobase/nocobase/blob/fa42722fefe44265490dff2c27d79e2882bce4fa/packages/core/cli/src/commands/app/shared.ts#L19-L55","documentation":"assertSafeRemovalPath (packages/core/cli/src/commands/app/shared.ts, used by removePathIfExists) resolves the target path and rejects removal if it equals the filesystem root, the current working directory, or the user's home directory, throwing 'Refusing to remove <label> at \"<resolved>\" because it is too broad.' This is a safety guard against catastrophic rm -rf of critical directories during app cleanup.","triggerScenarios":"Calling removePathIfExists (e.g. during `app destroy` cleanup of app dir / data / storage paths) when the configured path variable points at /, the CWD, or $HOME — typically because a path config is empty, '.', unset, or wrongly set to home.","commonSituations":"App env config with an empty/missing app root so the cleanup target falls back to '.' or '/'; users setting storage/data directories to ~; misconfigured DEST/APP_ROOT env vars; running the CLI from inside the directory meant to be removed.","solutions":["Fix the path configuration so the cleanup target is the specific app directory (e.g. ~/.nocobase/apps/<name>), not its parent.","Ensure the CLI is run from the intended working directory, since CWD itself is protected.","Check the app env config for empty or '.' values for dir/storage/data paths before destroy.","If you truly need to remove a broad directory, do it manually with explicit rm -rf of the precise path after verifying contents."],"exampleFix":"// before (config)\nAPP_ROOT=          # falls back to '.', 'too broad' error\n// after\nAPP_ROOT=~/.nocobase/apps/myapp","handlingStrategy":"validation","validationCode":"import path from 'path';\nimport os from 'os';\nfunction isSafeRemovalPath(target: string): boolean {\n  const resolved = path.resolve(target);\n  const protectedPaths = [path.parse(resolved).root, path.resolve(process.cwd()), path.resolve(os.homedir())];\n  return !protectedPaths.includes(resolved);\n}\nif (!isSafeRemovalPath(cleanupPath)) throw new Error(`Refusing cleanup: ${cleanupPath} resolves to a protected directory; fix the app path config`);","typeGuard":"function isSpecificAppPath(target: string): boolean {\n  const resolved = path.resolve(target);\n  return resolved.split(path.sep).filter(Boolean).length >= 3 && resolved.startsWith(path.resolve(os.homedir(), '.nocobase'));\n}","tryCatchPattern":"try {\n  await removePathIfExists(dir, { label: 'app dir' });\n} catch (error) {\n  if ((error as Error).message.includes('because it is too broad')) {\n    console.error(`Cleanup target ${(error as Error).message.match(/at \"(.*)\"/)?.[1]} is protected — correct the app path configuration instead of overriding.`);\n  } else throw error;\n}","preventionTips":["Never leave app path / storage / data config values empty or set to '.' or '~'.","Point all directory config at explicit per-app subdirectories (e.g. ~/.nocobase/apps/<name>).","Avoid running destroy from inside the directory being removed (CWD is protected and commands may misbehave).","Print resolved paths in a dry-run before enabling destructive cleanup in scripts."],"tags":["cli","filesystem-safety","destroy","path-validation"],"backgroundTag":"unsafe-path-removal-guard","analyzedSha":"fa42722fefe44265490dff2c27d79e2882bce4fa","analyzedAt":"2026-09-01T00:54:31.202Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}