Autofix: Code scanning alert #1#4
Conversation
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
| os.makedirs(os.path.dirname(args.write_md), exist_ok=True) | ||
| with open(args.write_md, "w", encoding="utf-8") as f: | ||
| output_dir = os.path.dirname(write_path) or "." | ||
| os.makedirs(output_dir, exist_ok=True) |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix uncontrolled path use, derive a canonical path rooted under a fixed base directory, normalize it with os.path.normpath (or realpath), and ensure the result is still within that base directory. Avoid trusting string patterns alone (like just checking for ".."), and avoid letting the user control directory components when you intend to constrain writes to a specific folder tree.
For this code, the best fix is to treat args.write_md as a relative path under the fixed base directory out, then normalize and validate the combined path. Specifically:
- Compute an absolute, normalized base directory (e.g.,
base_output_dir_abs = os.path.abspath(base_output_dir)). - Join
base_output_dir_absand the user-suppliedargs.write_md, normalize the result (os.path.normpath), and make it absolute. - Check that the resulting path is still under
base_output_dir_abs(using a robust prefix check likeos.path.commonpath). - If validation fails (absolute path, path escaping the base, or empty), fall back to a safe default like
code_scanning_triage.mdunder the base directory. - Use this validated
write_pathto deriveoutput_dirand for writing the file.
This keeps the existing behavior—always writing into an out directory and allowing the user to pick a file name and (optionally) subdirectories under that directory—while making the safety guarantees explicit and aligned with the recommended pattern.
Concretely, in scripts/triage_all_repos.py around lines 129–137, replace the current ad hoc requested_path / safe_filename logic with the robust normalization and containment check. No new imports are needed because os is already imported. The rest of the file (writing, printing, issue updating) remains unchanged.
| @@ -127,14 +127,16 @@ | ||
| args = ap.parse_args() | ||
|
|
||
| base_output_dir = "out" | ||
| # Normalize the requested output path and ensure it stays within base_output_dir. | ||
| requested_path = os.path.normpath(args.write_md) | ||
| # Disallow absolute paths or traversal outside the base directory. | ||
| if os.path.isabs(requested_path) or os.pardir in requested_path.split(os.sep): | ||
| safe_filename = "code_scanning_triage.md" | ||
| # Compute a safe, normalized output path rooted under base_output_dir. | ||
| base_output_dir_abs = os.path.abspath(base_output_dir) | ||
| requested_rel_path = args.write_md or "code_scanning_triage.md" | ||
| # Join and normalize the user-supplied path with the base directory. | ||
| candidate_path = os.path.normpath(os.path.join(base_output_dir_abs, requested_rel_path)) | ||
| # Ensure the candidate path stays within the base output directory. | ||
| if not os.path.commonpath([base_output_dir_abs, candidate_path]) == base_output_dir_abs: | ||
| write_path = os.path.join(base_output_dir_abs, "code_scanning_triage.md") | ||
| else: | ||
| safe_filename = os.path.basename(requested_path) or "code_scanning_triage.md" | ||
| write_path = os.path.normpath(os.path.join(base_output_dir, safe_filename)) | ||
| write_path = candidate_path | ||
|
|
||
| rest, cs = create_clients() | ||
|
|
| with open(args.write_md, "w", encoding="utf-8") as f: | ||
| output_dir = os.path.dirname(write_path) or "." | ||
| os.makedirs(output_dir, exist_ok=True) | ||
| with open(write_path, "w", encoding="utf-8") as f: |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, the problem is fixed by strictly constraining the user-controlled portion of the path: treat user input as a filename only (no directory components), validate or sanitize it, and then join it to a predefined base directory. Optionally, normalize and verify that the resulting path is still within that base directory before using it.
In this script, the best minimal fix that does not change existing functionality is:
- Continue to use a fixed base directory (
out). - Treat
args.write_mdstrictly as a filename, stripping any directory parts withos.path.basename. - Reject any attempt to smuggle path separators or parent directories by checking for
os.sep, alternative separators (likeos.altsep), oros.pardirin the resulting filename. If validation fails, fall back to a safe default filename (code_scanning_triage.md). - Join the validated filename to
base_output_dir, normalize once, and rely only on that path.
Concretely, in scripts/triage_all_repos.py, lines 129–137 already attempt something similar but still permit some edge cases (for example, non-standard separators on some platforms, or filenames like .. that may normalize oddly). We’ll replace that block with stricter logic:
- Compute
requested_path = os.path.normpath(args.write_md or ""). - Derive
candidate = os.path.basename(requested_path)so we never keep directory components. - Validate that
candidateis non-empty, is not.or.., does not containos.sep, does not containos.altsep(if present), and does not equalos.pardir. - If any of those checks fail, use the default filename; otherwise accept
candidate. - Build
write_path = os.path.join(base_output_dir, safe_filename)and normalize it.
No new imports are required; os is already imported at the top of the file.
| @@ -127,13 +127,21 @@ | ||
| args = ap.parse_args() | ||
|
|
||
| base_output_dir = "out" | ||
| # Normalize the requested output path and ensure it stays within base_output_dir. | ||
| requested_path = os.path.normpath(args.write_md) | ||
| # Disallow absolute paths or traversal outside the base directory. | ||
| if os.path.isabs(requested_path) or os.pardir in requested_path.split(os.sep): | ||
| # Treat the user-provided path as a filename only and validate it before use. | ||
| requested_path = os.path.normpath(args.write_md or "") | ||
| candidate = os.path.basename(requested_path) | ||
| # Reject empty names, parent-directory indicators, or any embedded path separators. | ||
| invalid_name = ( | ||
| not candidate | ||
| or candidate in (".", "..") | ||
| or candidate == os.pardir | ||
| or os.sep in candidate | ||
| or (os.altsep is not None and os.altsep in candidate) | ||
| ) | ||
| if invalid_name: | ||
| safe_filename = "code_scanning_triage.md" | ||
| else: | ||
| safe_filename = os.path.basename(requested_path) or "code_scanning_triage.md" | ||
| safe_filename = candidate | ||
| write_path = os.path.normpath(os.path.join(base_output_dir, safe_filename)) | ||
|
|
||
| rest, cs = create_clients() |
Automated autofix campaign run at 2026-01-18T23:39:25Z.
Alert: https://github.com/Notoriousjayy/ghas-code-scanning-toolkit/security/code-scanning/1