T09 · Insecure Skill Coding Practices
- Location
s2b.sh:273- Finding
Unvalidated Session Identifier Allows Path Traversal and Arbitrary JSONL File Reads
- Content
View full analysis
Vulnerability Details
File Location:
s2b.sh, lines 273–274; user-controlled input originates at line 25
Vulnerability Type: Path traversal / arbitrary local file read
Risk Level: High
Classification: T09: Insecure Skill Coding PracticesVulnerable Code
bash --session) SESSION_ID="${2:-}"; shift 2 ;;python if session_id: target = os.path.join(session_dir, session_id + ".jsonl")The resulting path is subsequently accepted when it refers to a regular file and is opened by the session-processing functions.
Technical Analysis
The
--sessionargument is copied intoSESSION_IDwithout validation. The Python implementation then appends.jsonland joins the result to the intended session directory.os.path.join()does not enforce directory containment. A session identifier containing../components can resolve outside the configured session directory. In addition, if the supplied identifier is absolute, Python discards the precedingsession_dircomponent.The implementation neither canonicalizes the target with
realpath()nor verifies that the resolved target remains beneath the canonical session directory. Consequently, the check that the target is a regular file does not prevent an out-of-directory read.Exploitation requires the target to be readable by the account running the Skill, to end in
.jsonlafter argument processing, and to contain records matching the expected message format. The redaction function does not eliminate the vulnerability because it is heuristic and does not cover every possible secret or sensitive data format.Attack Path
- An attacker causes the Skill to be invoked with a crafted argument such as:
text /s2b --session ../../path/to/sensitive-session - The shell stores the traversal string in
S2B_SESSION_ID. - Python constructs:
python os.path.join(session_dir, "../../path/to/sensiti
...[truncated 1064 chars]
- An attacker causes the Skill to be invoked with a crafted argument such as:
- Remediation
View remediation
Remediation Suggestions
- Restrict session identifiers to an explicit allowlist:
python import re if not re.fullmatch(r"[A-Za-z0-9_-]+", session_id): raise ValueError("Invalid session identifier") - Resolve and verify containment before opening the file:
python base = os.path.realpath(session_dir) target = os.path.realpath(os.path.join(base, session_id + ".jsonl")) if os.path.commonpath([base, target]) != base: raise ValueError("Session path is outside the session directory") - Prefer selecting the target only from the already enumerated
filescollection instead of constructing a path directly from user input. - Reject absolute paths, path separators,
.components, and..components explicitly. - Where supported, open files with protections against symbolic-link races and verify the opened descriptor refers to an expected regular file.
- Add tests covering relative traversal, absolute paths, symbolic links, encoded separators, nonexistent sessions, and valid session identifiers.
- Treat redaction only as defense in depth. Avoid emitting session content to terminal output by default and require explicit confirmation before including sensitive dialogue in generated material.
- Restrict session identifiers to an explicit allowlist:
