T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/generate_report.py:53
- Finding
- Unrestricted Chart Paths Allow Arbitrary Local File Disclosure Through Base64 Embedding<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_report.py:53-55` and `scripts/generate_report.py:419-421` **Vulnerability Type**: Arbitrary local file read and unintended data embedding **Risk Level**: High ### Vulnerable Code ```python def b64img(path): if not path or not os.path.isfile(path): return "" try: with open(path, "rb") as f: return f"data:image/png;base64,{base64.b64encode(f.read()).decode()}" except: return "" ``` The paths passed to this function are obtained directly from the input JSON: ```python charts = data.get("charts", {}) ``` They are then embedded in the generated report: ```python chart_imgs = [] for key in ["price", "revenue", "margins", "pe"]: src = b64img(charts.get(key, "")) if src: chart_imgs.append(f'<img src="{src}" alt="{key}">') ``` ### Technical Analysis Embedding generated charts as Base64 is consistent with the declared requirement to produce portable HTML reports. The encoding itself is therefore not evidence of a covert channel. However, `b64img()` accepts any regular file path supplied through the data JSON. It does not verify that the file: - Is located in the expected chart-output directory. - Has a permitted filename. - Is a genuine PNG image. - Was created by the current pipeline execution. - Is not a symbolic link. - Falls below a safe size limit. The `data:image/png` prefix does not validate or transform the underlying content. Any readable file is loaded in full, Base64-encoded, and written into the HTML source. This exceeds the minimum filesystem access required to embed the four charts. Because `generate_report.py` accepts an arbitrary JSON file as a positional argument, an attacker who can influence that file can make the process read files available under the agent's operating-system privileges. ### Attack Path 1. The attacker supplies or modifies a pipeline JSON document. 2. A chart entry is changed to reference a sensitive local file: ```json { ...[truncated 1441 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Do not accept chart paths from the input JSON. Reconstruct expected chart filenames from a strictly validated ticker and a pipeline-owned working directory. - Resolve and validate every path before opening it: ```python from pathlib import Path CHART_DIR = Path("/tmp/stock-valuation/charts").resolve() def validated_chart_path(raw_path): candidate = Path(raw_path) if candidate.is_symlink(): raise ValueError("Symbolic links are not allowed") resolved = candidate.resolve(strict=True) if CHART_DIR not in resolved.parents: raise ValueError("Chart path is outside the permitted directory") if resolved.suffix.lower() != ".png": raise ValueError("Only PNG charts are permitted") return resolved ``` - Open files using protections against symbolic-link traversal where supported, such as `os.open()` with `O_NOFOLLOW`. - Verify the PNG signature and decode the image with a trusted image library before embedding it. - Enforce a conservative maximum file size before reading. - Create a unique per-run private directory with mode `0700`, and accept only files created in that directory. - Avoid broad exception handling that silently hides attempted invalid file access. Log validation failures without exposing sensitive paths. ]]>
