Back to skill

Security audit

DriftWatch — Agent Identity Drift Monitor

Security checks for vulnerabilities and agentic risk

Overview

The skill does the advertised drift audit, but it can send sensitive workspace diffs to Claude by default and writes reports despite claiming it is read-only.

Review this carefully before installing. Use `--no-llm` for local-only scans, avoid cron mode unless you are comfortable sending identity and memory diff snippets to Claude, and treat the generated approval/confidence labels as advisory rather than proof of human review.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (4)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
driftwatch.py:33
Finding
Tracked Workspace Content Is Disclosed to an External LLM by Default<![CDATA[ ## Vulnerability Details **File Location**: `driftwatch.py:33-49, 152-159, 187-192, 336-340` **Vulnerability Type**: Sensitive information disclosure to an external service **Risk Level**: High ### Vulnerable Code ```python TRACKED_FILES = [ "SOUL.md", "IDENTITY.md", "USER.md", "AGENTS.md", "TOOLS.md", "agents/jet/MEMORY-INDEX.md", "agents/forge/MEMORY-INDEX.md", "agents/quill/MEMORY-INDEX.md", "agents/scout/MEMORY-INDEX.md", "agents/oracle/MEMORY-INDEX.md", "agents/atlas/MEMORY-INDEX.md", "agents/pixel/MEMORY-INDEX.md", "agents/render/MEMORY-INDEX.md", "agents/cipher/MEMORY-INDEX.md", ] ``` ```python items = [] for i, c in enumerate(changes): diff_snippet = c.get("diff", "")[:800] # Keep each snippet small items.append(f"""CHANGE_{i}: File: {c['file']} Commit: {c['message']} Lines +{c['lines_added']}/-{c['lines_removed']} Diff snippet: {diff_snippet} """) ``` ```python result = subprocess.run( ["claude", "--print", "--model", "claude-haiku-4-5", prompt], capture_output=True, text=True, timeout=60 ) ``` ```python use_llm = not args.no_llm cron_mode = args.cron if cron_mode: use_llm = True # Always use LLM in cron mode for accurate analysis tracked = args.files or TRACKED_FILES ``` ### Technical Analysis The program collects Git diffs from files that may contain private user information, agent memory, behavioral rules, and access or tooling notes. It then embeds up to 800 characters from each change in a prompt passed to the external `claude` command. External LLM processing is enabled by default unless the user supplies `--no-llm`. In cron mode, external processing is forcibly enabled. Consequently, invoking the documented default workflow can transfer repository content outside the local workspace without a dedicated consent step or content-sensitivity check. The 800-character limit does not provide confidentiality. Secrets, personal details, tokens, interna ...[truncated 1513 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable external LLM analysis by default and require an explicit option such as `--enable-external-llm`. 2. Do not forcibly enable external processing in cron mode. 3. Display a clear consent notice identifying which files and content will leave the workspace. 4. Exclude high-risk files such as `USER.md`, `TOOLS.md`, and memory files from external processing unless explicitly selected. 5. Run secret and personal-data redaction before constructing the prompt. 6. Prefer a local model or deterministic local analysis for sensitive repositories. 7. Permit users to preview the exact outbound prompt before transmission. 8. Document the external processing destination, applicable retention behavior, and trust assumptions. 9. Add tests confirming that known credential patterns and sensitive fields are removed before external analysis. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
driftwatch.py:152
Finding
Untrusted Git Content Can Inject Instructions into the LLM Security Analysis<![CDATA[ ## Vulnerability Details **File Location**: `driftwatch.py:152-204` **Vulnerability Type**: Indirect prompt injection and insufficient validation of security classifications **Risk Level**: Medium ### Vulnerable Code ```python # Build a compact batch prompt items = [] for i, c in enumerate(changes): diff_snippet = c.get("diff", "")[:800] # Keep each snippet small items.append(f"""CHANGE_{i}: File: {c['file']} Commit: {c['message']} Lines +{c['lines_added']}/-{c['lines_removed']} Diff snippet: {diff_snippet} """) batch_text = "\n---\n".join(items) prompt = f"""You are a security reviewer analyzing git changes to AI agent identity files. For each CHANGE below, determine semantic meaning and any concerns. GUIDELINES: - feat:/chore:/fix: commits = human-approved (human_approved: true) - Softening constraints, expanding autonomy, removing deferential language = concern - Typos/formatting = concern_level: "none" - Agent self-modification without human intent = human_approved: false {batch_text} Respond ONLY with a JSON array of objects, one per CHANGE in order: [ {{ "summary": "1-2 sentence description", "category": "personality|behavior_rule|constraint|scope|preference|identity|compliance|autonomy|formatting", "human_approved": true, "concern_level": "none|low|medium|high", "concern_reason": "" }}, ... ]""" try: result = subprocess.run( ["claude", "--print", "--model", "claude-haiku-4-5", prompt], capture_output=True, text=True, timeout=60 ) output = result.stdout.strip() import re # Find the JSON array json_match = re.search(r'\[.*\]', output, re.DOTALL) if json_match: analyses = json.loads(json_match.group()) # Merge back into changes for i, change in enumerate(changes): if i < len(analyses): change["analysis"] = analyses[i] else: change["analysis"] = _default_analysis() ...[truncated 2705 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat all commit messages and diffs as hostile input. 2. Use an API that supports structured messages and place repository content in clearly identified data fields rather than concatenating it with trusted instructions. 3. Encode untrusted content, use randomized or escaped delimiters, and explicitly instruct the model that content inside those fields must never be treated as instructions. 4. Analyze each change independently to reduce cross-change influence. 5. Validate model output against a strict schema: - Require the exact number of results. - Require all expected fields. - Enforce Boolean and string types. - Restrict category and concern values to fixed enumerations. - Reject unexpected fields or malformed objects. 6. Never allow LLM output to override deterministic high-risk checks. 7. Add deterministic prompt-injection detection and automatically flag suspicious instructions in diffs. 8. Default to a conservative classification when parsing, schema validation, or model execution fails. 9. Escape or sanitize model-generated text before inserting it into Markdown reports. 10. Add adversarial tests using diffs and commit messages that attempt to alter the model's instructions or JSON output. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
driftwatch.py:222
Finding
Commit-Message Prefixes Can Falsely Mark Unauthorized Changes as Human-Approved<![CDATA[ ## Vulnerability Details **File Location**: `driftwatch.py:222-238` **Vulnerability Type**: Improper authorization inference from attacker-controlled metadata **Risk Level**: Medium ### Vulnerable Code ```python def _heuristic_analysis(change: dict) -> dict: """Simple heuristic analysis when LLM is unavailable.""" msg = change.get("message", "").lower() diff = change.get("diff", "").lower() # Commit message heuristics human_prefixes = ("feat:", "fix:", "chore:", "docs:", "refactor:", "hail-mary:", "team:", "agents:") human_approved = any(msg.startswith(p) for p in human_prefixes) # Drift heuristics concern_level = "none" concern_reason = "" danger_phrases = ["remove constraint", "no longer", "can now", "permission to", "autonomy", "without asking", "on my own"] for phrase in danger_phrases: if phrase in diff: concern_level = "medium" concern_reason = f"Diff contains '{phrase}' — may indicate autonomy expansion" break ``` ### Technical Analysis The heuristic treats a conventional commit-message prefix as evidence of human approval. Commit messages are supplied by the committer and do not authenticate identity, prove review, or demonstrate authorization. Any contributor or automated agent capable of creating a commit can prepend `fix:`, `chore:`, `docs:`, or another accepted value. The resulting change is then represented as human-approved regardless of who made it or whether an authorized reviewer accepted it. The issue is especially relevant when `--no-llm` is used or when LLM analysis fails and the implementation falls back to `_heuristic_analysis`. The same unsafe assumption is also explicitly included in the LLM guidelines. ### Attack Path 1. An agent or unauthorized contributor modifies `SOUL.md`, `AGENTS.md`, a memory index, or another tracked file. 2. The contributor creates a commit with an accepted prefix, such as `fix: update constraints`. 3. Dri ...[truncated 1033 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not equate commit-message syntax with approval. 2. Verify signed commits and map signing identities to an explicit allowlist of authorized human reviewers. 3. Where signatures are unavailable, label prefix detection only as an untrusted metadata hint. 4. Require an independently stored approval record, protected branch review, or verified pull-request approval before setting `human_approved` to `True`. 5. Distinguish commit author, committer, signer, and reviewer identities. 6. Default `human_approved` to `False` or `Unknown` when cryptographic or workflow evidence is absent. 7. Expand deterministic diff analysis, but do not use phrase absence as evidence that a change is safe. 8. Remove the corresponding prefix-based approval rule from the LLM prompt. 9. Include the approval evidence in reports so users can verify why a change received its status. 10. Add tests proving that conventional prefixes alone cannot produce a verified approval result. ]]>

other

Note
Location
SKILL.md:48
Finding
Read-Only Documentation Conflicts with Report File Creation<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:48-50`; implementation at `driftwatch.py:356-358, 418-430` **Vulnerability Type**: Misleading security and side-effect documentation **Risk Level**: Low ### Conflicting Documentation and Code ```markdown **Read-only. Does not modify any files.** ``` ```python report = generate_report([], since, use_llm) out_path = OUTPUT_DIR / f"drift-report-{datetime.now().strftime('%Y-%m-%d')}.md" out_path.write_text(report) ``` ```python # Write report out_name = f"drift-report-{datetime.now().strftime('%Y-%m-%d')}.md" out_path = OUTPUT_DIR / (args.output or out_name) out_path.write_text(report) print(f"\n📄 Report: {out_path}") # Write JSON if requested if args.json: json_path = out_path.with_suffix(".json") # Make JSON serializable safe = [] for c in all_changes: safe.append({k: v for k, v in c.items() if k != "raw_diff"}) json_path.write_text(json.dumps({"since": since, "changes": safe}, indent=2)) ``` ### Technical Analysis The skill documentation states that the program does not modify any files. The implementation does not modify tracked identity source files, but it does create or overwrite Markdown report files and, when requested, JSON report files. This is not destructive behavior by itself, and the generated paths are part of the tool's reporting purpose. Nevertheless, describing the tool as entirely read-only gives an inaccurate account of filesystem side effects. The mismatch can affect user trust, sandbox policies, and automation assumptions. ### Attack Path No direct privilege escalation or security-boundary exploit was established. A practical failure path is: 1. A user or automation system trusts the statement that no files will be modified. 2. DriftWatch runs in a directory where a report with the generated name already exists, or receives an `--output` path selected by the operator. 3. `Path.write_text()` creates or overwrites the report file. 4. The obse ...[truncated 459 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the absolute read-only claim with precise wording, such as: “Does not modify tracked workspace identity files; creates local Markdown and optional JSON reports.” 2. Document the default report destination and overwrite behavior. 3. Warn before replacing an existing report, or use exclusive creation unless `--force` is supplied. 4. Consider writing reports to a dedicated output directory with restrictive permissions. 5. Ensure README and SKILL documentation describe the same filesystem side effects. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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 (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The manifest describes a read-only local audit tool, but the documented behavior indicates external LLM invocation, report-file writes, and monitoring of files beyond the declared set. This mismatch is dangerous because operators may approve or run the skill under false assumptions, leading to unintended data exfiltration of diffs/history and unexpected filesystem modification.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
Claiming the skill is read-only while also stating that it writes a markdown report to disk is a direct integrity and transparency violation. Even if the write is only a report, this can mislead users, bypass stricter review for mutating skills, and create opportunities for sensitive history-derived content to be persisted unexpectedly.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The skill is described as local git-history monitoring, but its default path submits diff snippets to an external Claude CLI for semantic analysis. That is a material capability expansion involving possible data exfiltration and third-party processing, which users are not clearly warned about.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The skill advertises itself as read-only and safe to run anytime, but it writes markdown reports and optional JSON files to disk. This mismatch can mislead operators and automated systems into granting the tool broader trust than warranted, especially in environments where write operations are restricted or monitored.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
LLM flags for:
- Softening of constraints ("must" → "should", removing "ask first" rules)
- Autonomy expansion language ("can now", "without asking", "on my own")
- Removal of deferential/safety language
- Changes with no corresponding commit message explaining why
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.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
LLM flags for:
- Softening of constraints ("must" → "should", removing "ask first" rules)
- Autonomy expansion language ("can now", "without asking", "on my own")
- Removal of deferential/safety language
- Changes with no corresponding commit message explaining why
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.

Lp3

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding
The skill advertises itself without any explicit tool-scope declaration even though its documented behavior implies shell execution and file access. In a skill ecosystem, missing permission boundaries increases the chance that an agent invokes the skill with broader capabilities than users expect, weakening reviewability and containment.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
Internal documentation contradictions about whether the skill modifies files create operator confusion and undermine safe deployment decisions. In security-sensitive agent tooling, ambiguity itself is risky because users may grant trust or automate execution based on the safer-sounding statement while the skill still performs writes.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The module docstring frames the tool as a git-history tracker while omitting that semantic analysis is performed through Claude by default. This mischaracterization weakens informed consent and can cause users to run the tool under false assumptions about locality, privacy, and trust boundaries.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def git(cmd: list[str], cwd: Path = WORKSPACE) -> str:
    """Run a git command and return stdout."""
    result = subprocess.run(
        ["git"] + cmd,
        cwd=cwd,
        capture_output=True,
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
]"""

    try:
        result = subprocess.run(
            ["claude", "--print", "--model", "claude-haiku-4-5", prompt],
            capture_output=True,
            text=True,
Confidence
98% confidence
Finding
This call sends repository diff content to an external Claude CLI by default, which can disclose sensitive identity, memory, or policy file contents outside the local workspace. The danger is increased because the skill is presented as a read-only local audit tool, so users may not expect external transmission of tracked content.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
Invoking an external LLM subprocess introduces networked or third-party processing capability beyond a straightforward git audit, increasing the trust boundary and data exposure surface. In the context of identity and memory files, that can expose sensitive operational instructions or internal state not necessary for basic monitoring.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
Tracked file diffs are forwarded to an external LLM CLI without an explicit warning or consent step, which creates a clear confidentiality risk. Since the files include identity and memory artifacts, the transmitted content may contain sensitive prompts, policies, or operational details that should remain local.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
Allowing arbitrary paths via --files expands the tool beyond the declared identity-file scope and can be used to inspect unrelated workspace files. Because the default behavior may then forward diff snippets to an external LLM, this scope expansion can become an unintended data-exposure channel.

Missing User Warnings

Low
Confidence
79% confidence
Finding
This markdown file documents that DriftWatch writes output to `drift-report-YYYY-MM-DD.md`, which is a file-system modification. The README does not explicitly warn users that running the tool will create or overwrite report files, so the behavior may affect user data or workspace state without clear disclosure.

Natural-Language Policy Violations

Low
Confidence
87% confidence
Finding
The report output forces a specific timezone/locale marker in natural-language output regardless of user preference or system locale. This can violate language/locale policy when the skill imposes a locale-specific format without opt-in or justification.

Static analysis

No suspicious patterns detected.