Back to skill

Security audit

AI Usage Ledger Skill

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed local AI-usage ledger with sensitive opt-in features, not an artifact-backed exfiltration or deception case.

Install only if you want a local ledger that reads your AI agent logs. Keep credential reads, prompt capture, identifiable accounts, raw-log archive, all-profiles/WSL/SSH scanning, and scheduling off unless you need them and are authorized to scan that data. Treat the ledger home, archive, accounts file, and anonymization map as sensitive. Until the archive grep terminal-output bug is fixed, prefer ledger.py query logs table/JSON output or avoid searching untrusted archived transcript text directly in a terminal.

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

Warning
Location
scripts/ledger_archive.py:395
Finding
Unsanitized Archived Log Content Written to the Terminal## Vulnerability Details **File Location**: `scripts/ledger_archive.py:395-396, 501` **Vulnerability Type**: Terminal escape/control-sequence injection **Risk Level**: Medium ### Complete Code Snippet ```python a = max(0, m.start() - context // 2) snippet = line[a:a + context].decode("utf-8", "replace").replace("\n", " ") yield { "host": f["host"], "path": f["path"], "line": lineno, "ts": ts, "snippet": snippet, } ``` The returned value is subsequently printed without terminal sanitization: ```python elif a.cmd == "grep": for hit in ar.grep(a.pattern, a.host, a.since, a.until, a.path_filter, a.limit): if a.json: print(json.dumps(hit)) else: print("%s %s:%d %s\n %s" % ( hit["ts"] or "----------T--:--:--", hit["path"], hit["line"], hit["host"], hit["snippet"], )) ``` ### Technical Analysis The `grep()` method derives `snippet` directly from archived AI-agent log bytes. It removes newline characters but does not remove ANSI escape sequences, C0/C1 controls, carriage returns, tabs, or other terminal-active characters. The human-readable `grep` command then interpolates this value directly into `print()`. AI transcripts may contain untrusted content originating from prompts, repository files, tool output, remote hosts, or model responses. An attacker who can influence any archived transcript can insert escape sequences into a line that also contains a value the operator is likely to search for. The project already provides `safety.clean_for_terminal()` for this trust boundary and uses it elsewhere, including the primary `ledger_query.py` table output. The direct `ledger_archive.py grep` path bypasses that protection. ### Attack Path 1. An attacker causes an AI-agent transcript to contain a selected search term alongside terminal ...[truncated 1685 chars]
Remediation
## Remediation Suggestions 1. Sanitize every untrusted field immediately before human-readable terminal output: ```python def terminal_field(value, limit): return safety.clean_for_terminal(value, limit=limit).replace("\n", " ").replace("\t", " ") print("%s %s:%d %s\n %s" % ( terminal_field(hit["ts"] or "----------T--:--:--", 64), terminal_field(hit["path"], 1000), hit["line"], terminal_field(hit["host"], 128), terminal_field(hit["snippet"], 1000), )) ``` 2. Prefer sanitization at the output boundary rather than altering archived data. The archive should retain exact bytes, while terminal rendering should always be safe. 3. Treat paths, host names, timestamps, exception messages, and snippets as untrusted output, even where separate validation normally constrains some fields. 4. Preserve machine-readable output as correctly encoded JSON rather than applying terminal-oriented transformations to its underlying values. Document that JSON output should be redirected or processed by a JSON-aware tool. 5. Add regression tests containing: - ANSI color and cursor-control sequences. - OSC title and clipboard sequences. - Carriage returns and C0/C1 controls. - Tabs, embedded newlines, and very long matching lines. - Untrusted content obtained from an SSH-host archive. 6. Assert that human-readable output contains no ESC byte or prohibited control character after sanitization.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (155)

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding
The code clearly aligns with part of the declaration: it scans many AI coding-agent log formats, deduplicates certain events, optionally captures prompt text, prices usage using list-rate tables, assigns accounts/billing via rules, and emits consolidated summaries. However, the declared description presents a much larger end-to-end product. This code chunk is only the scanner/report generator core and lacks many central advertised capabilities: there is no append-only ledger database, no SQLite output backend for the compiled ledger, no dashboard, no onboarding/state persistence, no scheduling, no anonymisation, no archival of raw logs, no multi-format document renderer beyond Markdown/CSV/JSON, and no query interface. It also does not itself autodetect other drives/profiles/WSL/SSH hosts or collect from them; it only processes roots passed on the command line. Because those omitted features are prominent parts of the declared purpose rather than minor implementation details, the description materially overstates what this code chunk actually does.

