{"record":{"id":"ca79605b71c78183","repo":"alibaba/open-code-review","slug":"unsupported-extension-q-only-md-txt-markdown","errorCode":null,"errorMessage":"unsupported extension %q, only .md/.txt/.markdown allowed","messagePattern":"unsupported extension %q, only \\.md/\\.txt/\\.markdown allowed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/rules/system_rules.go","lineNumber":647,"sourceCode":"\t}\n\treturn nil\n}\n\n// readRuleFileSafe reads and validates a rule file: extension whitelist, 512 KB cap,\n// and symlink resolution. When confineRoot is non-empty, the resolved path must stay\n// inside it. Returns the trimmed content on success.\nfunc readRuleFileSafe(path string, confineRoot string) (string, error) {\n\tresolved, err := filepath.EvalSymlinks(path)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif confineRoot != \"\" && !pathutil.WithinBase(confineRoot, resolved) {\n\t\treturn \"\", fmt.Errorf(\"rule file path %q escapes repo dir %q\", resolved, confineRoot)\n\t}\n\n\tif !allowedRuleExts[strings.ToLower(filepath.Ext(resolved))] {\n\t\treturn \"\", fmt.Errorf(\"unsupported extension %q, only .md/.txt/.markdown allowed\", filepath.Ext(resolved))\n\t}\n\n\tconst maxSize = 512 * 1024\n\tinfo, err := os.Stat(resolved)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif info.Size() > maxSize {\n\t\treturn \"\", fmt.Errorf(\"file too large (%d bytes, max %d)\", info.Size(), maxSize)\n\t}\n\n\tcontent, err := os.ReadFile(resolved)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn strings.TrimRight(string(content), \"\\n\"), nil\n}","sourceCodeStart":629,"sourceCodeEnd":665,"githubUrl":"https://github.com/alibaba/open-code-review/blob/5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f/internal/config/rules/system_rules.go#L629-L665","documentation":"readRuleFileSafe only allows rule files with .md, .txt or .markdown extensions (case-insensitive). Any other extension is rejected to keep rule content predictable and prevent loading binaries or executable file types.","triggerScenarios":"tryReadRuleFile receives a rule path ending in e.g. .json, .yaml, .rst, or no extension at all; allowedRuleExts lookup on the lowercased filepath.Ext fails.","commonSituations":"Passing a .yml config as a rule; referencing a README with .adoc; a rule file saved without any extension; confusion with the .opencodereview/rule.json (that file is loaded separately and not subject to this extension gate).","solutions":["Rename the rule file to end in .md, .txt, or .markdown","Convert the content to Markdown/plain text if it is another format","Check the --rule argument points at the intended prose file, not a config"],"exampleFix":"// before\nocr review --rule rules/review.yaml\n// after\nmv rules/review.yaml rules/review.md   # convert content to markdown\nocr review --rule rules/review.md","handlingStrategy":"validation","validationCode":"func hasAllowedRuleExt(p string) bool {\n    switch strings.ToLower(filepath.Ext(p)) {\n    case \".md\", \".txt\", \".markdown\":\n        return true\n    }\n    return false\n}\n// check before invoking: if !hasAllowedRuleExt(flag) { convert/rename }","typeGuard":null,"tryCatchPattern":"_, err := tryReadRuleFile(rulePath, confineRoot)\nif err != nil && strings.Contains(err.Error(), \"unsupported extension\") {\n    fmt.Fprintf(os.Stderr, \"rename %s to .md, .txt or .markdown\\n\", rulePath)\n    os.Exit(2)\n}","preventionTips":["Standardize on .md for all review rule files","Convert YAML/AsciiDoc guidance into Markdown instead of passing it raw","Add a CI check that any file passed as --rule has an allowed extension"],"tags":["go","config","file-extension","validation"],"backgroundTag":"unsupported-file-extension","analyzedSha":"5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f","analyzedAt":"2026-09-02T02:08:09.116Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}