alibaba/open-code-review · error
rule file path %q escapes repo dir %q
Error message
rule file path %q escapes repo dir %q
What it means
readRuleFileSafe resolves a user-supplied rule file path via EvalSymlinks and enforces a confinement policy: if confineRoot is set and the resolved path is not inside it, the read is refused. This blocks a malicious or misconfigured rule reference from exfiltrating files outside the repository (rule content is sent to the LLM).
Source
Thrown at internal/config/rules/system_rules.go:643
if os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "[ocr] WARNING: rule file not found: %s\n", rule)
} else {
fmt.Fprintf(os.Stderr, "[ocr] WARNING: cannot read rule file %s: %v\n", resolved, err)
}
return nil
}
// readRuleFileSafe reads and validates a rule file: extension whitelist, 512 KB cap,
// and symlink resolution. When confineRoot is non-empty, the resolved path must stay
// inside it. Returns the trimmed content on success.
func readRuleFileSafe(path string, confineRoot string) (string, error) {
resolved, err := filepath.EvalSymlinks(path)
if err != nil {
return "", err
}
if confineRoot != "" && !pathutil.WithinBase(confineRoot, resolved) {
return "", fmt.Errorf("rule file path %q escapes repo dir %q", resolved, confineRoot)
}
if !allowedRuleExts[strings.ToLower(filepath.Ext(resolved))] {
return "", fmt.Errorf("unsupported extension %q, only .md/.txt/.markdown allowed", filepath.Ext(resolved))
}
const maxSize = 512 * 1024
info, err := os.Stat(resolved)
if err != nil {
return "", err
}
if info.Size() > maxSize {
return "", fmt.Errorf("file too large (%d bytes, max %d)", info.Size(), maxSize)
}
content, err := os.ReadFile(resolved)
if err != nil {
return "", errView on GitHub (pinned to 5cf97d0d15)
Solutions
- Place the rule file inside the repository and pass a repo-root-relative path
- Replace absolute-path symlinks with real files (or symlinks whose resolved target is inside the repo)
- Copy the shared rule file into this repo instead of referencing it across repos
- If intentionally external, use --repo to set confineRoot appropriately or keep the rule in the repo root
Example fix
// before ocr review --rule /home/me/shared-rules.md // after cp /home/me/shared-rules.md ./docs/review-rules.md ocr review --rule docs/review-rules.md
Defensive patterns
Strategy: validation
Validate before calling
import "path/filepath"
func ruleInsideRepo(repoDir, rulePath string) (bool, error) {
root, err := filepath.EvalSymlinks(repoDir)
if err != nil { return false, err }
resolved, err := filepath.EvalSymlinks(rulePath)
if err != nil { return false, err }
rel, err := filepath.Rel(root, resolved)
if err != nil { return false, err }
return rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)), nil
} Try / catch
_, err := tryReadRuleFile(rulePath, confineRoot)
if err != nil && strings.Contains(err.Error(), "escapes repo dir") {
fmt.Fprintf(os.Stderr, "move %s inside the repo, or drop the --rule flag\n", rulePath)
os.Exit(2)
} Prevention
- Keep rule files as regular in-repo files, not cross-repo symlinks
- Never pass absolute paths outside the repo to --rule
- Run ocr with the repo you intend as confineRoot, not a parent directory
- Audit rule files for symlink targets after cloning a foreign repo
When it happens
Trigger: tryReadRuleFile is called with a path whose symlink-resolved target lies outside the repo dir — e.g. --rule /etc/passwd, --rule ../secrets.md, or a rule file that is a symlink to outside the repo.
Common situations: Pointing --rule at a shared rules file in another repo or a home directory; a symlinked file that used to be in-repo but now targets an absolute path elsewhere; running with a repoDir that differs from where rule files live.
Related errors
- resolve project rule %s: %w
- file path %q is outside repository
- resolve file %q: %w
- [ocr] WARNING: skipping %s (%d bytes exceeds %d-byte scan li
- "%q %s" (+ positional signature, valid values, usage line, e
AI-assisted analysis of alibaba/open-code-review@5cf97d0d15 (2026-09-02).
Data as JSON: /api/errors/8d58165bb87ad8c3.
Report an issue: GitHub.