Back to skill

Security audit

digital-legacy

Security checks for vulnerabilities and agentic risk

Overview

This is a legitimate digital-estate planner, but it under-protects very sensitive account and crypto-recovery information and may not provide the AES-GCM encryption users are told to expect.

Review this skill carefully before installing. Use it only in a private, secured workspace; do not put passwords, seed phrases, PINs, recovery codes, private keys, or security-question answers in `accounts.json` or the emergency guide. Treat `accounts.json`, `digital_will.enc`, and `emergency_guide.html` as highly sensitive, restrict file permissions, and do not rely on the advertised AES-GCM protection unless `cryptography` is installed and the fallback behavior is removed or clearly disabled.

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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/digital_legacy.py:152
Finding
Nonstandard Encryption Fallback Contradicts the Advertised AES-256-GCM Security Model<![CDATA[ ## Vulnerability Details **File Location**: `scripts/digital_legacy.py:152-192` **Vulnerability Type**: Custom cryptographic construction and misleading algorithm reporting **Risk Level**: High ### Vulnerable Code ```python def encrypt(data: str, passphrase: str) -> bytes: """Encrypt a string with AES-256-GCM or stdlib fallback. Uses the `cryptography` library if available (proper AES-GCM). Falls back to a stdlib XOR-stream cipher with HMAC authentication if not. The fallback is less secure but functional for personal use. """ plaintext = data.encode('utf-8') salt = secrets.token_bytes(SALT_LENGTH) nonce = secrets.token_bytes(NONCE_LENGTH) key = derive_key(passphrase, salt) try: from cryptography.hazmat.primitives.ciphers.aead import AESGCM aesgcm = AESGCM(key) ciphertext = aesgcm.encrypt(nonce, plaintext, None) return salt + nonce + ciphertext except ImportError: # Fallback: XOR-stream with HMAC-SHA256 authentication return _encrypt_fallback(plaintext, key, salt, nonce) def _encrypt_fallback(plaintext: bytes, key: bytes, salt: bytes, nonce: bytes) -> bytes: """Stdlib-only encryption: XOR keystream + HMAC tag.""" import hmac # Generate keystream from key + nonce (PBKDF2 as PRNG) keystream = b'' counter = 0 while len(keystream) < len(plaintext): block = hashlib.sha256(key + nonce + counter.to_bytes(4, 'big')).digest() keystream += block counter += 1 # XOR encrypt ciphertext = bytes(a ^ b for a, b in zip(plaintext, keystream)) # HMAC authentication tag tag = hmac.new(key, salt + nonce + ciphertext, hashlib.sha256).digest()[:TAG_LENGTH] return salt + nonce + ciphertext + tag ``` ### Technical Analysis The application uses AES-256-GCM only when the optional `cryptography` package is installed. Otherwise, it silently falls back to a custom construction consisting of: 1. A SHA-256-based k ...[truncated 2188 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the custom XOR-based fallback entirely. 2. Require a maintained authenticated-encryption implementation such as `cryptography`'s `AESGCM` or `ChaCha20Poly1305`. 3. Fail closed with a clear installation error if the required cryptographic dependency is unavailable. 4. Add a versioned file header containing: - Format version - Encryption algorithm - KDF algorithm and parameters - Salt and nonce lengths 5. Derive distinct encryption and authentication keys if a composition is ever necessary, although a standard AEAD construction is strongly preferred. 6. Update all documentation and command output so it accurately identifies the algorithm actually used. 7. Add known-answer, tampering, wrong-passphrase, and cross-environment compatibility tests. 8. Consider a migration command that decrypts legacy fallback files and re-encrypts them using the standardized format. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/digital_legacy.py:253
Finding
Sensitive Digital-Estate Inventory Is Written to Plaintext Files Without Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/digital_legacy.py:253-260` **Vulnerability Type**: Plaintext storage of sensitive information with inherited filesystem permissions **Risk Level**: Medium ### Vulnerable Code ```python def save_inventory(plan: LegacyPlan, path: Path = DEFAULT_INVENTORY_PATH) -> None: """Save inventory to JSON file.""" plan.updated = datetime.now().strftime("%Y-%m-%d") path.write_text( json.dumps(plan.to_dict(), indent=2, ensure_ascii=False), encoding='utf-8', ) ``` Related unrestricted sensitive-data prompts include: ```python account.username = ask("Username or email (NOT password)") account.access_method = ask("Access method (password manager, 2FA, etc.)") account.action = ask("Action (archive/memorialize/delete/transfer/maintain)", "archive") account.notes = ask("Notes (optional)") ``` ```python wallet.access_method = ask( "Access method (seed phrase LOCATION — do NOT enter the phrase itself)" ) wallet.approximate_value = ask("Approximate value (e.g. ~$5000)") wallet.notes = ask("Notes (optional)") ``` ### Technical Analysis The inventory is serialized directly to `accounts.json` as plaintext. The file can contain: - Account usernames and email addresses - Financial and service-provider names - Password-manager and 2FA access methods - Cryptocurrency wallet types, locations, and approximate values - Important local-file locations - Legacy contacts - Unrestricted notes The application does not explicitly create the file with owner-only permissions such as `0600`. Its effective permissions therefore depend on the process umask and surrounding filesystem configuration. On permissively configured or shared systems, other local users may be able to read it. Although prompts warn against entering passwords and seed phrases, free-text fields are not validated. Project documentation also encourages users to document password locations, recovery information, device credentials, ...[truncated 1426 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat `accounts.json` as sensitive rather than harmless metadata. 2. Create inventory files atomically with owner-only permissions, preferably mode `0600`. 3. Verify and warn about unsafe existing-file permissions before loading or overwriting the inventory. 4. Offer encryption for the inventory itself, especially for usernames, recovery methods, wallet locations, and unrestricted notes. 5. Add explicit warnings to every free-text prompt prohibiting passwords, seed phrases, PINs, recovery codes, private keys, and security-question answers. 6. Consider structured fields with validation instead of unrestricted notes. 7. Minimize collected data to what is strictly necessary. 8. Update documentation to explain that metadata can facilitate targeted attacks and must be protected. 9. Avoid placing the plaintext inventory next to the encrypted will unless the storage location has equivalent access controls. 10. Document safe backup and cloud-synchronization requirements. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/digital_legacy.py:282
Finding
Weak Passphrases Are Permitted for High-Value Encrypted Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/digital_legacy.py:282-297` **Vulnerability Type**: Insufficient passphrase policy and offline-guessing resistance **Risk Level**: Medium ### Vulnerable Code ```python def ask_passphrase(confirm: bool = True) -> str: """Securely prompt for a passphrase.""" while True: pw = getpass.getpass("Enter passphrase: ") if len(pw) < 8: print("Warning: passphrase is short (< 8 chars). Consider a stronger one.") cont = input("Continue anyway? (y/n) [n]: ").strip().lower() if cont != 'y': continue if confirm: pw2 = getpass.getpass("Confirm passphrase: ") if pw != pw2: print("Passphrases don't match. Try again.") continue return pw ``` The associated fixed KDF setting is: ```python PBKDF2_ITERATIONS = 100_000 ``` ### Technical Analysis The application only warns when a passphrase is shorter than eight characters and explicitly allows the user to continue. It therefore accepts extremely weak passphrases, including short dictionary words and common passwords. The encrypted file contains its salt and all information necessary to verify candidate passphrases through the authentication tag. An attacker who obtains the file can consequently perform unlimited offline guesses without account lockout, rate limiting, or detection. PBKDF2-HMAC-SHA256 with 100,000 fixed iterations adds computational cost, but it is not memory-hard and may be insufficient against modern parallel cracking hardware when users select weak passphrases. The KDF cost is not calibrated to the host and cannot be upgraded on a per-file basis because the parameters are not encoded in a versioned header. ### Attack Path 1. The victim chooses a short, common, reused, or otherwise predictable passphrase. 2. The program warns the victim but allows the weak choice after a single confirmation. 3. An attac ...[truncated 797 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce a strong minimum instead of allowing users to bypass the warning. 2. Prefer a minimum passphrase length of at least 14–16 characters while encouraging multiple randomly selected words or password-manager-generated values. 3. Reject common, compromised, and highly predictable passwords. 4. Use a memory-hard password KDF such as Argon2id or scrypt with parameters calibrated to the target system. 5. Store the KDF name, salt, and cost parameters in a versioned encrypted-file header. 6. Provide a secure passphrase generator or integrate guidance for password-manager generation. 7. Warn users against reused passphrases. 8. Add automated tests confirming rejection of empty, short, and commonly compromised values. 9. Provide a migration process for re-encrypting existing files with stronger KDF settings. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/digital_legacy.py:402
Finding
Unescaped Inventory Values Permit HTML Injection in the Emergency Guide<![CDATA[ ## Vulnerability Details **File Location**: `scripts/digital_legacy.py:402-418` **Vulnerability Type**: HTML and script injection through unsafe template substitution **Risk Level**: Low ### Vulnerable Code ```python replacements = { "{owner_name}": plan.owner_name or "[Your Name]", "{trusted_person}": plan.trusted_person or "[Trusted Person]", "{date}": now, "{will_location}": "~/Documents/DigitalLegacy/digital_will.enc (see notes)", "{script_source}": "https://github.com/voronindenis5/digital-legacy", "{passphrase_hint}": "[Add a hint that only your trusted person would understand]", "{lawyer_contact}": "[Add name and phone]", "{advisor_contact}": "[Add name and phone]", "{tech_contact}": "[Add name and phone]", "{medical_contact}": "[Add doctor name and phone]", "{version}": VERSION, } html = template for placeholder, value in replacements.items(): html = html.replace(placeholder, value) output.write_text(html, encoding='utf-8') ``` ### Technical Analysis Values loaded from `accounts.json`, including `owner_name` and `trusted_person`, are inserted directly into an HTML template through string replacement. No HTML escaping or sanitization is applied. A value containing HTML elements, event handlers, or script content is therefore interpreted as active markup when the generated emergency guide is opened in a browser. The issue can be introduced through direct editing of `accounts.json`, another process with write access to that file, or deliberately crafted interactive input. Browser protections may restrict some operations for local `file://` documents, but injected content can still alter emergency instructions, display fraudulent contact information, create deceptive links, and potentially execute JavaScript subject to the browser's local-file security model. ### Attack Path 1. An attacker gains write access to `accounts.json` or supplies a crafted owner or trusted-person value during initia ...[truncated 951 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape every dynamic value using `html.escape(value, quote=True)` before inserting it into HTML. 2. Prefer a template engine with automatic HTML escaping enabled. 3. Keep dynamic values in text contexts and avoid inserting them into raw HTML, attributes, scripts, or style blocks. 4. Validate names and other structured values against reasonable length and character constraints. 5. Treat `accounts.json` as untrusted input because it can be manually edited. 6. Add regression tests using payloads containing `<script>`, event handlers, quotes, ampersands, and closing tags. 7. Consider adding a restrictive Content Security Policy meta tag to generated guides as defense in depth. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (9)

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The README promotes generating a printable emergency access guide for trusted family members but does not clearly warn that printed or insecurely stored guides can expose sensitive recovery information or materially aid access to the encrypted will. In a digital-legacy skill, users may be especially likely to create and physically store such guides, so omission of handling guidance increases the chance of accidental disclosure or misuse by anyone who finds the document.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill advertises commands that read and write local files (`accounts.json`, `digital_will.enc`, `emergency_guide.html`) but does not declare any `permissions` or `allowed-tools` scope. That mismatch weakens least-privilege controls and can cause an agent or reviewer to underestimate the skill's filesystem access, which is especially sensitive here because the skill handles account inventories, crypto wallet details, and encrypted will artifacts.

Intent-Code Divergence

Medium
Confidence
92% confidence
Finding
The document presents AES-256-GCM as the script's encryption scheme while also stating that the script may fall back to a stdlib-only XOR-based stream cipher. This creates a misleading security guarantee: users handling highly sensitive digital legacy data may believe they are getting authenticated encryption when they may instead receive much weaker protection without integrity guarantees.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The guidance acknowledges a weaker XOR-based fallback but does not clearly warn that it is substantially less secure than AES-GCM and unsuitable for protecting sensitive assets like account inventories, recovery instructions, and crypto wallet information. In the context of a digital legacy skill, users may store extremely high-value secrets, so under-warning insecure encryption can directly lead to confidentiality compromise and possible tampering.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The guide explicitly tells users to gather device passcodes, email access, and open their password manager during a long setup session without a prominent warning to avoid recording, pasting, or exposing those secrets in notes, generated artifacts, or to anyone assisting. In a digital-legacy workflow, this concentration of high-value credentials increases the chance of accidental disclosure, shoulder-surfing, unsafe storage, or inclusion in documents that may later be shared with a trusted person or stored insecurely.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script stores the full legacy inventory in plaintext JSON on disk, even though that inventory contains highly sensitive metadata such as account names, usernames, wallet access locations, important file locations, and trusted-contact details. In the context of a digital inheritance planner, this materially increases risk because local compromise, backup leakage, accidental sharing, or multi-user system access could expose a roadmap to valuable accounts and crypto assets before the encrypted will is ever used.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
try again), but guessing wastes time.</li>
    <li>❌ Do <strong>not</strong> delete accounts immediately. Some data (photos,
        messages) may be irreplaceable. Check the will first.</li>
    <li>❌ Do <strong>not</strong> share the passphrase with anyone else without checking
        with my lawyer first.</li>
    <li>❌ Do <strong>not</strong> announce my death on social media until family has been
        personally notified.</li>
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Intent-Code Divergence

Low
Confidence
98% confidence
Finding
The documentation says the script uses 'Python stdlib only' but then says it will use the external `cryptography` library when available. This inconsistency can mislead operators about the actual dependency and security model, increasing the chance they deploy or trust the tool under false assumptions.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
The HTML document explicitly sets `lang="en"`, and all visible instructional content is written only in English. For a user-facing emergency-access guide, this constitutes a language/locale constraint without any opt-in or justification in the file.

Static analysis

No suspicious patterns detected.