T09 · Insecure Skill Coding Practices
- Location
scripts/auto_link.py:117- Finding
Shell Command Injection Through Generated Repair Script
- Content
View full analysis
Vulnerability Details
File Location:
scripts/auto_link.py, lines 13–15 and 117–123
Vulnerability Type: Shell command injection through unsafe script generation
Risk Level: MediumThe application inserts untrusted Markdown link content directly into a generated Bash script without rejecting line breaks or other control characters.
Source of untrusted content (
scripts/auto_link.py, lines 13–15):python def extract_links(content): """提取 [[双向链接]] 中的笔记名""" return re.findall(r'\[\[([^\]]+)\]\]', content)Vulnerable script-generation operation (
scripts/auto_link.py, lines 117–123):python # 生成修复建议(dry_run=false 时输出可执行脚本) if not dry_run and (dead_links or orphans): fix_script = vault / ".claude_fix_suggestions.sh" with open(fix_script, 'w', encoding='utf-8') as f: f.write("#!/bin/bash\n# Claude-Obsidian 修复建议(手动确认后执行)\n\n") for dl in dead_links: f.write(f"# 在 {dl['file'].relative_to(vault)} 中将 [[{dl['link']}]] 替换为 [[{dl['suggestion']}]]\n")Technical Analysis
extract_links()obtains link text directly from Markdown files inside the user-selected vault. Its regular expression uses the character class[^\]]+, which excludes closing square brackets but does not exclude newline or carriage-return characters.The extracted value is subsequently stored in
dl['link']and interpolated into.claude_fix_suggestions.sh. Although the generated content is intended to be a shell comment, an embedded newline can terminate the comment. The next line is then interpreted as a shell command if the generated file is executed.The derived
dl['suggestion']value is also unsafe because operations such asstrip(),replace(), andlower()do not remove embedded line breaks.Attack Path
- An attacker supplies, shares, or causes the user to import a Markdown file into the scanned vault.
- The Markdown file contains a crafte ...[truncated 1404 chars]
- Remediation
View remediation
Remediation Suggestions
-
Do not generate executable shell files from vault content. Generate a non-executable Markdown, JSON, or plain-text report instead. This is the preferred remediation because the file currently contains only comments and does not require shell semantics.
-
Reject control characters in all untrusted fields. Validate extracted links and relative paths before using them in generated output:
python def validate_single_line(value): if any(ch in value for ch in ("\n", "\r", "\x00")): raise ValueError("Link contains prohibited control characters") return value -
Make report-only behavior the default. Require an explicit option to generate any executable artifact rather than generating
.claude_fix_suggestions.shwhenever--dry-runis absent. -
Use structured repair data. Store the source file, original link, and suggested replacement as JSON fields. A separate trusted tool can consume that data without invoking a shell.
-
If shell generation is unavoidable, avoid embedding data in comments or command text. Pass validated values as arguments to a fixed program and quote each value with a proven shell-escaping function such as
shlex.quote(). Shell quoting alone should supplement, not replace, rejection of newline and control characters. -
Apply safe file permissions. Create generated reports without executable permission and avoid a
.shextension unless executable behavior is genuinely required. -
Add regression tests covering links with newline characters, carriage returns, shell metacharacters, command substitutions, backticks, quotes, and crafted filenames. Verify that these inputs are rejected or represented only in a non-executable structured format.
-
