T09 · Insecure Skill Coding Practices
- Location
scripts/audit_skills.py:255- Finding
Uncontained File Write Through Symbolic-Link Traversal
- Content
View full analysis
Vulnerability Details
File Location:
scripts/audit_skills.py:255-268
Vulnerability Type: Symbolic-link traversal and out-of-scope file overwrite
Risk Level: MediumVulnerable Code
python try: content = sf.read_text(encoding='utf-8') except Exception as e: report['skills'].append({ 'path': str(sf), 'error': f'Cannot read file: {e}', 'score': 0, }) report['issues_found'] += 1 continue fm, body = parse_frontmatter(content) if fm is None: report['skills'].append({ 'path': str(sf), 'error': 'No YAML frontmatter found', 'score': 0, 'issues': ['CRITICAL: Missing YAML frontmatter (--- ... ---)'], 'suggestions': ['Add frontmatter with name: and description: fields'], }) report['issues_found'] += 1 continue skill_info = { 'path': str(sf), 'name': fm['name'] or 'MISSING', 'description_length': len(fm['description'] or ''), } if fm['colon_issue']: skill_info['issues'] = ['YAML: Colon in description may break parsing'] skill_info['auto_fix'] = 'Wrap description in quotes' report['issues_found'] += 1 if auto_fix: fixed = auto_fix_colon(content) if fixed: sf.write_text(fixed) skill_info['fixed'] = TrueThe generated replacement is created by the following code at
scripts/audit_skills.py:310-322:python def auto_fix_colon(content: str) -> Optional[str]: """Fix unquoted colons in YAML description field.""" lines = content.split('\n') fixed = False for i, line in enumerate(lines): if line.strip().startswith('description:'): val = line.split('description:', 1)[1].strip() if ':' in val and not val.startswith('"') and not val.startswith("'"): lines[i] = f'description: "{val}"' fixed = True break return '\n'.join(lines) if fixed else NoneTechnical Analysis
The recursive d ...[truncated 2424 chars]
- Remediation
View remediation
Remediation Suggestions
-
Reject symbolic-link candidates before reading or writing:
python if sf.is_symlink(): # Report and skip the unsafe entry. continue -
Resolve both the scan root and each candidate, then enforce containment:
python root = Path(skills_dir).resolve() candidate = sf.resolve(strict=True) if not candidate.is_relative_to(root): # Report and skip the out-of-scope target. continue -
Revalidate containment and file identity immediately before writing to reduce time-of-check/time-of-use exposure.
-
Where supported, open the destination with no-follow semantics such as
O_NOFOLLOW, and verify withfstat()that it is a regular file. -
Write to a safely created temporary file in the same verified directory, flush and synchronize it, then atomically replace the original after repeating the safety checks.
-
Replace the regular-expression-based YAML transformation with a safe YAML parser and serializer. Ensure embedded quotes, line breaks, backslashes, and other YAML-special characters are escaped correctly.
-
Avoid running the audit or automatic-fix mode with elevated privileges. Consider making audit mode read-only by default and requiring explicit confirmation for each modified file.
-
