phy-path-traversal-audit Static scanner for OWASP A01:2021 — Broken Access Control / Path Traversal (CWE-22) and Local File Inclusion (CWE-98). Finds file system sinks that accept user-controlled paths, checks for missing containment guards, and flags PHP / patterns that allow template injection. Zero external API calls, zero dependencies beyond Python 3 stdlib. What Is Path Traversal? An attacker passes or as a filename parameter. Without validation, your code reads arbitrary files outside the intended base directory. With PHP , it can lead to Remote Code Execution. Classic exploit: If your…

),\n HIGH, \"CWE-98\",\n \"PHP include/require with variable — verify variable is not user-controlled.\",\n \"Use a whitelist of allowed template names; never construct include path from user input.\"),\n\n (\"PHP_READFILE_INPUT\",\n re.compile(r'\\b(?:readfile|highlight_file|show_source)\\s*\\(\\s*\\$(?:_GET|_POST|_REQUEST)\\b'),\n CRITICAL, \"CWE-22\",\n \"readfile/highlight_file with HTTP input — arbitrary file read/PHP source disclosure.\",\n \"Validate and sanitize filename; use basename() + allowlist; resolve + containment check.\"),\n\n (\"PHP_FOPEN_INPUT\",\n re.compile(r'\\bfopen\\s*\\(\\s*\\$(?:_GET|_POST|_REQUEST|_COOKIE)\\b'),\n CRITICAL, \"CWE-22\",\n \"fopen() with HTTP input — arbitrary file open.\",\n \"Validate path with realpath(); check containment: strpos(realpath, BASE_DIR) === 0.\"),\n\n (\"PHP_FILE_GET_CONTENTS\",\n re.compile(r'\\bfile(?:_get_contents|_put_contents|)\\s*\\(\\s*\\$(?:_GET|_POST|_REQUEST)\\b'),\n CRITICAL, \"CWE-22\",\n \"file_get_contents/file with HTTP input — arbitrary file read.\",\n \"Validate: $safe = realpath(BASE_DIR.'/'.basename($input)); if (!str_starts_with($safe, BASE_DIR)) { die(); }\"),\n ],\n \".js\": _build_js_path_patterns(),\n \".ts\": _build_js_path_patterns(),\n \".go\": [\n (\"OS_OPEN_FORM\",\n re.compile(r'\\bos\\.(?:Open|ReadFile|OpenFile|Create)\\s*\\(\\s*r\\.(?:FormValue|URL\\.Query\\(\\)\\.Get|Form\\.Get)\\s*\\('),\n CRITICAL, \"CWE-22\",\n \"os.Open/ReadFile with form value — direct path traversal.\",\n \"filepath.Clean(\\\"/\\\"+userInput) then filepath.Join(baseDir, cleaned); verify strings.HasPrefix(result, baseDir).\"),\n\n (\"FILEPATH_JOIN_NO_GUARD\",\n re.compile(r'\\bfilepath\\.Join\\s*\\('),\n HIGH, \"CWE-22\",\n \"filepath.Join with user input — Join alone doesn't prevent traversal.\",\n \"After join: safe := filepath.Clean(joined); if !strings.HasPrefix(safe, baseDir+sep) { error }\"),\n\n (\"HTTP_SERVE_FILE\",\n re.compile(r'\\bhttp\\.ServeFile\\s*\\('),\n HIGH, \"CWE-22\",\n \"http.ServeFile — Go's stdlib adds some protection but explicit validation is safer.\",\n \"Validate path against allowed directory before calling ServeFile.\"),\n ],\n \".rb\": [\n (\"FILE_OPEN_PARAMS\",\n re.compile(r'\\b(?:File\\.open|File\\.read|IO\\.read|IO\\.binread)\\s*\\(\\s*params\\['),\n CRITICAL, \"CWE-22\",\n \"File.open/read with params — arbitrary file read.\",\n \"Validate: path = Rails.root.join('safe_dir', File.basename(params[:file]))\\n\"\n \"raise unless path.to_s.start_with?(Rails.root.join('safe_dir').to_s)\"),\n\n (\"SEND_FILE_PARAMS\",\n re.compile(r'\\bsend_file\\s*\\(\\s*params\\['),\n CRITICAL, \"CWE-22\",\n \"Rails send_file with params — arbitrary file download.\",\n \"Whitelist allowed filenames; never construct path from user input.\"),\n\n (\"RENDER_PARAMS_TEMPLATE\",\n re.compile(r'\\brender\\s+params\\['),\n CRITICAL, \"CWE-98\",\n \"Rails render with params — template injection + path traversal.\",\n \"Whitelist allowed template names: ALLOWED_TEMPLATES = ['home', 'about']\\n\"\n \"render params[:page] if ALLOWED_TEMPLATES.include?(params[:page])\"),\n ],\n}\n\ndef _build_js_path_patterns():\n return [\n (\"FS_READFILE_PARAM\",\n re.compile(r'\\bfs\\.(?:readFile|readFileSync|createReadStream|readdirSync|stat|statSync)\\s*\\(\\s*req\\.[a-zA-Z.[\\]'\"]+'),\n CRITICAL, \"CWE-22\",\n \"fs.readFile/readFileSync with request param — direct path traversal.\",\n \"const safe = path.resolve(BASE_DIR, req.params.file);\\n\"\n \"if (!safe.startsWith(BASE_DIR + path.sep)) throw new Error('Forbidden');\\n\"\n \"fs.readFile(safe, ...)\"),\n\n (\"RES_SENDFILE_PARAM\",\n re.compile(r'\\bres\\.(?:sendFile|download)\\s*\\(\\s*(?:path\\.join\\s*\\([^)]+\\)|req\\.[a-zA-Z.[\\]'\"]+)'),\n HIGH, \"CWE-22\",\n \"res.sendFile/download with request param — path traversal.\",\n \"Validate path before sending. res.sendFile only after containment check.\"),\n\n (\"PATH_JOIN_NO_RESOLVE\",\n re.compile(r'\\bpath\\.join\\s*\\([^)]*(?:req\\.|__dirname)[^)]*\\)'),\n HIGH, \"CWE-22\",\n \"path.join() with request input — join preserves ../ components.\",\n \"After join: const safe = path.resolve(joined); assert safe.startsWith(BASE_DIR).\"),\n\n (\"REQUIRE_DYNAMIC\",\n re.compile(r'\\brequire\\s*\\(\\s*(?:path\\.join|req\\.[a-zA-Z.[\\]'\"]+)'),\n CRITICAL, \"CWE-22\",\n \"require() with user-controlled path — path traversal + arbitrary code execution.\",\n \"Never use require() with user input. Use a module whitelist.\"),\n\n (\"FS_WRITE_PARAM\",\n re.compile(r'\\bfs\\.(?:writeFile|writeFileSync|appendFile|appendFileSync)\\s*\\(\\s*req\\.[a-zA-Z.[\\]'\"]+'),\n CRITICAL, \"CWE-22\",\n \"fs.writeFile with request param — arbitrary file write (can overwrite config/keys).\",\n \"Validate destination path against BASE_DIR; never allow writing outside allowed dirs.\"),\n ]\n\nHTTP_MARKERS = {\n \".py\": re.compile(r'request\\.(args|form|json|data|files|GET|POST|params)|flask\\.request|starlette|fastapi|aiohttp\\.web'),\n \".java\": re.compile(r'HttpServletRequest|@RequestParam|@PathVariable|getParameter\\(|request\\.getParameter|@RequestBody'),\n \".php\": re.compile(r'\\$_(?:GET|POST|REQUEST|COOKIE|FILES)'),\n \".rb\": re.compile(r'\\bparams\\[|request\\.(body|params|query_string)'),\n \".js\": re.compile(r'\\breq\\.(body|params|query|headers)|ctx\\.(query|body|params)'),\n \".ts\": re.compile(r'\\breq\\.(body|params|query|headers)|ctx\\.(query|body|params)'),\n \".go\": re.compile(r'r\\.(FormValue\\(|URL\\.Query\\(\\)\\.Get|Form\\.Get|Body|Header\\.Get\\()'),\n}\n\nGUARD_PATTERNS = {\n \".py\": re.compile(r'os\\.path\\.abspath|\\.resolve\\(\\)|\\.is_relative_to|startswith\\s*\\(.*BASE|startswith\\s*\\(.*base_dir'),\n \".java\": re.compile(r'\\.normalize\\(\\)|\\.toAbsolutePath\\(\\)|startsWith\\s*\\('),\n \".php\": re.compile(r'\\brealpath\\s*\\(|strpos\\s*\\(|str_starts_with|basename\\s*\\('),\n \".rb\": re.compile(r'start_with\\?|File\\.basename|realpath'),\n \".js\": re.compile(r'path\\.resolve|\\.startsWith\\s*\\(|realpathSync|path\\.normalize'),\n \".ts\": re.compile(r'path\\.resolve|\\.startsWith\\s*\\(|realpathSync|path\\.normalize'),\n \".go\": re.compile(r'filepath\\.Clean|strings\\.HasPrefix|strings\\.Contains.*\"\\.\\.\"'),\n}\n\nSKIP_DIRS = {\".git\", \"node_modules\", \"vendor\", \"__pycache__\", \".venv\", \"venv\",\n \"dist\", \"build\", \"target\", \"test\", \"tests\", \"__tests__\", \"spec\", \"fixtures\"}\n\ndef scan_file(filepath: Path) -> list[Finding]:\n suffix = filepath.suffix.lower()\n if suffix not in PATTERNS:\n return []\n try:\n lines = filepath.read_text(encoding=\"utf-8\", errors=\"replace\").splitlines()\n except (OSError, PermissionError):\n return []\n\n full_text = \"\\n\".join(lines)\n findings: list[Finding] = []\n http_marker = HTTP_MARKERS.get(suffix)\n guard_pat = GUARD_PATTERNS.get(suffix)\n\n for (name, pat, base_sev, cwe, desc, fix) in PATTERNS[suffix]:\n for m in pat.finditer(full_text):\n lineno = full_text[:m.start()].count(\"\\n\") + 1\n line_text = lines[lineno - 1]\n\n start = max(0, lineno - 40)\n end = min(len(lines), lineno + 40)\n context = \"\\n\".join(lines[start:end])\n\n has_http = bool(http_marker and http_marker.search(context))\n guard_found = bool(guard_pat and guard_pat.search(context))\n\n if guard_found:\n continue # Safe pattern present — skip\n\n sev = base_sev\n if not has_http:\n if sev == CRITICAL:\n sev = HIGH\n elif sev == HIGH:\n sev = MEDIUM\n\n findings.append(Finding(\n file=str(filepath),\n line=lineno,\n pattern_name=name,\n matched_text=line_text.strip()[:120],\n severity=sev,\n cwe=cwe,\n description=desc,\n fix=fix,\n has_http_taint=has_http,\n ))\n return findings\n\ndef walk_files(root: Path) -> list[Path]:\n exts = {\".py\", \".java\", \".php\", \".rb\", \".js\", \".ts\", \".go\"}\n results = []\n for dirpath, dirnames, filenames in os.walk(root):\n dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]\n for fname in filenames:\n if Path(fname).suffix.lower() in exts:\n results.append(Path(dirpath) / fname)\n return results\n\ndef format_report(findings: list[Finding], scanned: int) -> str:\n by_sev = {CRITICAL: [], HIGH: [], MEDIUM: [], INFO: []}\n for f in findings:\n by_sev[f.severity].append(f)\n\n lines = [\n \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n \" PATH TRAVERSAL AUDIT (OWASP A01:2021 — CWE-22/98)\",\n \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n f\" Scanned: {scanned} files\",\n f\" Findings: {len(by_sev[CRITICAL])} CRITICAL {len(by_sev[HIGH])} HIGH {len(by_sev[MEDIUM])} MEDIUM\",\n \"\",\n ]\n\n for sev in [CRITICAL, HIGH, MEDIUM]:\n group = by_sev[sev]\n if not group:\n continue\n lines.append(f\"{ICONS[sev]} {sev} ({len(group)} findings)\")\n lines.append(\"\")\n for f in sorted(group, key=lambda x: x.file):\n rel = os.path.relpath(f.file)\n taint_str = \"⚡ HTTP taint confirmed\" if f.has_http_taint else \"⚠️ HTTP taint unconfirmed — verify source\"\n lines += [\n f\" {rel}:{f.line} — {f.pattern_name}\",\n f\" Code: {f.matched_text}\",\n f\" Taint: {taint_str}\",\n f\" Risk: {f.description}\",\n f\" {f.cwe}\",\n f\" Fix: {f.fix}\",\n \"\",\n ]\n\n critical_count = len(by_sev[CRITICAL])\n lines += [\n \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n f\" CI gate: {'exit 1 — CRITICAL findings present' if critical_count else 'exit 0 — clean'}\",\n \" OWASP: https://owasp.org/Top10/A01_2021-Broken_Access_Control/\",\n \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n ]\n return \"\\n\".join(lines)\n\ndef main():\n parser = argparse.ArgumentParser(description=\"Path traversal / LFI scanner\")\n parser.add_argument(\"path\", nargs=\"?\", default=\".\", help=\"Root directory to scan\")\n parser.add_argument(\"--json\", action=\"store_true\")\n parser.add_argument(\"--ci\", action=\"store_true\", help=\"Exit 1 if CRITICAL found\")\n args = parser.parse_args()\n\n files = walk_files(Path(args.path).resolve())\n all_findings: list[Finding] = []\n for f in files:\n all_findings.extend(scan_file(f))\n all_findings.sort(key=lambda x: (SEV_ORDER[x.severity], x.file, x.line))\n\n if args.json:\n import dataclasses\n print(json.dumps([dataclasses.asdict(f) for f in all_findings], indent=2))\n else:\n print(format_report(all_findings, len(files)))\n\n if args.ci:\n sys.exit(1 if any(f.severity == CRITICAL for f in all_findings) else 0)\n\nif __name__ == \"__main__\":\n main()\n```\n\n## Usage\n\n```bash\n# Scan current project\npython3 audit_path_traversal.py\n\n# Scan with CI fail-gate\npython3 audit_path_traversal.py --ci\n\n# JSON output\npython3 audit_path_traversal.py --json | jq '[.[] | select(.severity == \"CRITICAL\")]'\n\n# GitHub Actions\n- name: Path Traversal Audit\n run: python3 .claude/skills/phy-path-traversal-audit/audit_path_traversal.py --ci\n```\n\n## Sample Output\n\n```\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n PATH TRAVERSAL AUDIT (OWASP A01:2021 — CWE-22/98)\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n Scanned: 54 files\n Findings: 3 CRITICAL 2 HIGH 0 MEDIUM\n\n🔴 CRITICAL (3 findings)\n\n api/files.py:67 — OPEN_DIRECT\n Code: return open(os.path.join(\"uploads\", request.args[\"path\"])).read()\n Taint: ⚡ HTTP taint confirmed\n Risk: open() with user-controlled path enables arbitrary file read/write.\n CWE-22\n Fix: safe = os.path.abspath(os.path.join(\"uploads\", user_input))\n assert safe.startswith(os.path.abspath(\"uploads\"))\n\n pages/api.php:34 — PHP_INCLUDE_INPUT\n Code: include($_GET['page'] . '.php');\n Taint: ⚡ HTTP taint confirmed\n Risk: PHP include with HTTP input — LFI → potential RCE via log poisoning.\n CWE-98\n Fix: Use whitelist: $allowed = ['home', 'about'];\n if (!in_array($_GET['page'], $allowed)) die('invalid');\n\n src/routes/download.js:91 — FS_READFILE_PARAM\n Code: fs.createReadStream(req.query.file)\n Taint: ⚡ HTTP taint confirmed\n Risk: fs.createReadStream with request param — direct path traversal.\n CWE-22\n Fix: const safe = path.resolve(BASE_DIR, req.query.file);\n if (!safe.startsWith(BASE_DIR + path.sep)) throw new Error('Forbidden');\n fs.createReadStream(safe)\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n CI gate: exit 1 — CRITICAL findings present\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n```\n\n## Companion Skills\n\n| Skill | Use Together For |\n|-------|-----------------|\n| `phy-ssrf-audit` | Complete input → file/URL access security sweep |\n| `phy-deserialization-audit` | OWASP A08 + A01 — full untrusted input chain |\n| `phy-cors-audit` | Network boundary + filesystem boundary protection |\n| `phy-jwt-auth-audit` | Auth controls that should gate file access |\n---","attachment_filenames":["_meta.json"],"attachments":[{"filename":"_meta.json","content":"{\n \"owner\": \"phy041\",\n \"slug\": \"phy-path-traversal-audit\",\n \"displayName\": \"Phy Path Traversal Audit\",\n \"latest\": {\n \"version\": \"1.0.0\",\n \"publishedAt\": 1773947665754,\n \"commit\": \"https://github.com/openclaw/skills/commit/085e75d942d082f0c31550bd262d3712988e6056\"\n },\n \"history\": []\n}\n","content_type":"application/json; charset=utf-8","language":"json","size":300,"content_sha256":"e24c4aa57b1f729365e0ca95b3c3a8ae564c6c57348106be9dfd0cf7be738ca1"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"phy-path-traversal-audit","type":"text"}]},{"type":"paragraph","content":[{"text":"Static scanner for ","type":"text"},{"text":"OWASP A01:2021 — Broken Access Control / Path Traversal","type":"text","marks":[{"type":"strong"}]},{"text":" (CWE-22) and ","type":"text"},{"text":"Local File Inclusion","type":"text","marks":[{"type":"strong"}]},{"text":" (CWE-98). Finds file system sinks that accept user-controlled paths, checks for missing containment guards, and flags PHP ","type":"text"},{"text":"include","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"require","type":"text","marks":[{"type":"code_inline"}]},{"text":" patterns that allow template injection. Zero external API calls, zero dependencies beyond Python 3 stdlib.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"What Is Path Traversal?","type":"text"}]},{"type":"paragraph","content":[{"text":"An attacker passes ","type":"text"},{"text":"../../etc/passwd","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"..%2F..%2Fetc%2Fshadow","type":"text","marks":[{"type":"code_inline"}]},{"text":" as a filename parameter. Without validation, your code reads arbitrary files outside the intended base directory. With PHP ","type":"text"},{"text":"include","type":"text","marks":[{"type":"code_inline"}]},{"text":", it can lead to Remote Code Execution.","type":"text"}]},{"type":"paragraph","content":[{"text":"Classic exploit:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"GET /api/files?path=../../etc/passwd HTTP/1.1","type":"text"}]},{"type":"paragraph","content":[{"text":"If your handler does ","type":"text"},{"text":"open(\"uploads/\" + request.args[\"path\"])","type":"text","marks":[{"type":"code_inline"}]},{"text":", attacker reads ","type":"text"},{"text":"/etc/passwd","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"What It Detects","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Python","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pattern","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Severity","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Notes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"open(user_path)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Direct file read with user path","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"open(os.path.join(base, user_input))","type":"text","marks":[{"type":"code_inline"}]},{"text":" without ","type":"text"},{"text":"abspath","type":"text","marks":[{"type":"code_inline"}]},{"text":"+","type":"text"},{"text":"startswith","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Join doesn't sanitize ","type":"text"},{"text":"../","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pathlib.Path(user_path).read_text()","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pathlib doesn't sanitize traversal","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Path(base).joinpath(user_input)","type":"text","marks":[{"type":"code_inline"}]},{"text":" without ","type":"text"},{"text":".resolve().is_relative_to(base)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph"}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"os.listdir(user_path)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Directory listing disclosure","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"os.open(user_path, os.O_RDONLY)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Low-level file open","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"shutil.copy/move(user_src, ...)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File operation with user src","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"tarfile.open(user_path)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Zip/tar slip (CVE-class)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"zipfile.ZipFile(user_path)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Zip slip attack","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Java","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pattern","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Severity","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Notes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"new File(baseDir + userInput)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"String concat without normalization","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"new FileInputStream(userInput)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Direct file read","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Paths.get(userInput)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Path construction without validation","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Files.readAllBytes(Path.of(userInput))","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File read","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Files.newBufferedReader(Paths.get(userInput))","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File read","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"new File(request.getServletContext().getRealPath(userInput))","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Servlet path traversal","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"response.setHeader(\"Content-Disposition\", \"...\"+userInput)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MEDIUM","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Filename injection","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"PHP","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pattern","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Severity","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Notes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"include($_GET['page'])","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"include($_POST['file'])","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LFI → RCE via log poisoning","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"require($_GET['file'])","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LFI","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"include_once($_GET[...])","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"require_once($_GET[...])","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LFI","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"readfile($_GET['file'])","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File disclosure","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"file_get_contents($_GET['path'])","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File/URL read","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"fopen($_GET['file'], 'r')","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File open","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"file($_GET['path'])","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Read file into array","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"highlight_file($_GET['file'])","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PHP source disclosure","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"include(\"pages/\" . $_GET['page'] . \".php\")","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Partial mitigation (extension added) but still exploitable via null byte on older PHP","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Node.js / TypeScript","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pattern","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Severity","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Notes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"fs.readFile(req.params.path, ...)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Direct file read","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"fs.readFileSync(req.query.file)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sync file read","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"fs.createReadStream(req.body.path)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stream file read","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"res.sendFile(req.params.filename)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Express static file serve","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"res.download(req.query.file)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File download","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"path.join(__dirname, req.params.file)","type":"text","marks":[{"type":"code_inline"}]},{"text":" without ","type":"text"},{"text":"path.resolve","type":"text","marks":[{"type":"code_inline"}]},{"text":"+","type":"text"},{"text":"startsWith","type":"text","marks":[{"type":"code_inline"}]},{"text":" check","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Join alone is insufficient","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"require(req.params.module)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Path traversal + arbitrary code execution","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"fs.readdirSync(req.query.dir)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Directory listing","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Go","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pattern","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Severity","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Notes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"os.Open(r.FormValue(\"path\"))","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Direct file open","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"os.ReadFile(r.URL.Query().Get(\"file\"))","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File read","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"http.ServeFile(w, r, r.FormValue(\"path\"))","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File serve — ","type":"text"},{"text":"http.ServeFile","type":"text","marks":[{"type":"code_inline"}]},{"text":" has some built-in protection but verify","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"filepath.Join(base, r.FormValue(\"name\"))","type":"text","marks":[{"type":"code_inline"}]},{"text":" without ","type":"text"},{"text":"filepath.Clean","type":"text","marks":[{"type":"code_inline"}]},{"text":"+containment","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HIGH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph"}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"os.Stat(r.FormValue(\"path\"))","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MEDIUM","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Path existence disclosure","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Ruby","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pattern","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Severity","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Notes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File.open(params[:path])","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File open","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File.read(params[:file])","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File read","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"IO.read(params[:file])","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File read","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"send_file(params[:path])","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rails file serve","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"send_data(File.read(params[:file]))","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File read + serve","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"render params[:template]","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Template injection (+ path traversal)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"erb.result(binding) where erb from params","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CRITICAL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Template injection","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Containment Guard Detection","type":"text"}]},{"type":"paragraph","content":[{"text":"After finding a sink, the scanner checks if a safe-path guard exists within ±40 lines. If found, the finding is downgraded or suppressed:","type":"text"}]},{"type":"paragraph","content":[{"text":"Python safe guards:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"# Correct: resolve to absolute, then check containment\nsafe_path = os.path.abspath(os.path.join(BASE_DIR, user_input))\nif not safe_path.startswith(BASE_DIR):\n raise PermissionError(\"path traversal detected\")\n\n# Also safe: pathlib.resolve() + is_relative_to()\nresolved = (Path(BASE_DIR) / user_input).resolve()\nif not resolved.is_relative_to(BASE_DIR):\n raise ValueError(\"path traversal\")","type":"text"}]},{"type":"paragraph","content":[{"text":"Java safe guards:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"java"},"content":[{"text":"// Correct: normalize and verify containment\nPath safePath = Paths.get(baseDir).resolve(userInput).normalize().toAbsolutePath();\nif (!safePath.startsWith(Paths.get(baseDir).toAbsolutePath())) {\n throw new SecurityException(\"Path traversal detected\");\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Node.js safe guards:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// Correct: resolve and check startsWith\nconst safePath = path.resolve(BASE_DIR, userInput);\nif (!safePath.startsWith(BASE_DIR + path.sep)) {\n throw new Error(\"Path traversal detected\");\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"PHP safe guards:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"php"},"content":[{"text":"// Correct: realpath + containment check\n$path = realpath(BASE_DIR . '/' . $userInput);\nif ($path === false || strpos($path, BASE_DIR) !== 0) {\n http_response_code(403);\n exit('Forbidden');\n}\n// Also: basename() strips directory components (partial mitigation)\n$filename = basename($_GET['file']); // removes ../","type":"text"}]},{"type":"paragraph","content":[{"text":"Go safe guards:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"go"},"content":[{"text":"// Correct: Clean + containment check\nsafePath := filepath.Join(baseDir, filepath.Clean(\"/\"+r.FormValue(\"path\")))\nif !strings.HasPrefix(safePath, baseDir+string(os.PathSeparator)) {\n http.Error(w, \"Forbidden\", 403)\n return\n}","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Zip Slip Detection","type":"text"}]},{"type":"paragraph","content":[{"text":"Tar/zip extraction without path validation enables a special case of path traversal where malicious archives overwrite files outside the extraction directory:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"# DANGEROUS — zip slip\nwith zipfile.ZipFile(archive) as zf:\n zf.extractall(extract_dir) # member names not validated\n\n# SAFE\nimport zipfile, os\nwith zipfile.ZipFile(archive) as zf:\n for member in zf.infolist():\n member_path = os.path.abspath(os.path.join(extract_dir, member.filename))\n if not member_path.startswith(os.path.abspath(extract_dir) + os.sep):\n raise ValueError(f\"Zip slip: {member.filename}\")\n zf.extract(member, extract_dir)","type":"text"}]},{"type":"paragraph","content":[{"text":"The scanner detects ","type":"text"},{"text":"zipfile.ZipFile.extractall()","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"tarfile.TarFile.extractall()","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ZipInputStream","type":"text","marks":[{"type":"code_inline"}]},{"text":" in Java, ","type":"text"},{"text":"net/http","type":"text","marks":[{"type":"code_inline"}]},{"text":" archiver patterns in Go.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Implementation","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"#!/usr/bin/env python3\n\"\"\"\nphy-path-traversal-audit — OWASP A01:2021 path traversal scanner\nUsage: python3 audit_path_traversal.py [path] [--json] [--ci]\n\"\"\"\nimport argparse\nimport json\nimport os\nimport re\nimport sys\nfrom dataclasses import dataclass\nfrom pathlib import Path\nfrom typing import Optional\n\nCRITICAL, HIGH, MEDIUM, INFO = \"CRITICAL\", \"HIGH\", \"MEDIUM\", \"INFO\"\nSEV_ORDER = {CRITICAL: 0, HIGH: 1, MEDIUM: 2, INFO: 3}\nICONS = {CRITICAL: \"🔴\", HIGH: \"🟠\", MEDIUM: \"🟡\", INFO: \"⚪\"}\n\n@dataclass\nclass Finding:\n file: str\n line: int\n pattern_name: str\n matched_text: str\n severity: str\n cwe: str\n description: str\n fix: str\n has_http_taint: bool = False\n\nPATTERNS = {\n \".py\": [\n (\"OPEN_DIRECT\",\n re.compile(r'\\bopen\\s*\\('),\n CRITICAL, \"CWE-22\",\n \"open() with user-controlled path enables arbitrary file read/write.\",\n \"Resolve to absolute path, then verify it starts with the allowed base directory.\"),\n\n (\"PATHLIB_READ\",\n re.compile(r'\\bPath\\s*\\([^)]+\\)\\.(?:read_text|read_bytes|open)\\s*\\('),\n CRITICAL, \"CWE-22\",\n \"pathlib.Path().read_text/bytes with user path — Path() does not sanitize ../.\",\n \"Use (Path(BASE_DIR) / user_input).resolve().is_relative_to(BASE_DIR) before reading.\"),\n\n (\"OS_PATH_JOIN_NO_ABSPATH\",\n re.compile(r'\\bos\\.path\\.join\\s*\\([^)]+\\)'),\n HIGH, \"CWE-22\",\n \"os.path.join() alone doesn't prevent traversal — '../' components are preserved.\",\n \"After join: safe = os.path.abspath(joined); assert safe.startswith(BASE_DIR).\"),\n\n (\"OS_LISTDIR\",\n re.compile(r'\\bos\\.listdir\\s*\\('),\n HIGH, \"CWE-22\",\n \"os.listdir() with user path exposes directory contents.\",\n \"Validate path against BASE_DIR allowlist before listing.\"),\n\n (\"ZIP_EXTRACTALL\",\n re.compile(r'\\bextractall\\s*\\('),\n HIGH, \"CWE-22\",\n \"extractall() without member path validation enables zip-slip attack.\",\n \"Validate each member path before extraction (see zip-slip safe pattern).\"),\n\n (\"TARFILE_EXTRACT\",\n re.compile(r'\\btarfile\\.open\\s*\\(|\\b\\.extract\\s*\\('),\n HIGH, \"CWE-22\",\n \"tarfile extract without member path validation enables tar-slip.\",\n \"Use tarfile.data_filter (Python 3.12+) or manually check member.name.\"),\n ],\n \".java\": [\n (\"NEW_FILE_CONCAT\",\n re.compile(r'\\bnew\\s+File\\s*\\([^)]+\\+\\s*\\w+\\s*\\)'),\n CRITICAL, \"CWE-22\",\n \"new File(base + userInput) — string concatenation without path normalization.\",\n \"Use Paths.get(base).resolve(userInput).normalize() then check startsWith(base).\"),\n\n (\"NEW_FILE_INPUT_STREAM\",\n re.compile(r'\\bnew\\s+FileInputStream\\s*\\('),\n CRITICAL, \"CWE-22\",\n \"FileInputStream with user-controlled path enables arbitrary file read.\",\n \"Normalize and validate path before constructing FileInputStream.\"),\n\n (\"FILES_READ\",\n re.compile(r'\\bFiles\\.(readAllBytes|readString|newBufferedReader|newInputStream)\\s*\\('),\n CRITICAL, \"CWE-22\",\n \"Files.readAllBytes/readString with user path enables path traversal.\",\n \"Validate path: Path.of(base).resolve(userInput).normalize().toAbsolutePath() starts with base.\"),\n\n (\"PATHS_GET\",\n re.compile(r'\\bPaths\\.get\\s*\\('),\n HIGH, \"CWE-22\",\n \"Paths.get() with user input — check for subsequent normalize()+startsWith() guard.\",\n \"Always follow with .normalize().toAbsolutePath() and containment check.\"),\n\n (\"SERVLET_REAL_PATH\",\n re.compile(r'\\.getRealPath\\s*\\('),\n CRITICAL, \"CWE-22\",\n \"getRealPath() with user input translates to filesystem path — path traversal.\",\n \"Validate user input before passing to getRealPath(); prefer serving from classpath.\"),\n ],\n \".php\": [\n (\"PHP_INCLUDE_INPUT\",\n re.compile(r'\\b(?:include|require|include_once|require_once)\\s*\\(\\s*\\$(?:_GET|_POST|_REQUEST|_COOKIE)\\b'),\n CRITICAL, \"CWE-98\",\n \"PHP include/require with HTTP input — Local File Inclusion → potential RCE via log poisoning.\",\n \"NEVER use include/require with user input. Use a whitelist: $allowed = ['home', 'about']; \"\n \"if (!in_array($page, $allowed)) die('invalid');\"),\n\n (\"PHP_INCLUDE_VAR\",\n re.compile(r'\\b(?:include|require|include_once|require_once)\\s*\\([^)]*\\

phy-path-traversal-audit Static scanner for OWASP A01:2021 — Broken Access Control / Path Traversal (CWE-22) and Local File Inclusion (CWE-98). Finds file system sinks that accept user-controlled paths, checks for missing containment guards, and flags PHP / patterns that allow template injection. Zero external API calls, zero dependencies beyond Python 3 stdlib. What Is Path Traversal? An attacker passes or as a filename parameter. Without validation, your code reads arbitrary files outside the intended base directory. With PHP , it can lead to Remote Code Execution. Classic exploit: If your…

),\n HIGH, \"CWE-98\",\n \"PHP include/require with variable — verify variable is not user-controlled.\",\n \"Use a whitelist of allowed template names; never construct include path from user input.\"),\n\n (\"PHP_READFILE_INPUT\",\n re.compile(r'\\b(?:readfile|highlight_file|show_source)\\s*\\(\\s*\\$(?:_GET|_POST|_REQUEST)\\b'),\n CRITICAL, \"CWE-22\",\n \"readfile/highlight_file with HTTP input — arbitrary file read/PHP source disclosure.\",\n \"Validate and sanitize filename; use basename() + allowlist; resolve + containment check.\"),\n\n (\"PHP_FOPEN_INPUT\",\n re.compile(r'\\bfopen\\s*\\(\\s*\\$(?:_GET|_POST|_REQUEST|_COOKIE)\\b'),\n CRITICAL, \"CWE-22\",\n \"fopen() with HTTP input — arbitrary file open.\",\n \"Validate path with realpath(); check containment: strpos(realpath, BASE_DIR) === 0.\"),\n\n (\"PHP_FILE_GET_CONTENTS\",\n re.compile(r'\\bfile(?:_get_contents|_put_contents|)\\s*\\(\\s*\\$(?:_GET|_POST|_REQUEST)\\b'),\n CRITICAL, \"CWE-22\",\n \"file_get_contents/file with HTTP input — arbitrary file read.\",\n \"Validate: $safe = realpath(BASE_DIR.'/'.basename($input)); if (!str_starts_with($safe, BASE_DIR)) { die(); }\"),\n ],\n \".js\": _build_js_path_patterns(),\n \".ts\": _build_js_path_patterns(),\n \".go\": [\n (\"OS_OPEN_FORM\",\n re.compile(r'\\bos\\.(?:Open|ReadFile|OpenFile|Create)\\s*\\(\\s*r\\.(?:FormValue|URL\\.Query\\(\\)\\.Get|Form\\.Get)\\s*\\('),\n CRITICAL, \"CWE-22\",\n \"os.Open/ReadFile with form value — direct path traversal.\",\n \"filepath.Clean(\\\"/\\\"+userInput) then filepath.Join(baseDir, cleaned); verify strings.HasPrefix(result, baseDir).\"),\n\n (\"FILEPATH_JOIN_NO_GUARD\",\n re.compile(r'\\bfilepath\\.Join\\s*\\('),\n HIGH, \"CWE-22\",\n \"filepath.Join with user input — Join alone doesn't prevent traversal.\",\n \"After join: safe := filepath.Clean(joined); if !strings.HasPrefix(safe, baseDir+sep) { error }\"),\n\n (\"HTTP_SERVE_FILE\",\n re.compile(r'\\bhttp\\.ServeFile\\s*\\('),\n HIGH, \"CWE-22\",\n \"http.ServeFile — Go's stdlib adds some protection but explicit validation is safer.\",\n \"Validate path against allowed directory before calling ServeFile.\"),\n ],\n \".rb\": [\n (\"FILE_OPEN_PARAMS\",\n re.compile(r'\\b(?:File\\.open|File\\.read|IO\\.read|IO\\.binread)\\s*\\(\\s*params\\['),\n CRITICAL, \"CWE-22\",\n \"File.open/read with params — arbitrary file read.\",\n \"Validate: path = Rails.root.join('safe_dir', File.basename(params[:file]))\\n\"\n \"raise unless path.to_s.start_with?(Rails.root.join('safe_dir').to_s)\"),\n\n (\"SEND_FILE_PARAMS\",\n re.compile(r'\\bsend_file\\s*\\(\\s*params\\['),\n CRITICAL, \"CWE-22\",\n \"Rails send_file with params — arbitrary file download.\",\n \"Whitelist allowed filenames; never construct path from user input.\"),\n\n (\"RENDER_PARAMS_TEMPLATE\",\n re.compile(r'\\brender\\s+params\\['),\n CRITICAL, \"CWE-98\",\n \"Rails render with params — template injection + path traversal.\",\n \"Whitelist allowed template names: ALLOWED_TEMPLATES = ['home', 'about']\\n\"\n \"render params[:page] if ALLOWED_TEMPLATES.include?(params[:page])\"),\n ],\n}\n\ndef _build_js_path_patterns():\n return [\n (\"FS_READFILE_PARAM\",\n re.compile(r'\\bfs\\.(?:readFile|readFileSync|createReadStream|readdirSync|stat|statSync)\\s*\\(\\s*req\\.[a-zA-Z.[\\]'\"]+'),\n CRITICAL, \"CWE-22\",\n \"fs.readFile/readFileSync with request param — direct path traversal.\",\n \"const safe = path.resolve(BASE_DIR, req.params.file);\\n\"\n \"if (!safe.startsWith(BASE_DIR + path.sep)) throw new Error('Forbidden');\\n\"\n \"fs.readFile(safe, ...)\"),\n\n (\"RES_SENDFILE_PARAM\",\n re.compile(r'\\bres\\.(?:sendFile|download)\\s*\\(\\s*(?:path\\.join\\s*\\([^)]+\\)|req\\.[a-zA-Z.[\\]'\"]+)'),\n HIGH, \"CWE-22\",\n \"res.sendFile/download with request param — path traversal.\",\n \"Validate path before sending. res.sendFile only after containment check.\"),\n\n (\"PATH_JOIN_NO_RESOLVE\",\n re.compile(r'\\bpath\\.join\\s*\\([^)]*(?:req\\.|__dirname)[^)]*\\)'),\n HIGH, \"CWE-22\",\n \"path.join() with request input — join preserves ../ components.\",\n \"After join: const safe = path.resolve(joined); assert safe.startsWith(BASE_DIR).\"),\n\n (\"REQUIRE_DYNAMIC\",\n re.compile(r'\\brequire\\s*\\(\\s*(?:path\\.join|req\\.[a-zA-Z.[\\]'\"]+)'),\n CRITICAL, \"CWE-22\",\n \"require() with user-controlled path — path traversal + arbitrary code execution.\",\n \"Never use require() with user input. Use a module whitelist.\"),\n\n (\"FS_WRITE_PARAM\",\n re.compile(r'\\bfs\\.(?:writeFile|writeFileSync|appendFile|appendFileSync)\\s*\\(\\s*req\\.[a-zA-Z.[\\]'\"]+'),\n CRITICAL, \"CWE-22\",\n \"fs.writeFile with request param — arbitrary file write (can overwrite config/keys).\",\n \"Validate destination path against BASE_DIR; never allow writing outside allowed dirs.\"),\n ]\n\nHTTP_MARKERS = {\n \".py\": re.compile(r'request\\.(args|form|json|data|files|GET|POST|params)|flask\\.request|starlette|fastapi|aiohttp\\.web'),\n \".java\": re.compile(r'HttpServletRequest|@RequestParam|@PathVariable|getParameter\\(|request\\.getParameter|@RequestBody'),\n \".php\": re.compile(r'\\$_(?:GET|POST|REQUEST|COOKIE|FILES)'),\n \".rb\": re.compile(r'\\bparams\\[|request\\.(body|params|query_string)'),\n \".js\": re.compile(r'\\breq\\.(body|params|query|headers)|ctx\\.(query|body|params)'),\n \".ts\": re.compile(r'\\breq\\.(body|params|query|headers)|ctx\\.(query|body|params)'),\n \".go\": re.compile(r'r\\.(FormValue\\(|URL\\.Query\\(\\)\\.Get|Form\\.Get|Body|Header\\.Get\\()'),\n}\n\nGUARD_PATTERNS = {\n \".py\": re.compile(r'os\\.path\\.abspath|\\.resolve\\(\\)|\\.is_relative_to|startswith\\s*\\(.*BASE|startswith\\s*\\(.*base_dir'),\n \".java\": re.compile(r'\\.normalize\\(\\)|\\.toAbsolutePath\\(\\)|startsWith\\s*\\('),\n \".php\": re.compile(r'\\brealpath\\s*\\(|strpos\\s*\\(|str_starts_with|basename\\s*\\('),\n \".rb\": re.compile(r'start_with\\?|File\\.basename|realpath'),\n \".js\": re.compile(r'path\\.resolve|\\.startsWith\\s*\\(|realpathSync|path\\.normalize'),\n \".ts\": re.compile(r'path\\.resolve|\\.startsWith\\s*\\(|realpathSync|path\\.normalize'),\n \".go\": re.compile(r'filepath\\.Clean|strings\\.HasPrefix|strings\\.Contains.*\"\\.\\.\"'),\n}\n\nSKIP_DIRS = {\".git\", \"node_modules\", \"vendor\", \"__pycache__\", \".venv\", \"venv\",\n \"dist\", \"build\", \"target\", \"test\", \"tests\", \"__tests__\", \"spec\", \"fixtures\"}\n\ndef scan_file(filepath: Path) -> list[Finding]:\n suffix = filepath.suffix.lower()\n if suffix not in PATTERNS:\n return []\n try:\n lines = filepath.read_text(encoding=\"utf-8\", errors=\"replace\").splitlines()\n except (OSError, PermissionError):\n return []\n\n full_text = \"\\n\".join(lines)\n findings: list[Finding] = []\n http_marker = HTTP_MARKERS.get(suffix)\n guard_pat = GUARD_PATTERNS.get(suffix)\n\n for (name, pat, base_sev, cwe, desc, fix) in PATTERNS[suffix]:\n for m in pat.finditer(full_text):\n lineno = full_text[:m.start()].count(\"\\n\") + 1\n line_text = lines[lineno - 1]\n\n start = max(0, lineno - 40)\n end = min(len(lines), lineno + 40)\n context = \"\\n\".join(lines[start:end])\n\n has_http = bool(http_marker and http_marker.search(context))\n guard_found = bool(guard_pat and guard_pat.search(context))\n\n if guard_found:\n continue # Safe pattern present — skip\n\n sev = base_sev\n if not has_http:\n if sev == CRITICAL:\n sev = HIGH\n elif sev == HIGH:\n sev = MEDIUM\n\n findings.append(Finding(\n file=str(filepath),\n line=lineno,\n pattern_name=name,\n matched_text=line_text.strip()[:120],\n severity=sev,\n cwe=cwe,\n description=desc,\n fix=fix,\n has_http_taint=has_http,\n ))\n return findings\n\ndef walk_files(root: Path) -> list[Path]:\n exts = {\".py\", \".java\", \".php\", \".rb\", \".js\", \".ts\", \".go\"}\n results = []\n for dirpath, dirnames, filenames in os.walk(root):\n dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]\n for fname in filenames:\n if Path(fname).suffix.lower() in exts:\n results.append(Path(dirpath) / fname)\n return results\n\ndef format_report(findings: list[Finding], scanned: int) -> str:\n by_sev = {CRITICAL: [], HIGH: [], MEDIUM: [], INFO: []}\n for f in findings:\n by_sev[f.severity].append(f)\n\n lines = [\n \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n \" PATH TRAVERSAL AUDIT (OWASP A01:2021 — CWE-22/98)\",\n \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n f\" Scanned: {scanned} files\",\n f\" Findings: {len(by_sev[CRITICAL])} CRITICAL {len(by_sev[HIGH])} HIGH {len(by_sev[MEDIUM])} MEDIUM\",\n \"\",\n ]\n\n for sev in [CRITICAL, HIGH, MEDIUM]:\n group = by_sev[sev]\n if not group:\n continue\n lines.append(f\"{ICONS[sev]} {sev} ({len(group)} findings)\")\n lines.append(\"\")\n for f in sorted(group, key=lambda x: x.file):\n rel = os.path.relpath(f.file)\n taint_str = \"⚡ HTTP taint confirmed\" if f.has_http_taint else \"⚠️ HTTP taint unconfirmed — verify source\"\n lines += [\n f\" {rel}:{f.line} — {f.pattern_name}\",\n f\" Code: {f.matched_text}\",\n f\" Taint: {taint_str}\",\n f\" Risk: {f.description}\",\n f\" {f.cwe}\",\n f\" Fix: {f.fix}\",\n \"\",\n ]\n\n critical_count = len(by_sev[CRITICAL])\n lines += [\n \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n f\" CI gate: {'exit 1 — CRITICAL findings present' if critical_count else 'exit 0 — clean'}\",\n \" OWASP: https://owasp.org/Top10/A01_2021-Broken_Access_Control/\",\n \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n ]\n return \"\\n\".join(lines)\n\ndef main():\n parser = argparse.ArgumentParser(description=\"Path traversal / LFI scanner\")\n parser.add_argument(\"path\", nargs=\"?\", default=\".\", help=\"Root directory to scan\")\n parser.add_argument(\"--json\", action=\"store_true\")\n parser.add_argument(\"--ci\", action=\"store_true\", help=\"Exit 1 if CRITICAL found\")\n args = parser.parse_args()\n\n files = walk_files(Path(args.path).resolve())\n all_findings: list[Finding] = []\n for f in files:\n all_findings.extend(scan_file(f))\n all_findings.sort(key=lambda x: (SEV_ORDER[x.severity], x.file, x.line))\n\n if args.json:\n import dataclasses\n print(json.dumps([dataclasses.asdict(f) for f in all_findings], indent=2))\n else:\n print(format_report(all_findings, len(files)))\n\n if args.ci:\n sys.exit(1 if any(f.severity == CRITICAL for f in all_findings) else 0)\n\nif __name__ == \"__main__\":\n main()","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Usage","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Scan current project\npython3 audit_path_traversal.py\n\n# Scan with CI fail-gate\npython3 audit_path_traversal.py --ci\n\n# JSON output\npython3 audit_path_traversal.py --json | jq '[.[] | select(.severity == \"CRITICAL\")]'\n\n# GitHub Actions\n- name: Path Traversal Audit\n run: python3 .claude/skills/phy-path-traversal-audit/audit_path_traversal.py --ci","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Sample Output","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n PATH TRAVERSAL AUDIT (OWASP A01:2021 — CWE-22/98)\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n Scanned: 54 files\n Findings: 3 CRITICAL 2 HIGH 0 MEDIUM\n\n🔴 CRITICAL (3 findings)\n\n api/files.py:67 — OPEN_DIRECT\n Code: return open(os.path.join(\"uploads\", request.args[\"path\"])).read()\n Taint: ⚡ HTTP taint confirmed\n Risk: open() with user-controlled path enables arbitrary file read/write.\n CWE-22\n Fix: safe = os.path.abspath(os.path.join(\"uploads\", user_input))\n assert safe.startswith(os.path.abspath(\"uploads\"))\n\n pages/api.php:34 — PHP_INCLUDE_INPUT\n Code: include($_GET['page'] . '.php');\n Taint: ⚡ HTTP taint confirmed\n Risk: PHP include with HTTP input — LFI → potential RCE via log poisoning.\n CWE-98\n Fix: Use whitelist: $allowed = ['home', 'about'];\n if (!in_array($_GET['page'], $allowed)) die('invalid');\n\n src/routes/download.js:91 — FS_READFILE_PARAM\n Code: fs.createReadStream(req.query.file)\n Taint: ⚡ HTTP taint confirmed\n Risk: fs.createReadStream with request param — direct path traversal.\n CWE-22\n Fix: const safe = path.resolve(BASE_DIR, req.query.file);\n if (!safe.startsWith(BASE_DIR + path.sep)) throw new Error('Forbidden');\n fs.createReadStream(safe)\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n CI gate: exit 1 — CRITICAL findings present\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Companion Skills","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Skill","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use Together For","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"phy-ssrf-audit","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Complete input → file/URL access security sweep","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"phy-deserialization-audit","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OWASP A08 + A01 — full untrusted input chain","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"phy-cors-audit","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Network boundary + filesystem boundary protection","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"phy-jwt-auth-audit","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Auth controls that should gate file access","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"phy-path-traversal-audit","tags":["security","path-traversal","lfi","owasp","python","java","php","nodejs","go","ruby"],"author":"@skillopedia","source":{"stars":2012,"repo_name":"openclaw-master-skills","origin_url":"https://github.com/leoyeai/openclaw-master-skills/blob/HEAD/skills/phy-path-traversal-audit/SKILL.md","repo_owner":"leoyeai","body_sha256":"c0c932460d428a277cf42508cecf8c75f50b8f11bfb2ce281b9c8d2e2ad46682","cluster_key":"8817a140a604f1a71234526f415e1daf644ea8195e26020bf0c0a424ef630f5a","clean_bundle":{"format":"clean-skill-bundle-v1","source":"leoyeai/openclaw-master-skills/skills/phy-path-traversal-audit/SKILL.md","attachments":[{"id":"21c2106e-c03b-52e5-b1de-ed45a2f600a3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/21c2106e-c03b-52e5-b1de-ed45a2f600a3/attachment.json","path":"_meta.json","size":300,"sha256":"e24c4aa57b1f729365e0ca95b3c3a8ae564c6c57348106be9dfd0cf7be738ca1","contentType":"application/json; charset=utf-8"}],"bundle_sha256":"0e04b773ef745f571cb99c53f425891971d15f11bcb1e165ddcf344e8f3910f0","attachment_count":1,"text_attachments":1,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/phy-path-traversal-audit/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"security","category_label":"Security"},"exact_dupes_collapsed_into_this":0},"license":"Apache-2.0","version":"v1","category":"security","metadata":{"author":"PHY041","version":"1.0.0"},"import_tag":"clean-skills-v1","description":"Path traversal and Local File Inclusion (LFI) vulnerability scanner (OWASP A01:2021). Detects user-controlled paths passed to file system sinks in Python/Java/PHP/Node.js/Go/Ruby without containment checks. Identifies missing os.path.abspath+startswith, realpath validation, basename stripping, and PHP include/require with user input. Outputs CWE-22/CWE-23 findings with HTTP taint analysis and per-language safe-path-handling code snippets. Zero competitors on ClawHub."}},"renderedAt":1782980912462}

phy-path-traversal-audit Static scanner for OWASP A01:2021 — Broken Access Control / Path Traversal (CWE-22) and Local File Inclusion (CWE-98). Finds file system sinks that accept user-controlled paths, checks for missing containment guards, and flags PHP / patterns that allow template injection. Zero external API calls, zero dependencies beyond Python 3 stdlib. What Is Path Traversal? An attacker passes or as a filename parameter. Without validation, your code reads arbitrary files outside the intended base directory. With PHP , it can lead to Remote Code Execution. Classic exploit: If your…