CodeQL Path Injection Alert: How to Diagnose and Fix It Safely
GitHub CodeQL flags path injection alerts when your code constructs file paths from user-supplied input without proper validation. These alerts fall under the CWE-22: Improper Limitation of a Pathname to a Restricted Directory category — also known as directory traversal or path traversal. If you have received a CodeQL path injection alert in your repository, this guide walks you through exactly what it means, how to confirm it, and how to fix it safely without breaking your application.
What Is a CodeQL Path Injection Alert?
CodeQL detects path injection when an attacker can control part of a file path and potentially access files outside the intended directory. The classic pattern looks like this:
# Vulnerable: user input directly in file path
from flask import Flask, request
import os
app = Flask(__name__)
@app.route('/download')
def download():
filename = request.args.get('file')
filepath = os.path.join('/var/data/reports/', filename)
return open(filepath).read()
If an attacker sends ?file=../../etc/passwd, the os.path.join call resolves to /var/data/reports/../../etc/passwd — which simplifies to /etc/passwd. CodeQL correctly identifies this as a path injection vulnerability because the filename variable flows from user input (request.args.get) into a file operation without sanitization.
Common CodeQL query names for these alerts include:
js/path-injection— JavaScript/TypeScriptpy/path-injection— Pythonjava/path-injection— Javacsharp/path-injection— C#
All of these trace data flow from a source (user input, HTTP parameters, environment variables from untrusted origins) to a sink (file read, write, delete, or execute operations).
Step-by-Step Diagnosis
Before you fix anything, confirm the alert is genuine. Not every CodeQL path injection alert is a real vulnerability — some are false positives or low-severity findings in non-production code.
1. Locate the Alert in Your Code
Open the GitHub Security tab, find the CodeQL alert, and trace the data flow path CodeQL shows you. It will highlight:
- Source: Where the untrusted data enters (e.g.,
request.args,sys.argv,os.environfrom external input) - Sink: Where the path is used in a file operation (e.g.,
open(),os.path.join(),subprocess.call()) - Intermediate steps: Any transformations or assignments along the way
2. Check: Can the Attacker Actually Control the Path?
Ask yourself:
- Is the source truly external (HTTP request, user upload, config file editable by untrusted users)?
- Is there any existing validation or allowlisting that CodeQL might not recognize?
- Does the runtime environment restrict file access (chroot, container filesystem, AppArmor)?
If the source is internal-only (e.g., a constant, a config file only admins can edit), the alert may be a false positive you can dismiss.
3. Check: Is There Already a Guard?
CodeQL sometimes misses custom sanitization. Look for:
- Explicit allowlist checks (
if filename in ALLOWED_FILES:) - Prefix enforcement (
if not filepath.startswith(BASE_DIR):) - Character filtering (
filename.replace('..', '')— note: this alone is not sufficient)
Safe Fixes for CodeQL Path Injection Alerts
Fix 1: Allowlist of Valid Filenames
The safest approach is to only allow specific, known filenames:
ALLOWED_FILES = {'report1.pdf', 'report2.pdf', 'summary.csv'}
@app.route('/download')
def download():
filename = request.args.get('file', '')
if filename not in ALLOWED_FILES:
abort(404)
filepath = os.path.join('/var/data/reports/', filename)
return open(filepath).read()
This eliminates path injection entirely because an attacker can never insert traversal sequences — they can only choose from the allowed set.
Fix 2: Resolve and Validate the Real Path
If you need dynamic filenames, resolve the path and then verify it stays within the base directory:
import os
BASE_DIR = os.path.realpath('/var/data/reports/')
@app.route('/download')
def download():
filename = request.args.get('file', '')
filepath = os.path.realpath(os.path.join(BASE_DIR, filename))
if not filepath.startswith(BASE_DIR + os.sep):
abort(403)
return open(filepath).read()
Key details:
os.path.realpathresolves.., symlinks, and//sequences to the actual canonical path.- The
startswith(BASE_DIR + os.sep)check ensures the resolved path is inside the base directory. - Always add
os.sep(or/) to prevent the base directory name itself being a prefix of an unrelated path (e.g.,/var/data/reports-other/).
Fix 3: Strip Directory Components
For simpler cases, you can strip directory components from the filename:
@app.route('/download')
def download():
filename = os.path.basename(request.args.get('file', ''))
filepath = os.path.join('/var/data/reports/', filename)
return open(filepath).read()
os.path.basename removes all directory components, so ../../etc/passwd becomes just passwd. This is simple but less flexible — it prevents directory traversal but still lets the attacker choose any filename in the target directory. Use this only when combined with other checks or when the target directory contains no sensitive files.
Fix 4: Use a Safe Path Library
Python 3.9+ offers pathlib.PurePath for safer path handling, and some frameworks provide built-in safe path utilities. For example, Flask’s send_from_directory automatically validates the path:
from flask import send_from_directory
@app.route('/download')
def download():
filename = request.args.get('file', '')
return send_from_directory('/var/data/reports/', filename)
send_from_directory raises a NotFound error if the path escapes the directory — no manual validation needed.
What Not to Do
These approaches are not safe and CodeQL will still flag them:
- String replace of
..—filename.replace('..', '')can be bypassed with....//or..%2f. - Checking only for
..in the raw string — URL encoding, double encoding, and case variations bypass this. - Relying on
os.path.joinalone —os.path.join('/safe/dir', '../../etc/passwd')returns/safe/dir/../../etc/passwdwhich resolves outside the directory. - Assuming container isolation is enough — containers still have sensitive files (
/etc/passwd,/proc/self/environ, environment secrets). Defense in depth means fixing the code, not only relying on runtime boundaries.
CodeQL Path Injection Fix Checklist
Use this checklist for every CodeQL path injection alert you triage:
- [ ] Identify the source: where does the untrusted data enter?
- [ ] Identify the sink: what file operation uses the path?
- [ ] Confirm the alert is genuine (attacker can control the path, no existing guard)
- [ ] Apply one of the safe fixes: allowlist, realpath+startswith, basename, or framework utility
- [ ] Re-run CodeQL to confirm the alert is dismissed
- [ ] Add a regression test that sends a traversal payload and asserts safe behavior
- [ ] Document the fix in your PR description with a reference to CWE-22
- [ ] If the alert is a false positive, dismiss it in GitHub with a clear reason
Language-Specific Notes
JavaScript / Node.js
Use path.resolve and path.normalize with a base directory check:
const path = require('path');
const BASE = path.resolve('/var/data/reports');
function safePath(filename) {
const resolved = path.resolve(BASE, filename);
if (!resolved.startsWith(BASE + path.sep)) {
throw new Error('Invalid path');
}
return resolved;
}
Java
Use Path.normalize().toAbsolutePath() and verify with startsWith:
Path basePath = Paths.get("/var/data/reports").normalize().toAbsolutePath();
Path resolved = basePath.resolve(filename).normalize().toAbsolutePath();
if (!resolved.startsWith(basePath)) {
throw new SecurityException("Path traversal detected");
}
C# / .NET
Use Path.GetFullPath combined with a base directory check:
var basePath = Path.GetFullPath("/var/data/reports/");
var resolved = Path.GetFullPath(Path.Combine(basePath, filename));
if (!resolved.StartsWith(basePath))
throw new SecurityException("Path traversal detected");
When to Dismiss a CodeQL Path Injection Alert
Not every alert requires a code change. Dismiss an alert when:
- The source is not attacker-controlled (hardcoded values, admin-only config)
- The sink operates on a temporary or isolated filesystem with no sensitive data
- The path is already validated by a framework or middleware that CodeQL does not recognize
- The alert is on test code or a build script that never runs in production
When you dismiss, always add a reason in GitHub so other team members understand the decision.
Integrating Path Injection Checks Into Your CI Pipeline
CodeQL alerts are most useful when they are part of your continuous integration pipeline. Here is a practical setup:
- Enable CodeQL analysis in GitHub Actions: Use the
github/codeql-actionwithconfig-fileto customize which queries to run. - Set
paths-filter: Only run CodeQL on changed files to keep CI fast. - Fail on high-severity path alerts: Configure your CodeQL config to treat
CWE-22as an error, not a warning. - Auto-dismiss false positives: Use
.github/codeql/codeql-config.ymlto exclude test directories if they generate noise. - Add pre-commit hooks: For local checks, integrate CodeRiskTools AI code security to catch path injection patterns before they reach CI.
For a broader security review checklist that covers path injection alongside other common vulnerabilities, download the free AI code review checklist.
FAQ: CodeQL Path Injection Alerts
Is every CodeQL path injection alert a critical vulnerability?
No. Some alerts are informational. Always check whether the source is actually attacker-controlled and whether there is existing sanitization CodeQL did not recognize.
Can I just suppress the alert instead of fixing the code?
You can dismiss false positives with a reason. But suppressing a genuine vulnerability creates technical debt and risk. Always prefer to fix or add explicit validation.
Does escaping percent-encoding prevent path traversal?
No. Percent-encoding is a URL-level concern. Always validate the decoded path after URL decoding. Use urllib.parse.unquote before path validation.
Does os.path.join prevent path traversal?
No. os.path.join('/base', '../secret') produces /base/../secret. Always combine os.path.join with os.path.realpath and a startswith check.
Where can I get a broader code security review?
The CodeRiskTools product library includes automated security checklists and audit kits designed for solo developers and small teams who want to catch vulnerabilities like path injection before they ship.
For a side-by-side look at how local security tools stack up against cloud SaaS alternatives, see our full comparison page.
Need a professional second pair of eyes on your codebase? The Expert AI Code Security Audit gives you a human-reviewed security assessment of your most critical repositories — not just automated scans, but real analysis of AI-generated code risks.


