Back to skill

Security audit

password-auditor

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent password-auditing tool, but its optional HTML report can execute crafted content from vault entry titles, so it should be reviewed before installation.

Install only if you are comfortable giving the skill access to an explicit vault export you choose. Keep breach checks disabled unless you accept sending SHA-1 prefixes to HaveIBeenPwned, store generated reports carefully because they include account titles and findings, and avoid opening HTML reports from untrusted or shared vault exports until the renderer escapes all dynamic fields.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/password_auditor.py:589
Finding
Stored HTML Injection Through Unescaped Vault Entry Titles<![CDATA[ ## Vulnerability Details **File Location**: `scripts/password_auditor.py`, lines 589–603 **Vulnerability Type**: Stored HTML injection / cross-site scripting in generated reports **Risk Level**: High ### Vulnerable Code ```python frows = [] for kind, items in res["findings"].items(): for f in items: sev = {"breach": "P0", "reuse": "P1", "weakness": "P1", "staleness": "P3", "twofa": "P3"}[kind] sevcls = "p0" if sev == "P0" else "" tag = f.get("tier", "") tagcls = tag if tag in ("critical", "sensitive") else "standard" frows.append(f"<tr><td>{f['entry']}</td><td class='{sevcls}'>{sev}</td>" f"<td>{kind}</td><td>{f['title']} " f"<span class='tag {tagcls}'>{tagcls}</span></td>" f"<td>{f['action']}</td></tr>") prows = "".join( f"<tr><td>{p['priority']}</td><td>#{p['entry']} {p['title']}</td>" f"<td>{p['dimension']}</td><td>{p['action']}</td></tr>" for p in res["plan"][:15]) ``` ### Technical Analysis Vault entry titles originate from imported CSV or JSON content. These values are copied into finding and remediation-plan objects and then interpolated directly into HTML without contextual output encoding. Because `f['title']` and `p['title']` are not processed with `html.escape()`, HTML elements and event-handler attributes in a crafted title become active browser content. For example, an entry title containing an image element with an `onerror` handler could execute JavaScript when the generated dashboard is opened. The vulnerability is stored rather than reflected: the malicious value is first stored in or supplied through a vault export, incorporated into the generated dashboard through `--html`, and executed later when a user opens that file. ### Attack Path 1. An attacker introduces a crafted title into a password-vault entry, shared vault item, impor ...[truncated 1472 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape every dynamic value before inserting it into HTML: ```python import html safe_title = html.escape(str(f["title"]), quote=True) safe_action = html.escape(str(f["action"]), quote=True) safe_kind = html.escape(str(kind), quote=True) ``` 2. Apply equivalent escaping to remediation-plan fields, including titles, dimensions, actions, labels, and any future vault-derived values. 3. Prefer a template engine with automatic HTML escaping instead of constructing markup through f-strings. 4. Add a restrictive Content Security Policy to the generated document, for example: ```html <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:"> ``` This should be defense in depth and must not replace output encoding. 5. Add regression tests with titles containing: - `<script>` elements - Event handlers such as `onerror` - Quotes and angle brackets - Encoded HTML entities - SVG-based script payloads 6. Verify that generated reports contain encoded text such as `&lt;script&gt;` rather than executable elements. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (8)

Vague Triggers

Medium
Confidence
94% confidence
Finding
The README advertises invocation based on a very broad natural-language cue: asking things like "how bad are my passwords?" can easily occur in ordinary conversation and may cause an agent to invoke the skill unexpectedly. Because this skill processes highly sensitive vault exports and can optionally make external breach-check requests, accidental triggering increases the risk of unintended access to secrets or privacy-impacting actions even if the skill itself is not overtly malicious.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill describes behavior that reads local vault exports, writes JSON/HTML reports, optionally performs network requests to the HIBP API, and invokes shell-style commands in examples, but it does not declare any explicit tool scope or permissions. For a password-auditing skill handling highly sensitive credential exports, this ambiguity is risky because an agent runtime may grant broader file, network, or execution access than users expect, increasing the chance of unintended data exposure or misuse.

External Transmission

Medium
Category
Data Exfiltration
Content
## Breach Exposure (HaveIBeenPwned, k-anonymity)

1. Compute `SHA1(password).upper()`.
2. Send only the first 5 hex chars to `https://api.pwnedpasswords.com/range/XXXXX`.
3. HIBP returns ~800 suffixes for that prefix; match locally against the remaining 35 chars.
4. The response includes a count = how many times that exact password appears in breach corpora.
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The top-level documentation says the tool is 'Read-only' and 'Never writes credentials anywhere,' which communicates no disk writes. However, the main routine supports --json and --html outputs and calls write_text() to create report files, so the documentation contradicts actual side effects even if the reports are intended to be password-free.

External Transmission

Medium
Category
Data Exfiltration
Content
for prefix, members in by_prefix.items():
        try:
            req = urllib.request.Request(
                f"https://api.pwnedpasswords.com/range/{prefix}",
                headers={"User-Agent": "password-auditor-skill"})
            with urllib.request.urlopen(req, timeout=timeout) as resp:
                body = resp.read().decode()
Confidence
89% confidence
Finding
When --check-breaches is used, the tool transmits SHA-1 prefix data for each password to the HaveIBeenPwned range API. Although this uses k-anonymity and does not send plaintext passwords, it still discloses derived secret material and creates a network side channel that may be unacceptable in high-sensitivity environments, especially for vault-auditing workflows where users expect strict local-only handling.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run(*args):
    return subprocess.run([sys.executable, str(SCRIPT), *args],
                          capture_output=True, text=True)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Intent-Code Divergence

Low
Confidence
78% confidence
Finding
The argparse description states 'Audit password vault exports without storing passwords,' suggesting non-persistence. Yet the same CLI exposes --json and --html options that save audit artifacts to files, which is a weaker but still real contradiction in user-facing intent documentation.

Missing User Warnings

Low
Confidence
77% confidence
Finding
This code creates a CSV fixture containing real-looking plaintext passwords and writes it to disk via p.write_text(). Although it is a test file and uses a temporary directory, the operation still writes sensitive credential-like data to the filesystem with no comment or user-facing disclosure near the write beyond the module docstring's note about network access.

Static analysis

No suspicious patterns detected.