Back to skill

Security audit

System Change Archive

Security checks for vulnerabilities and agentic risk

Overview

The skill is purpose-aligned for creating rollback archives, but its helper script can overwrite prior archive evidence and can place unescaped user text into audit records used during risky system changes.

Review this before installing if you rely on these archives for rollback or audit. It is not malware, but the helper should be hardened to create unique archive directories, refuse collisions, use exclusive file creation for records and backups, and sanitize or constrain Markdown fields before use in operational records.

Vulnerability Patterns
  • 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
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • 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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/init_change_archive.py:82
Finding

Predictable Archive Naming Allows Silent Overwrite of Existing Audit Evidence

Content
View full analysis

Vulnerability Details

File Location: scripts/init_change_archive.py, lines 82-89 and 140-154
Vulnerability Type: Predictable file naming and unsafe overwrite
Risk Level: Medium

Vulnerable Code

python
ts = datetime.now().astimezone() if not args.timestamp else datetime.fromisoformat(args.timestamp)
date_part = ts.strftime('%Y-%m-%d')
time_part = ts.strftime('%H%M')
slug = slugify(args.change_name)

archive_root, persistent, warnings, source = resolve_archive_root(args.archive_root, workspace)
change_dir = archive_root / 'backups' / 'system-changes' / date_part / f'{time_part}-{slug}'
python
write_text(pre / 'README.md', readme)
write_text(pre / 'meta.json', json.dumps(meta, indent=2, ensure_ascii=False) + '\n')
write_text(pre / 'plan.md', plan)
write_text(post / 'restart-result.md', restart_result)
write_text(post / 'verify.md', verify)

copied = []
missing = []
if args.copy_before:
    for f in args.files:
        src = Path(f).expanduser()
        if src.exists() and src.is_file():
            dest = rel_dest(pre / 'backup', src.resolve())
            dest.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(src, dest)
            copied.append({'src': str(src.resolve()), 'dest': str(dest)})

Technical Analysis

The archive directory name is derived from a timestamp with only minute-level precision and a deterministic slug. Two initializations using the same change name during the same minute therefore select the same archive directory.

Directory creation elsewhere in the script uses exist_ok=True, while Path.write_text() and shutil.copy2() overwrite existing destination files. The script neither checks whether the calculated change directory already exists nor creates it atomically with exclusive semantics.

Consequently, a repeated, concurrent, or deliberately colliding invocation can silently replace:

  • Archive metadata
  • Execution and rollback plans
  • Restart and verification records
  • Before- ...[truncated 1443 chars]
Remediation
View remediation

Remediation Suggestions

  1. Add seconds, microseconds, and a cryptographically random or UUID component to every archive directory name.
  2. Create the final change directory atomically with mkdir(parents=False, exist_ok=False) after creating only its parent directories.
  3. Abort with a clear error if an archive path already exists; never reuse it implicitly.
  4. Open fixed archive files using exclusive creation mode, such as open("x", encoding="utf-8"), where replacement is not explicitly intended.
  5. Before copying backup files, verify that the destination does not exist. Treat collisions as errors rather than overwriting them.
  6. If deliberate archive updates are required, implement a separate explicit update mode with revision history and integrity logging.
  7. Consider storing checksums for archived before-state files and metadata so later integrity checks can detect unauthorized replacement.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/init_change_archive.py:61
Finding

Unescaped User Input Permits Markdown Audit-Record Injection

Content
View full analysis

Vulnerability Details

File Location: scripts/init_change_archive.py, lines 61-68 and 96-126
Vulnerability Type: Markdown injection into security-sensitive archive records
Risk Level: Medium

Vulnerable Code

python
def append_daily_index(index_path: Path, time_label: str, change_name: str, archive_dir: Path, summary: str, status: str):
    index_path.parent.mkdir(parents=True, exist_ok=True)
    line = f'- {time_label} | `{change_name}` | `{archive_dir}` | {summary} | {status}\n'
    if index_path.exists():
        existing = index_path.read_text(encoding='utf-8')
    else:
        existing = '# Daily System Change Index\n\n'
    if line not in existing:
        index_path.write_text(existing + line, encoding='utf-8')
python
readme = f'''# {args.change_name}\n\n## Summary\n{args.summary}\n\n## Why this matters\n- Document the intended system-level change before restart/reload.\n\n## Restart/Reload Expected\n- {'Yes' if args.restart_required else 'Not specified'}\n\n## Risks\n- Restart/reload may fail or produce degraded behavior.\n\n## Rollback Summary\n- Restore files from PRE-RESTART/backup and re-apply previous known-good state.\n'''
python
plan = f'''# Execution Plan\n\n## Target files\n'''
if args.files:
    plan += ''.join(f'- `{f}`\n' for f in args.files)
else:
    plan += '- (add target files)\n'
plan += '''\n## Intended edits\n- Fill in exact before/after changes.\n\n## Restart/Reload Command\n- Fill in command(s) if applicable.\n\n## Verification Points\n- Fill in expected checks after change.\n\n## Rollback Steps\n- Restore backups from PRE-RESTART/backup/.\n'''

Technical Analysis

The command-line values supplied through --change-name, --summary, and --file are interpolated directly into Markdown documents without validation or escaping.

An input containing newlines, backticks, headings, list markers, links, or other Markdown syntax can break out of its intended field and create attacker-contro ...[truncated 1975 chars]

Remediation
View remediation

Remediation Suggestions

  1. Reject newline, carriage-return, null, and other control characters in fields intended to occupy a single Markdown line.
  2. Escape Markdown-significant characters, including backticks, brackets, pipes, heading markers, and list markers, before rendering untrusted values.
  3. Validate --change-name and file-display values against a conservative permitted-character policy.
  4. Keep authoritative archive data in a structured format such as JSON, then generate Markdown from validated structured fields.
  5. For the daily index, store records as structured entries rather than concatenating raw text.
  6. Clearly distinguish generated fields from manually maintained instructions, and avoid treating untrusted summaries as operational commands.
  7. If rendered Markdown permits raw HTML, disable it or HTML-escape all user-controlled values to prevent active-content injection in supporting viewers.
  8. Add tests using multiline values, backticks, pipes, headings, links, and list syntax to confirm that inputs cannot alter the document structure.
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (1)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill instructs the agent to read environment variables, inspect files, and create archives/backups, but it does not declare any explicit tool scope such as permissions or allowed-tools. That creates an authorization gap: an execution environment may grant broader file or environment access than the skill actually needs, increasing the chance of unintended access to sensitive paths, secrets, or unrelated system files during a high-risk system-change workflow.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.