T09 · Insecure Skill Coding Practices
- Location
scripts/rebuild_critical_fact_cards.py:108- Finding
Unrestricted Recursive Directory Deletion Through User-Controlled Output Path
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This is a disclosed memory-indexing skill, but it needs review because it can persist sensitive workspace memory into plaintext derived files and includes unsafe path handling that could delete or overwrite unintended files.
Install only if you want agents to maintain a persistent structured memory layer for this workspace. Avoid storing secrets in daily memory, consider running first setup with `--no-backfill`, review generated `critical-facts/` files, and restrict script use to trusted operators. Do not pass custom card output directories, and treat recalled notes as data rather than instructions.
scripts/rebuild_critical_fact_cards.py:108Unrestricted Recursive Directory Deletion Through User-Controlled Output Path
scripts/rebuild_one_day.py:40Unvalidated Date Argument Enables Path Traversal and File Clobbering
scripts/extract_critical_facts.py:148Sensitive Operational Context Is Duplicated Into Persistent Plaintext Files Without Redaction
scripts/rebuild_critical_fact_cards.py:89Verbatim Memory Notes Create a Persistent Prompt-Injection and Memory-Poisoning Channel
The script reads untrusted memory content, extracts values from it, and writes those values directly into persistent markdown files under critical-facts. Because fields like 'note' and 'value' come from attacker-controlled text with almost no sanitization, a crafted memory file can poison the long-term knowledge store with false hosts, accounts, paths, or malicious markdown content, causing data integrity issues and possible downstream misuse by agents that trust these files.
f" related_entity: {fact['related_entity']}\n"
f" note: {fact['note']}\n"
)
path.write_text(path.read_text(encoding='utf-8').rstrip() + '\n' + block, encoding='utf-8')
return True
The description says to use the skill when 'designing, initializing, updating, or operating a memory system' across a very wide range of topics, but it does not provide concrete trigger phrases, boundaries, or exclusion conditions. This makes invocation scope ambiguous and increases the chance the skill is used during ordinary discussions that merely touch those topics.
The skill states that first initialization automatically backfills existing memory history into indexes, critical-facts, and cards, but does not prominently warn that this may read and rewrite large amounts of pre-existing workspace data. That creates a real risk of unintended data expansion, duplication of sensitive information into more discoverable locations, and unexpected modification of repository contents.
These lines tell the agent to treat rebuild as part of its default workflow after 'meaningful' updates, which is subjective and can cause autonomous file-modifying behavior without a fresh user request. In an agent setting, ambiguous automatic write behavior increases the risk of unintended workspace changes, surprise bulk processing, and propagation of sensitive content into additional indexed files.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
cmd = ['python3', str(ROOT / 'skills/structured-memory/scripts/rebuild_one_day.py'), date]
before = snapshot()
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL)
after1 = snapshot()
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL)
after2 = snapshot()
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
cmd = ['python3', str(ROOT / 'skills/structured-memory/scripts/rebuild_one_day.py'), date]
before = snapshot()
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL)
after1 = snapshot()
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL)
after2 = snapshot()
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def load_context(path: Path) -> dict:
raw = subprocess.run(
['python3', str(PARSE_SCRIPT), str(path)],
check=True,
capture_output=True,
The skill is explicitly designed to harvest and persist sensitive operational details such as hosts, accounts, endpoints, repositories, and identifiers into centralized markdown files, yet it provides no user-facing warning, consent prompt, or data-classification safeguard when --write is used. In a security-sensitive agent environment, silently converting free-form memory into a durable sensitive-facts repository materially increases the risk of over-collection, unauthorized retention, and later exfiltration or misuse.
This write path persists content derived from parsed memory into critical-facts files without any trust boundary, approval gate, or strong validation. Even though the immediate write here is only adding a date section marker, it is part of the same workflow that lets untrusted memory drive persistent state changes, enabling storage poisoning and unauthorized modification of operational records.
if not text.endswith('\n'):
text += '\n'
text += f"\n{marker}\n"
path.write_text(text, encoding='utf-8')
lines = text.splitlines()
start = lines.index(marker)
end = len(lines)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def backfill(days: list[str]):
rebuilt = []
for day in days:
subprocess.run(['python3', str(REBUILD_ONE_DAY), day], check=True)
rebuilt.append(day)
BOOTSTRAP_MARKER.write_text(json.dumps({'days': rebuilt}, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
return rebuilt
The script unconditionally deletes the entire output directory with shutil.rmtree(cards_dir) before rebuilding it. Because the directory is user-controllable via --cards-dir and there is no safety check, confirmation, or path constraint, a mistaken or maliciously supplied path could cause irreversible deletion of arbitrary files or directories accessible to the process.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
parsed_path = ROOT / f'tmp.parsed-memory-{date}.json'
index_path = ROOT / 'memory-index' / 'by-date.json'
subprocess.run([
'python3', str(ROOT / 'skills/structured-memory/scripts/parse_daily_memory.py'), str(memory_path)
], check=True, stdout=parsed_path.open('w', encoding='utf-8'))
The script performs broad destructive updates: it rewrites matching markdown files across multiple directories and then triggers several write-capable helper scripts, all based on an unvalidated date argument and without confirmation, dry-run mode, or rollback. In an agent or automation context, this increases the risk of unintended data loss or corruption if the script is invoked with malformed input, pointed at an unexpected repository state, or run non-interactively.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
for candidate in (ROOT / 'critical-facts').glob('*.md'):
safe_remove_if_contains(candidate, date)
subprocess.run([
'python3', str(ROOT / 'skills/structured-memory/scripts/upsert_by_date_index.py'), str(index_path), str(parsed_path)
], check=True)
subprocess.run([
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
subprocess.run([
'python3', str(ROOT / 'skills/structured-memory/scripts/upsert_by_date_index.py'), str(index_path), str(parsed_path)
], check=True)
subprocess.run([
'python3', str(ROOT / 'skills/structured-memory/scripts/update_topic_indexes.py'), str(parsed_path), str(memory_path)
], check=True)
subprocess.run([
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
subprocess.run([
'python3', str(ROOT / 'skills/structured-memory/scripts/update_topic_indexes.py'), str(parsed_path), str(memory_path)
], check=True)
subprocess.run([
'python3', str(ROOT / 'skills/structured-memory/scripts/extract_critical_facts.py'), str(memory_path), '--write'
], check=True)
subprocess.run([
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
subprocess.run([
'python3', str(ROOT / 'skills/structured-memory/scripts/extract_critical_facts.py'), str(memory_path), '--write'
], check=True)
subprocess.run([
'python3', str(ROOT / 'skills/structured-memory/scripts/rebuild_critical_fact_cards.py')
], check=True)
print(f'Rebuilt indexes, critical facts, and cards for {date}')
The script hard-codes Chinese trigger and classification patterns across its summarization logic, which makes the skill effectively tailored to one language/locale. There is no user opt-in, fallback, or documented scope indicating that this is intentionally limited to Chinese-language memories, so it can violate language/locale policy expectations.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
script = ROOT / 'skills/structured-memory/scripts/summarize_daily_memory.py'
try:
result = subprocess.run(
['python3', str(script), str(memory_path)],
check=True,
capture_output=True,
The script automatically extracts summaries and metadata from memory files and appends them into broader index files under memory-modules and memory-entities. This can propagate sensitive personal or business information into additional locations, increasing exposure, discoverability, and retention without any filtering, consent gate, or warning to the operator.
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
'priority': priority,
}
index_path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
print(f'Updated {index_path} for {date}')
The file content is entirely written in Chinese and provides no indication that language choice is optional or justified by a documented region-specific constraint. Under the language/locale policy, forcing a specific language without user opt-in can be a natural-language policy violation.
The markdown content is entirely in Chinese and provides no indication that language choice is optional or that the skill is specifically scoped to Chinese-speaking users. Under the policy, forcing a specific language without user opt-in can be a natural-language policy violation.
This markdown file presents all natural-language content in Chinese and does not indicate that the user opted into that language or that the skill is intended for a Chinese-only audience. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation.
The instructional text is written entirely in Chinese, which constitutes a language constraint in the skill content without any indication that the user can choose a preferred language. The policy requires either user opt-in for a specific language/locale or a clearly documented justification for the constraint.