T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/public_repo_check.py:58
- Finding
- Changed-since scans inspect mutable working-tree content instead of committed HEAD content## Vulnerability Details **File Location**: `scripts/public_repo_check.py`, lines 58-68 and 136-141 **Vulnerability Type**: Security-gate bypass caused by scanning the wrong Git content source **Risk Level**: High ### Vulnerable Code ```python def read_content(repo: Path, path: str, staged: bool) -> bytes: if staged: result = subprocess.run( ["git", "-C", str(repo), "show", f":{path}"], check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) if result.returncode: return b"" return result.stdout return (repo / path).read_bytes() ``` The affected function is invoked by the changed-since scanning path as follows: ```python paths = git_paths(repo, staged, changed_since) print(f"branch: {branch or '(detached)'}") scope = "staged" if staged else (f"changed since {changed_since}" if changed_since else "tracked/untracked") print(f"checking {len(paths)} {scope} candidate paths") findings.extend(scan_paths(repo, paths, staged, config)) if check_remote_flag: findings.extend(check_remote(repo)) ``` ### Technical Analysis When `--changed-since REF` is used, `git_paths()` obtains candidate filenames from the committed range `REF...HEAD`. However, `scan_paths()` receives only the `staged` Boolean to select the content source. Because a changed-since scan is not a staged scan, `staged` is false and `read_content()` reads each candidate from the mutable working-tree filesystem: ```python return (repo / path).read_bytes() ``` Consequently, the set of paths represents committed `HEAD` changes, but the bytes being inspected may represent unrelated, uncommitted working-tree changes. The public push gate therefore does not reliably inspect the exact content that Git will push. This is a time-of-check and content-source mismatch. A sensitive committed blob can be hidden from the scanner by replacin ...[truncated 1336 chars]
- Remediation
- ## Remediation Suggestions Make the content source explicit rather than deriving it solely from the `staged` flag: - For `--staged`, read index content with `git show :path`. - For `--changed-since`, read committed content with `git show HEAD:path`. - For `--all`, read working-tree files where that behavior is intentionally required. - Treat a failure to read a candidate from the selected Git tree as a gate failure rather than returning empty bytes. - Consider using `git diff` with appropriate options to scan added content directly where practical. - Add regression tests in which the committed `HEAD` version contains a known secret while the working-tree version is benign. The changed-since gate must reject that repository. - Add tests for deleted, renamed, conflicted, and submodule paths so all Git object states are handled explicitly. A suitable design is to pass a content-source enum such as `INDEX`, `HEAD`, or `WORKTREE` into `scan_paths()` and `read_content()`, preventing future ambiguity between scan scope and content source.
