Finding Summary
Finding ID: SEC-W71-001
Severity: LOW
CWE: CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Two path-safety hardening gaps in bin/compute-input-hash were identified during the wave-71 security review (STORY-157). Both are still open on develop (b642c0f). They are filed together because they share a fix.
Affected File and Lines
SEC-001 — Comment-stripping path-traversal enablement
File: bin/compute-input-hash
Lines: 137–140
# AC-157-010: strip inline comment suffix (` # ...`)
comment_idx = path.find(" #")
if comment_idx != -1:
path = path[:comment_idx].strip()
After stripping the # ... suffix, no sanitization is applied to path before it is used in repo_root / path. An inputs: entry such as:
- ../../etc/shadow # RETIRED 2026-06-19
has the comment stripped, leaving ../../etc/shadow, which resolves outside the repository. Before the comment-stripping PR, the literal comment text made the path unresolvable; after PR merge it silently traverses out of .factory/.
SEC-002 — Absolute-path swallowing via PurePath.__truediv__
File: bin/compute-input-hash (repo_root resolution step)
Python's PurePath.__truediv__ discards the left-hand side when the right-hand side is an absolute path (e.g., /etc/shadow). An inputs: entry containing an absolute path bypasses repo_root entirely and resolves to any path on the filesystem.
Exploitability
- Requires write access to the
factory-artifacts branch (privileged, small trust boundary).
- The output is a 7-char hex MD5 truncation — not file contents. No direct information disclosure.
- The tool is an internal developer CLI with no untrusted callers and no CI privilege-escalation path.
- A successful traversal causes a hash of a file outside
.factory/ to be computed and stored, producing spurious drift signals rather than data exfiltration.
- Severity correctly classified as LOW.
Validation Provenance
- Primary evidence:
.factory/code-delivery/STORY-157/security-review.md §SEC-001 + §SEC-002
- Confirmed still open on
develop (b642c0f) during maint-2026-07-08 triage by research-agent per policy DF-VALIDATION-001
- Status at triage close:
VALIDATED-PENDING-FILING (2026-07-08)
- Human-approved filing: 2026-07-09 at maint-2026-07-09 gate
- DF-VALIDATION-001 prerequisites satisfied — finding is CONFIRMED and research-validated
Suggested Remediation
After the comment-strip step, validate path before resolution:
Option A — Reject traversal and absolute paths explicitly:
if ".." in Path(path).parts or Path(path).is_absolute():
raise ValueError(f"Input path '{path}' contains traversal or is absolute")
Option B — Use Path.resolve(strict=True) + is_relative_to(repo_root) (preferred):
resolved = (repo_root / path).resolve(strict=True)
if not resolved.is_relative_to(repo_root.resolve()):
raise ValueError(f"Input path '{path}' resolves outside the repository root")
Option B covers both SEC-001 and SEC-002 in one guard and is the canonical safe pattern for restricting path resolution to a base directory.
References
.factory/code-delivery/STORY-157/security-review.md §SEC-001, §SEC-002
.factory/maintenance/backlog-triage-maint-2026-07-08.md §1 (SEC-W71-001)
.factory/maintenance/sweep-report-2026-07-08.md DF-VALIDATION-001 Triage table
- MITRE CWE-22: https://cwe.mitre.org/data/definitions/22.html
Finding Summary
Finding ID: SEC-W71-001
Severity: LOW
CWE: CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Two path-safety hardening gaps in
bin/compute-input-hashwere identified during the wave-71 security review (STORY-157). Both are still open ondevelop(b642c0f). They are filed together because they share a fix.Affected File and Lines
SEC-001 — Comment-stripping path-traversal enablement
File:
bin/compute-input-hashLines: 137–140
After stripping the
# ...suffix, no sanitization is applied topathbefore it is used inrepo_root / path. Aninputs:entry such as:has the comment stripped, leaving
../../etc/shadow, which resolves outside the repository. Before the comment-stripping PR, the literal comment text made the path unresolvable; after PR merge it silently traverses out of.factory/.SEC-002 — Absolute-path swallowing via
PurePath.__truediv__File:
bin/compute-input-hash(repo_root resolution step)Python's
PurePath.__truediv__discards the left-hand side when the right-hand side is an absolute path (e.g.,/etc/shadow). Aninputs:entry containing an absolute path bypassesrepo_rootentirely and resolves to any path on the filesystem.Exploitability
factory-artifactsbranch (privileged, small trust boundary)..factory/to be computed and stored, producing spurious drift signals rather than data exfiltration.Validation Provenance
.factory/code-delivery/STORY-157/security-review.md§SEC-001 + §SEC-002develop(b642c0f) during maint-2026-07-08 triage by research-agent per policy DF-VALIDATION-001VALIDATED-PENDING-FILING(2026-07-08)Suggested Remediation
After the comment-strip step, validate
pathbefore resolution:Option A — Reject traversal and absolute paths explicitly:
Option B — Use
Path.resolve(strict=True)+is_relative_to(repo_root)(preferred):Option B covers both SEC-001 and SEC-002 in one guard and is the canonical safe pattern for restricting path resolution to a base directory.
References
.factory/code-delivery/STORY-157/security-review.md§SEC-001, §SEC-002.factory/maintenance/backlog-triage-maint-2026-07-08.md§1 (SEC-W71-001).factory/maintenance/sweep-report-2026-07-08.mdDF-VALIDATION-001 Triage table