T09 · Insecure Skill Coding Practices
Warning
- Location
- forge.py:23
- Finding
- Persistent State and SOUL.md Backups Are Written Outside the Skill Directory## Vulnerability Details **File Location**: `observe.py:20-23`, `reflect.py:18-21`, and `forge.py:23-27` **Vulnerability Type**: Incorrect base-directory resolution and cross-project state exposure **Risk Level**: Medium ### Vulnerable Code `observe.py:20-23`: ```python SKILL_DIR = Path(__file__).parent.parent MEMORY_DIR = SKILL_DIR / "memory" OBSERVATIONS_FILE = MEMORY_DIR / "observations.json" MEMORY_DIR.mkdir(exist_ok=True) ``` `reflect.py:18-21`: ```python SKILL_DIR = Path(__file__).parent.parent MEMORY_DIR = SKILL_DIR / "memory" OBSERVATIONS_FILE = MEMORY_DIR / "observations.json" ``` `forge.py:23-27`: ```python SKILL_DIR = Path(__file__).parent.parent MEMORY_DIR = SKILL_DIR / "memory" OBSERVATIONS_FILE = MEMORY_DIR / "observations.json" BACKUPS_DIR = MEMORY_DIR / "backups" BACKUPS_DIR.mkdir(parents=True, exist_ok=True) ``` ### Technical Analysis The three Python files are located directly in the project root rather than in the documented `scripts/` subdirectory. As a result, `Path(__file__).parent.parent` resolves to the parent of the project directory, not the project directory itself. The effective paths therefore become: ```text <project-parent>/memory/observations.json <project-parent>/memory/backups/ ``` instead of the documented locations: ```text <project>/memory/observations.json <project>/memory/backups/ ``` This creates an unintended shared-state boundary. Other projects or local users with write access to the project parent can create or alter the observation file consumed by `reflect.py` and `forge.py`. Likewise, `observe.py` stores session-derived behavioral data outside the expected Skill directory, and `forge.py` copies SOUL.md into an externally located backup directory. The use of `mkdir()` at module scope also means importing `observe.py` or `forge.py` can create directories outside the Skill without invoking their command-line entry points. ### Attack Path 1. An attacker with local write access to the parent directory c ...[truncated 1647 chars]
- Remediation
- ## Remediation Suggestions 1. Resolve the Skill directory according to the actual package layout: ```python SKILL_DIR = Path(__file__).resolve().parent MEMORY_DIR = SKILL_DIR / "memory" ``` Alternatively, move the Python files into the documented `scripts/` directory and retain the parent traversal only after verifying the resolved destination. 2. Validate that all state paths remain beneath the intended Skill directory: ```python SKILL_DIR = Path(__file__).resolve().parent MEMORY_DIR = (SKILL_DIR / "memory").resolve() if SKILL_DIR not in MEMORY_DIR.parents: raise RuntimeError("Memory directory escapes the skill directory") ``` 3. Remove import-time filesystem mutations. Create `memory/` and `backups/` only inside explicit runtime initialization functions. 4. Create state directories and files with restrictive permissions appropriate to the platform, because observations and SOUL.md backups may contain sensitive behavioral or identity information. 5. Validate the observations JSON schema before using it. Require expected types and bounded values for fields such as `session_count`, `tone_history`, `hedging`, and `vocabulary`. 6. Write `observations.json` atomically by creating a restricted temporary file in the same directory, flushing it, and replacing the destination. This reduces corruption and race-condition risks. 7. Add tests asserting that `OBSERVATIONS_FILE` and `BACKUPS_DIR` resolve beneath the project root for the distributed package layout. 8. Update the documentation only after implementation paths and the published file structure are consistent.
