Back to skill

Security audit

Structured Memory

Security checks for vulnerabilities and agentic risk

Overview

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.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/rebuild_critical_fact_cards.py:108
Finding
Unrestricted Recursive Directory Deletion Through User-Controlled Output Path<![CDATA[ ## Vulnerability Details **File Location**: `scripts/rebuild_critical_fact_cards.py:108-116` **Vulnerability Type**: Arbitrary recursive directory deletion **Risk Level**: High ### Vulnerable Code ```python parser = argparse.ArgumentParser(description='Rebuild critical-fact object cards from critical-facts/*.md with tolerant parsing.') parser.add_argument('--critical-facts-dir', default=str(DEFAULT_CRITICAL_FACTS_DIR), help='Directory containing critical-facts markdown files') parser.add_argument('--cards-dir', default=str(DEFAULT_CARDS_DIR), help='Output directory for rebuilt cards') args = parser.parse_args() critical_facts_dir = Path(args.critical_facts_dir) cards_dir = Path(args.cards_dir) if cards_dir.exists(): shutil.rmtree(cards_dir) ``` ### Technical Analysis The `--cards-dir` command-line argument accepts an arbitrary filesystem path. The supplied path is passed directly to `shutil.rmtree()` without canonicalization, workspace confinement, symlink protection, or validation that it refers to the intended `critical-facts/cards` directory. `shutil.rmtree()` recursively deletes the target and all of its contents. Consequently, any process or Agent capable of influencing the command arguments can convert this maintenance utility into a destructive filesystem operation. The vulnerability does not require shell metacharacters because the dangerous behavior is implemented directly by the Python process. The default invocation uses the expected cards directory, but the exposed unrestricted option makes the script unsafe when invoked directly or through an Agent-generated command. ### Attack Path 1. An attacker causes an Agent, automation process, or operator to invoke `rebuild_critical_fact_cards.py`. 2. The attacker influences the `--cards-dir` argument, for example by supplying a path to another workspace directory. 3. The script converts the supplied string into a `Path` without checking its resolved location. 4. If the selected dire ...[truncated 872 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the configurable `--cards-dir` option unless custom output directories are required. 2. Resolve and validate the path before deletion: - Call `Path.resolve(strict=False)`. - Require the resolved path to be a strict descendant of the expected `critical-facts` directory. - Reject the filesystem root, user home, workspace root, input directory, and all parent directories. 3. Reject symlinks and verify every relevant path component before deletion. 4. Refuse recursive deletion when the path differs from the expected default unless an explicit administrative confirmation flag is present. 5. Prefer rebuilding into a newly created temporary directory and atomically replacing the old cards directory. 6. Add regression tests for absolute paths, `..` traversal, symlink targets, the workspace root, the home directory, and the filesystem root. 7. Run the Skill under an account with write access limited to its intended workspace. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/rebuild_one_day.py:40
Finding
Unvalidated Date Argument Enables Path Traversal and File Clobbering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/rebuild_one_day.py:40-53` **Vulnerability Type**: Path traversal and unsafe temporary-file construction **Risk Level**: High ### Vulnerable Code ```python if len(sys.argv) != 2 or sys.argv[1] in {'-h', '--help'}: print('Usage: rebuild_one_day.py <YYYY-MM-DD>') print('Example: rebuild_one_day.py 2026-03-10') raise SystemExit(0 if len(sys.argv) == 2 else 1) date = sys.argv[1] memory_path = ROOT / 'memory' / f'{date}.md' 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')) parsed = json.loads(parsed_path.read_text(encoding='utf-8')) ``` ### Technical Analysis Although the interface documents the argument as `YYYY-MM-DD`, the script does not validate its syntax or semantic validity. The raw value is embedded in both an input path and an output path: - `ROOT / 'memory' / f'{date}.md'` - `ROOT / f'tmp.parsed-memory-{date}.json'` A value containing path separators and traversal components can alter the resolved location. The output path is opened in write mode before the parser subprocess runs, so an existing writable file at the constructed location is truncated immediately. The use of a subprocess argument list prevents shell command injection, but it does not prevent filesystem path traversal. The predictable `tmp.parsed-memory-*.json` naming also introduces unsafe temporary-file behavior and possible symlink-based clobbering in a hostile or shared workspace. ### Attack Path 1. An attacker influences the date passed to `rebuild_one_day.py`. 2. The attacker supplies a value containing path separators or traversal elements rather than a valid ISO date. 3. The application constructs `memory_path` and `parsed_path` using the untrusted value. 4. Path res ...[truncated 994 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce the exact format `^\d{4}-\d{2}-\d{2}$`. 2. Validate semantic correctness with `datetime.date.fromisoformat()`. 3. Resolve the generated input path and verify that its parent is exactly the resolved workspace `memory` directory. 4. Do not derive temporary-file names directly from untrusted input. 5. Use `tempfile.NamedTemporaryFile()` or `TemporaryDirectory()` in a trusted directory. 6. Write generated output to a fresh temporary file and atomically replace the destination only after parsing and validation succeed. 7. Reject symlinked source or destination files when symlinks are not explicitly supported. 8. Apply the same date validation in `check_idempotency.py`, initialization backfill logic, and all downstream scripts. 9. Add tests for traversal strings, absolute paths, malformed dates, invalid calendar dates, symlinks, and pre-existing temporary files. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/extract_critical_facts.py:148
Finding
Sensitive Operational Context Is Duplicated Into Persistent Plaintext Files Without Redaction<![CDATA[ ## Vulnerability Details **File Location**: `scripts/extract_critical_facts.py:148-163` **Vulnerability Type**: Plaintext sensitive-data persistence **Risk Level**: Medium ### Vulnerable Code ```python def make_fact(date: str, fact_type: str, value: str, line: str, context: dict) -> dict: value = value.strip('`"\'.,)】]') return { 'entity': value, 'fact_type': fact_type, 'value': value, 'status': 'active', 'sensitivity': sensitivity_for(fact_type, value), 'source': f'memory/{date}.md', 'last_verified': date, 'domains': context.get('domains', []), 'modules': context.get('modules', []), 'tags': context.get('system_tags', []) + context.get('free_tags', []), 'related_project': infer_related_project(context), 'related_entity': infer_related_entity(line, value, fact_type, context), 'note': line.strip(), } ``` The resulting record is written without redaction: ```python block = ( f"- entity: {fact['entity']}\n" f" fact_type: {fact['fact_type']}\n" f" value: {fact['value']}\n" f" status: {fact['status']}\n" f" sensitivity: {fact['sensitivity']}\n" f" source: {fact['source']}\n" f" last_verified: {fact['last_verified']}\n" f" domains: {fact['domains']}\n" f" modules: {fact['modules']}\n" f" tags: {fact['tags']}\n" f" related_project: {fact['related_project']}\n" 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') ``` Accounts are explicitly directed to a plaintext credentials file: ```python mapping = { 'host': CRITICAL_FACTS_DIR / 'hosts.md', 'account': CRITICAL_FACTS_DIR / 'credentials.md', 'path': CRITICAL_FACTS_DIR / 'locations.md', 'repo': CRITICAL_FACTS_DIR / 'locations.md', 'service': CRITICAL_FACTS_DIR / 'services.md', 'endpoint': C ...[truncated 2403 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not persist the complete source line in the `note` field. 2. Store only the minimum typed value required for future execution. 3. Add redaction for passwords, API keys, tokens, authorization headers, private keys, session cookies, signed URLs, database connection strings, and common cloud credentials. 4. If contextual prose is necessary, generate a sanitized summary after removing sensitive substrings. 5. Treat high- and critical-sensitivity records differently: - Require explicit user consent. - Encrypt them at rest or store only a secure-secret-manager reference. - Apply restrictive file permissions. 6. Make historical backfill opt-in when sensitive extraction is enabled, and display the exact files and data categories that will be processed. 7. Prevent cards from duplicating sensitive notes. 8. Add retention, deletion, and supersession handling so removed source data does not remain indefinitely in derived files. 9. Add tests where recognized identifiers appear beside passwords, bearer tokens, API keys, and signed query parameters. ]]>

T02 · Agent Memory Poisoning

Warning
Location
scripts/rebuild_critical_fact_cards.py:89
Finding
Verbatim Memory Notes Create a Persistent Prompt-Injection and Memory-Poisoning Channel<![CDATA[ ## Vulnerability Details **File Location**: `scripts/rebuild_critical_fact_cards.py:89-103` **Vulnerability Type**: Persistent Agent memory poisoning **Risk Level**: Medium ### Vulnerable Code ```python for date in sorted(by_date.keys(), reverse=True): lines.append(f'## {date}') for fact in by_date[date]: lines.append(f"- {fact.get('fact_type')}: {fact.get('value')}") if fact.get('sensitivity'): lines.append(f" - sensitivity: {fact.get('sensitivity')}") if fact.get('source'): lines.append(f" - source: {fact.get('source')}") if fact.get('note'): lines.append(f" - note: {fact.get('note')}") lines.append('') return '\n'.join(lines).rstrip() + '\n' ``` The untrusted note originates from the daily-memory line: ```python def make_fact(date: str, fact_type: str, value: str, line: str, context: dict) -> dict: value = value.strip('`"\'.,)】]') return { 'entity': value, 'fact_type': fact_type, 'value': value, 'status': 'active', 'sensitivity': sensitivity_for(fact_type, value), 'source': f'memory/{date}.md', 'last_verified': date, 'domains': context.get('domains', []), 'modules': context.get('modules', []), 'tags': context.get('system_tags', []) + context.get('free_tags', []), 'related_project': infer_related_project(context), 'related_entity': infer_related_entity(line, value, fact_type, context), 'note': line.strip(), } ``` ### Technical Analysis Daily-memory content is potentially influenced by external messages, documents, tool output, or other untrusted sources. When a line contains a recognized host, URL, path, username, service, repository, or identifier, the extractor stores the complete line as a note. Card rebuilding then preserves that text verbatim. The Skill instructs Agents to prioritize `critical-facts/cards/` and raw critical-fact files when r ...[truncated 2022 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat all daily-memory and derived-note content as untrusted data. 2. Do not copy complete source lines into persistent fact cards. 3. Store only schema-validated fields such as fact type, normalized value, source, and verification date. 4. Strip or quarantine imperative phrases, tool directives, role instructions, and attempts to alter Agent behavior. 5. Add explicit retrieval wrappers stating that recalled records are data and must never override system, developer, user, or current-task instructions. 6. Separate operational facts from standing rules. Promote rules only through an explicit trusted approval workflow. 7. Track provenance and trust level for every fact, including whether it originated from a user, external document, tool output, or inferred summary. 8. Require confirmation before acting on sensitive or high-impact operational facts retrieved solely from memory. 9. Add regression tests with instruction-bearing URLs, paths, usernames, IDs, and service records to verify that directives are removed or safely delimited. 10. Provide tooling to identify and purge poisoned records from raw facts, cards, indexes, and historical backfill results. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (149)

Direct flow: pathlib.Path.read_text (file read) → pathlib.Path.write_text (file write)

High
Category
Data Flow
Content
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
Confidence
94% confidence
Finding
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.

Vague Triggers

Medium
Confidence
92% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
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.

Vague Triggers

Medium
Confidence
90% confidence
Finding
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 call

Medium
Category
Dangerous Code Execution
Content
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()
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
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()
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def load_context(path: Path) -> dict:
    raw = subprocess.run(
        ['python3', str(PARSE_SCRIPT), str(path)],
        check=True,
        capture_output=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
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.

Tainted flow: 'text' from pathlib.Path.read_text (line 260, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
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)
Confidence
89% confidence
Finding
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.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
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
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
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 call

Medium
Category
Dangerous Code Execution
Content
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'))
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
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 call

Medium
Category
Dangerous Code Execution
Content
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([
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
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([
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
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([
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
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}')
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
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 call

Medium
Category
Dangerous Code Execution
Content
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,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
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.

Tainted flow: 'data' from pathlib.Path.read_text (line 64, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
'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}')
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
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.

Natural-Language Policy Violations

Medium
Confidence
86% confidence
Finding
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.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
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.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
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.

Static analysis

No suspicious patterns detected.