{"record":{"id":"d6e4a6fe572148e6","repo":"yamadashy/repomix","slug":"refusing-to-trust-configname-the-remote-reposi","errorCode":null,"errorMessage":"Refusing to trust ${configName}: the remote repository's config must be a regular file, not a symlink.","messagePattern":"Refusing to trust (.+?): the remote repository's config must be a regular file, not a symlink\\.","errorType":"exception","errorClass":"RepomixError","httpStatus":null,"severity":"error","filePath":"src/cli/prompts/remoteConfigTrustPrompt.ts","lineNumber":112,"sourceCode":"};\n\n/**\n * Reject a config that is not a regular file inside the cloned repo. A repository\n * can ship `repomix.config.js` as a symlink (git preserves symlinks on POSIX), and\n * config resolution follows it. That would let the reviewed bytes come from outside\n * the owner-only temp dir: the content shown could be an unrelated local file, and\n * the target could be swapped between this read and the later load. Everything we\n * show the user must live in the tree we just cloned.\n */\nconst assertConfigIsContained = async (\n  configPath: string,\n  repoDir: string,\n  deps: Pick<ConfirmRemoteConfigTrustDeps, 'lstat' | 'realpath'>,\n): Promise<void> => {\n  const configName = path.basename(configPath);\n  const stats = await deps.lstat(configPath);\n  if (stats.isSymbolicLink() || !stats.isFile()) {\n    throw new RepomixError(\n      `Refusing to trust ${configName}: the remote repository's config must be a regular file, not a symlink.`,\n    );\n  }\n\n  const [realConfigPath, realRepoDir] = await Promise.all([deps.realpath(configPath), deps.realpath(repoDir)]);\n  const relative = path.relative(realRepoDir, realConfigPath);\n  if (relative.startsWith('..') || path.isAbsolute(relative)) {\n    throw new RepomixError(`Refusing to trust ${configName}: it resolves outside the cloned repository.`);\n  }\n};\n\n/**\n * Interactively confirm before a cloned remote repository's config is trusted\n * (via `--remote-trust-config` / `REPOMIX_REMOTE_TRUST_CONFIG`). Shows the config\n * that is about to run, then asks the user. Throws `OperationCancelledError` when\n * the user declines.\n *\n * Proceeds without prompting when: `--force` is passed, the shell is","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/yamadashy/repomix/blob/f465ad909315a22120636baf03fa5e28701a50cb/src/cli/prompts/remoteConfigTrustPrompt.ts#L94-L130","documentation":"Repomix refuses to trust a config file from a cloned remote repository when the resolved config path is a symlink or not a regular file. Git preserves symlinks, so a remote repo can ship e.g. repomix.config.json as a symlink pointing outside the owner-only temp clone dir; the reviewed bytes could come from an unrelated local file and the target could be swapped between review and load. assertConfigIsContained lstat()s the path and throws this RepomixError before any trust decision, and this check runs even under --force and in non-interactive (CI) runs.","triggerScenarios":"Running `repomix --remote <url>` (with --remote-trust-config or REPOMIX_REMOTE_TRUST_CONFIG, or interactively) where the cloned repo contains a config file (repomix.config.ts/js/json5/etc.) that is a symbolic link, or a special file (FIFO, device, directory) rather than a regular file. Thrown by assertConfigIsContained via confirmRemoteConfigTrust before readFile/prompt.","commonSituations":"A malicious or misconfigured repo symlinks its config to /etc/passwd or a file outside the temp clone; a developer generates configs as symlinks to shared templates; a dotfile manager (e.g. GNU Stow, chezmoi) replaced the config with a symlink in the shipped repo.","solutions":["Replace the symlink with a regular file in the remote repo (git rm the link, copy the real file in, commit).","Verify what the path points at: `git ls-files -s` in the repo shows mode 120000 for symlinks; use `file repomix.config.*` locally after cloning.","If you own the repo, check your build/tooling for steps that create symlinks named repomix.config.* before publishing.","If you cannot change the repo, pass an explicit regular-file config with `--config /path/to/local.config.json` (explicit --config skips the remote config entirely)."],"exampleFix":"# before (in the remote repo)\nrepomix.config.json -> /shared/configs/repomix.json  (symlink, mode 120000)\n\n# after\ngit rm repomix.config.json\ncp /shared/configs/repomix.json repomix.config.json\ngit add repomix.config.json && git commit -m \"replace config symlink with regular file\"","handlingStrategy":"validation","validationCode":"import fs from 'node:fs/promises';\nconst stats = await fs.lstat(configPath);\nif (stats.isSymbolicLink() || !stats.isFile()) {\n  throw new Error(`Refusing to use ${configPath}: must be a regular file, not a symlink`);\n}","typeGuard":"const isRegularFile = (stats: fs.Stats): boolean => stats.isFile() && !stats.isSymbolicLink();","tryCatchPattern":"try {\n  await runRemotePack();\n} catch (e) {\n  if (e instanceof RepomixError && e.message.includes('must be a regular file, not a symlink')) {\n    console.error('Remote repo ships a symlinked config; replace it with a real file or use --config.');\n  } else throw e;\n}","preventionTips":["Never commit symlinks named repomix.config.* in published repos (`git ls-files -s | grep 120000`).","Audit repos with dotfile-manager symlinks (GNU Stow/chezmoi) before packing them remotely.","Prefer an explicit local --config when you don't control the remote repo.","Treat any symlink inside a cloned repo's config path as untrusted."],"tags":["security","symlink","remote-config","filesystem"],"backgroundTag":"symlinked-config-file","analyzedSha":"f465ad909315a22120636baf03fa5e28701a50cb","analyzedAt":"2026-08-29T01:27:42.024Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}