Ae1

High
Category
analysis-evasion
Content
(`~/.claude`, `~/.codex`, `~/.copilot`, IDE `globalStorage`, and the roots in `references/harness-catalog.md`), plus the tools' own counters (`state_5.sqlite`,
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
(`~/.claude`, `~/.codex`, `~/.copilot`, IDE `globalStorage`, and the roots in `references/harness-catalog.md`), plus the tools' own counters (`state_5.sqlite`,
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
(`~/.claude`, `~/.codex`, `~/.copilot`, IDE `globalStorage`, and the roots in `references/harness-catalog.md`), plus the tools' own counters (`state_5.sqlite`,
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
| What will be read | Agent transcripts, session logs and tool databases in the operator's own profile for every supported harness (`~/.claude`, `~/.codex`, `~/.copilot`, IDE `globalStorage`, and the roots in `references/harness-catalog.md`), plus the tools' own counters (`state_5.sqlite`, `stats-cache.json`). Nothing is modified or deleted; nothing needs elevation; `detect_hosts.py` lists every root before the first run and prints what it could not read. |
| What is retained and where | A durable, append-only ledger of per-call records (timestamps, models, token counts, prices, host, project path, session id, pseudonymous account) in `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`, owner-only): `config.json`, the store, `manifest.json`, `accounts.json`, `pricing.json`, `report_config.json`, `anonymize-map.json`, `logs/`; and the generated `scans/`, `compiled/`, `reports/`, `documents/` and `anonymized/` trees under the chosen working directory. Nothing is transmitted; no network access is used. |
| Opt-in: prompt text | `prompts.capture` (default off). Off, the ledger stores counts and metadata only; on, the prompt and history text is kept in the store so `query prompts` can search it. |
| Opt-in: credential files | `accounts.from_credentials` (default off). On, `auth.json`, `.claude.json` and `.credentials.json` are opened for account id prefixes and plan types only, after a warning that names each file and field; tokens and keys are never kept or printed. |
| Opt-in: identifiable accounts | `accounts.identifiable` (default off). On, e-mails and organisation names are kept; otherwise accounts are pseudonymous (`codex:<8-char prefix>`, `claude:<8-char hash>`). |
| Opt-in: raw-log archive | `archive.raw_logs` (default off). On, every source file the scanners read is copied to `~/.ai-usage-ledger/archive/` (about the size of the tools' log trees, one tenth compressed) so it outlives the tools' retention and `query logs` can search it. |
| Opt-in: other profiles and
...[truncated 25 chars]
Confidence
95% confidence
Finding
The skill explicitly supports opening credential-related files such as auth.json, .claude.json, and .credentials.json to derive account identifiers and plan metadata. Even with stated safeguards, accessing credential stores is highly sensitive because these files may contain tokens, keys, emails, organization names, or other secrets, and the same skill also persists derived data into a long-lived local ledger and can aggregate across multiple profiles and hosts.

Ae1

High
Category
analysis-evasion
Content
prompt or in `config.json` `extra_hosts`. For each such host the skill uploads `compile_ai_logs.py` with `scp`, runs it there over `ssh`, and downloads the scan
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
prompt or in `config.json` `extra_hosts`. For each such host the skill uploads `compile_ai_logs.py` with `scp`, runs it there over `ssh`, and downloads the scan
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| How to remove everything | `python3 scripts/ledger.py schedule remove` (if a schedule exists), then delete `~/.ai-usage-ledger/` (or `$AI_USAGE_LEDGER_HOME`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Static analysis

No suspicious patterns detected.