T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/sentry.py:482
- Finding
- Unvalidated file paths permit operations outside the selected workspace<![CDATA[ ## Vulnerability Details **File Location**: `scripts/sentry.py:482-695` **Vulnerability Type**: Path traversal and workspace-boundary violation **Risk Level**: High ### Vulnerable Code The redaction command constructs a target from an unvalidated user-controlled path: ```python def _redact_file(filepath, workspace): """Redact secrets in a single file. Returns (num_redacted, findings).""" try: content = filepath.read_text(encoding="utf-8", errors="ignore") except (OSError, PermissionError): return 0, [] # Secret matching and replacement occur here. if total_redacted > 0: # Create .bak backup before modifying bak = filepath.with_suffix(filepath.suffix + ".bak") shutil.copy2(filepath, bak) # Write redacted content filepath.write_text("\n".join(new_lines), encoding="utf-8") return total_redacted, findings ``` ```python if filepath: fpath = workspace / filepath if not fpath.exists(): print(f"File not found: {filepath}") return 1 if is_binary(fpath): print(f"Skipping binary file: {filepath}") return 0 count, findings = _redact_file(fpath, workspace) ``` The quarantine and restoration commands use the same unsafe path construction: ```python def cmd_quarantine(workspace, filepath): """Move a file containing secrets to quarantine with metadata.""" fpath = workspace / filepath if not fpath.exists(): print(f"File not found: {filepath}") return 1 # Scan the file first findings = [] if not is_binary(fpath): findings = scan_file(fpath, workspace) # Create quarantine directory qdir = quarantine_base(workspace) qdir.mkdir(parents=True, exist_ok=True) # Determine quarantine destination (preserve relative structure) rel = Path(filepath) dest = qdir / rel dest.parent.mkdir(parents=True, exist_ok=True) # Move file to quarantine shutil.move(str(fpa ...[truncated 2898 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Resolve the workspace once and validate every target before any operation: ```python def resolve_workspace_file(workspace, user_path): workspace = workspace.resolve(strict=True) supplied = Path(user_path) if supplied.is_absolute() or ".." in supplied.parts: raise ValueError("File path must be workspace-relative") target = (workspace / supplied).resolve(strict=False) try: target.relative_to(workspace) except ValueError: raise ValueError("File path escapes the workspace") return target ``` 2. Apply equivalent containment checks independently to: - Redaction targets. - Quarantine source and destination paths. - Unquarantine source and restoration paths. - Backup paths and metadata paths. 3. Validate the resolved path again immediately before mutation to reduce time-of-check/time-of-use and symlink-switching risks. 4. Reject symlinks for mutating commands, or securely verify that every resolved symlink target remains inside the allowed root. 5. Require explicit confirmation before moving private keys, environment files, or other operational credentials. 6. Add automated tests for: - `../` traversal. - Nested traversal. - Absolute Unix and Windows paths. - Symlinks pointing outside the workspace. - Quarantine and restoration paths that escape their respective roots. ]]>
