T09 · Insecure Skill Coding Practices
- Location
scripts/init-novel.sh:32- Finding
Unescaped User Input Can Corrupt or Inject Fields into the JSON Manifest
- Content
View full analysis
Vulnerability Details
File Location:
scripts/init-novel.sh:32-42, 160;assets/init/manifest.json:1-8
Vulnerability Type: Improper escaping during JSON generation
Risk Level: LowVulnerable Code
bash get_template_content() { local template_name="$1" local template_path="$INIT_TEMPLATE_DIR/$template_name" local content content="$(cat "$template_path")" content="${content//\{\{NOVEL_NAME\}\}/$NOVEL_NAME}" content="${content//\{\{MODE\}\}/$MODE}" content="${content//\{\{CREATED_AT\}\}/$CREATED_AT}" printf '%s' "$content" }bash write_markdown_file "$MANIFEST_PATH" "$(get_template_content "manifest.json")"The affected template is:
json { "novel_name": "{{NOVEL_NAME}}", "init_mode": "{{MODE}}", "created_at": "{{CREATED_AT}}", "current_chapter": 0, "current_arc": "", "status": "active" }Technical Analysis
The initialization script accepts the novel name as user-controlled command-line input and replaces
{{NOVEL_NAME}}in a JSON template through raw Bash string substitution. The value is not encoded according to JSON string rules.A novel name containing a double quote, backslash, control character, or newline can therefore produce malformed JSON. A deliberately crafted value can also terminate the original string and introduce additional JSON properties. Shell command injection is not present because the relevant variables are quoted and are not evaluated as commands; the issue is confined to generated-data integrity.
The
MODEvalue is restricted to two known strings, and the timestamp is generated internally, so the practical attacker-controlled input isNOVEL_NAME.Attack Path
- An attacker or untrusted caller invokes
scripts/init-novel.shwith a crafted novel name containing JSON syntax. - The script stores that argument in
NOVEL_NAME. get_template_contentsubstitutes the value verbatim intoassets/init/manifest.json.- The resulting content is written ...[truncated 1125 chars]
- An attacker or untrusted caller invokes
- Remediation
View remediation
Remediation Suggestions
Generate JSON with a JSON-aware serializer rather than textual substitution. For example, use Python:
bash python3 - "$MANIFEST_PATH" "$NOVEL_NAME" "$MODE" "$CREATED_AT" <<'PY' import json import sys path, novel_name, mode, created_at = sys.argv[1:] manifest = { "novel_name": novel_name, "init_mode": mode, "created_at": created_at, "current_chapter": 0, "current_arc": "", "status": "active", } with open(path, "w", encoding="utf-8") as handle: json.dump(manifest, handle, ensure_ascii=False, indent=2) handle.write("\n") PYAlternatively, use
jq --argif adding that dependency is acceptable. Do not attempt to maintain JSON escaping through ad hoc shell replacements.Additional hardening should include:
- Validate the generated manifest immediately with a JSON parser.
- Write to a temporary file in the target directory and atomically rename it after successful validation.
- Add regression tests for names containing double quotes, backslashes, newlines, tabs, Unicode characters, and JSON-like fragments.
- Keep the
MODEallowlist and quoted shell expansions already present. - Ensure downstream consumers validate the manifest schema and reject unexpected fields or invalid field types.
