T09 · Insecure Skill Coding Practices
- Location
src/index.ts:175- Finding
Path Traversal Allows Arbitrary Markdown File Overwrite
- Content
View full analysis
Vulnerability Details
File Location:
src/index.ts, lines 175–176 and 219
Vulnerability Type: Path traversal and uncontrolled file write
Risk Level: HighVulnerable Code
ts export function generateReport(date?: string): string { const targetDate = date || new Date().toISOString().split('T')[0];ts fs.writeFileSync(path.join(LOGS_DIR, `${targetDate}.md`), report); return report; }Technical Analysis
The exported
generateReport()function accepts a caller-controlleddatestring and inserts it directly into an output filename. The code does not enforce the expectedYYYY-MM-DDformat, reject path separators, or verify that the resolved destination remains insideLOGS_DIR.path.join()normalizes traversal components such as../, but it does not constrain the resulting path to the intended directory. Consequently, a value such as../../SKILLcan cause the destination path to escapedata/logs:text data/logs/../../SKILL.mdThis normalizes to the project-level
SKILL.md. Becausefs.writeFileSync()replaces an existing file by default, the destination can be overwritten if the process has filesystem permission to write it.The
.mdsuffix limits direct targeting to paths ending in.md, but it does not prevent traversal or protect Markdown files outside the report directory.Attack Path
-
An attacker gains control of, or influences, the argument passed to
generateReport(). -
The attacker supplies a traversal value, for example:
ts generateReport("../../SKILL"); -
The function uses the unvalidated value as both report content metadata and part of the filename.
-
path.join(LOGS_DIR, "../../SKILL.md")resolves outsideLOGS_DIR. -
fs.writeFileSync()overwrites the resolved Markdown file. -
If the overwritten file is subsequently loaded as agent instructions, the generated content may alter or break Skill behavior. Even without subsequent loading, the overwr ...[truncated 845 chars]
-
- Remediation
View remediation
Remediation Suggestions
-
Enforce an exact ISO date format before using the value:
ts function validateDate(value: string): string { if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) { throw new Error('Date must use YYYY-MM-DD format'); } const parsed = new Date(`${value}T00:00:00.000Z`); if ( Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value ) { throw new Error('Invalid calendar date'); } return value; } -
Resolve the final path and verify that it remains directly under the expected logs directory:
ts const safeDate = validateDate( date || new Date().toISOString().slice(0, 10), ); const logsRoot = path.resolve(LOGS_DIR); const outputPath = path.resolve(logsRoot, `${safeDate}.md`); if (path.dirname(outputPath) !== logsRoot) { throw new Error('Invalid report path'); } fs.writeFileSync(outputPath, report, { encoding: 'utf8', flag: 'w', mode: 0o600, }); -
Never construct filesystem paths from raw caller input. Derive the filename from a successfully parsed date or another strict allowlisted identifier.
-
Add tests covering traversal and malformed input, including
../../SKILL,../report, absolute paths, encoded separators, invalid dates, and platform-specific separators. -
Run the Skill with least-privilege filesystem permissions so the process can write only to its intended data and log directories.
-
