T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/callback_server.py:72
- Finding
- Path Traversal Through Unvalidated Tenant and OAuth State Identifiers<![CDATA[ ## Vulnerability Details **File Location**: `scripts/lib/auth.py:134-145`, `scripts/onboard.py:172`, `scripts/callback_server.py:72-91`, `scripts/fetch.py:205-213` **Vulnerability Type**: Path traversal and arbitrary file access **Risk Level**: High ### Vulnerable Code `scripts/lib/auth.py:134-145`: ```python def load_mandant(mandant_id: str) -> dict: """Load mandanten/{mandant_id}.json.""" MANDANTEN_DIR.mkdir(parents=True, exist_ok=True) path = MANDANTEN_DIR / f"{mandant_id}.json" if not path.exists(): print(f"❌ Mandant not found: {path}", file=sys.stderr) sys.exit(1) with open(path) as f: return json.load(f) def save_mandant(mandant_id: str, data: dict) -> None: """Save mandanten/{mandant_id}.json (chmod 600).""" MANDANTEN_DIR.mkdir(parents=True, exist_ok=True) path = MANDANTEN_DIR / f"{mandant_id}.json" ``` `scripts/callback_server.py:72-91`: ```python if parsed.path == "/callback": params = parse_qs(parsed.query) code = params.get("code", [None])[0] state = params.get("state", [None])[0] if code and state: # Save callback PENDING_DIR.mkdir(parents=True, exist_ok=True) callback_data = { "code": code, "state": state, "receivedAt": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), } path = PENDING_DIR / f"{state}.json" with open(path, "w") as f: json.dump(callback_data, f, indent=2) os.chmod(path, 0o600) ``` `scripts/fetch.py:205-213`: ```python # Save to data directory data_dir = output_dir or DATA_DIR mandant_data_dir = data_dir / mandant_id mandant_data_dir.mkdir(parents=True, exist_ok=True) today = datetime.now().strftime("%Y-%m-%d") data_file = mandant_data_dir / f"{today}.json" with open(data_file, "w") as f: json.dump(result, f, indent=2, ensure_ascii=False) os.chmod(data_file, 0o600) ``` ### Technical Analysis The code interpolates attacker-controlled ` ...[truncated 2045 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Validate tenant IDs and callback states before using them in paths. For tenant IDs, use a strict allowlist such as: ```python SAFE_ID = re.compile(r"^[A-Za-z0-9_-]{1,64}$") ``` 2. Do not use an OAuth state received from the network as an unrestricted filename. Store pending states under server-generated opaque identifiers. 3. Resolve every destination and verify containment: ```python candidate = (base / f"{identifier}.json").resolve() if candidate.parent != base.resolve(): raise ValueError("Invalid identifier") ``` 4. Perform the same validation in `load_mandant`, `save_mandant`, onboarding existence checks, callback handling, renewal, and fetch output handling. 5. Use exclusive or atomic file creation where appropriate to prevent replacement races. 6. Run the callback service under a dedicated low-privilege account with write access only to its pending-callback directory. 7. Add tests covering `../`, absolute paths, path separators, encoded separators, excessive lengths, and platform-specific traversal forms. ]]>
