Back to skill

Security audit

Clean Log Toolkit

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent local log-analysis skill, but it needs review because its output files can overwrite source logs and its reports can preserve sensitive raw log content.

Review this before installing in workflows that handle production, audit, incident, or customer logs. Use separate output directories, do not write reports over the original log path or symlink aliases, and inspect or redact generated reports before sharing them in tickets, repositories, spreadsheets, or chat. The skill shows no evidence of exfiltration or persistence, but its current output handling can cause data loss or leak sensitive log details through user-created reports.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/parse.py:149
Finding
Input and Output Path Collision Can Truncate Source Logs in parse.py## Vulnerability Details **File Location**: `scripts/parse.py:149-150, 204, 237-248` **Vulnerability Type**: Input/output path collision and unsafe file overwrite **Risk Level**: High ### Vulnerable Code ```python try: in_path = safe_path(args.input) out_path = safe_path(args.output) except ValueError as e: print(f"Error: {e}", file=sys.stderr) return 2 ``` ```python out_path.parent.mkdir(parents=True, exist_ok=True) ``` ```python if not use_buffer: delim = "\t" if fmt_out == "tsv" else "," fout = out_path.open("w", encoding="utf-8", newline="") try: if fmt_out == "jsonl": writer = None else: writer = csv.DictWriter(fout, fieldnames=fixed_header, delimiter=delim, extrasaction="ignore") writer.writeheader() for line in iter_lines(in_path): ``` ### Technical Analysis The input and output paths are independently checked only against a character allowlist. The code never verifies that they refer to different filesystem objects. Opening `out_path` with mode `"w"` truncates the target immediately. If the output path is identical to the input path, or is a symlink or alternate path resolving to the input file, the source log can be truncated before parsing completes. Lexical comparison alone would also be insufficient because paths such as `logs/app.log` and `logs/../logs/app.log`, or two symlinks, may identify the same object. The buffered execution path still overwrites the source after reading it, so the collision remains destructive even where truncation does not occur before parsing. ### Attack Path 1. An attacker or mistaken automation supplies a source log as the input. 2. The same file, a normalized alias, hard link, or symlink to that file is supplied as the output. 3. Both paths pass `safe_path()` because the function only checks characters. 4. `out_path ...[truncated 543 chars]
Remediation
## Remediation Suggestions - Resolve and normalize the input and output paths before writing. - Use `os.path.samefile()` or `Path.samefile()` when both paths exist, with a safe fallback for a new output path. - Reject hard-link, symlink, and normalized-path collisions. - Write generated data to a securely created temporary file in the destination directory and atomically replace the destination only after successful completion. - Do not follow an existing output symlink unless this behavior is explicitly required and protected. - Add tests covering identical paths, relative aliases, symlink aliases, and hard links. Example defensive check: ```python resolved_input = in_path.resolve(strict=True) resolved_output = out_path.resolve(strict=False) if resolved_input == resolved_output: raise ValueError("Input and output must refer to different files") if out_path.exists() and in_path.samefile(out_path): raise ValueError("Input and output must refer to different files") ```

T09 · Insecure Skill Coding Practices

Error
Location
scripts/errors.py:108
Finding
errors.py Allows Report Output to Overwrite Its Source Log## Vulnerability Details **File Location**: `scripts/errors.py:108-111, 127-135, 183-202` **Vulnerability Type**: Input/output path collision and unsafe file overwrite **Risk Level**: High ### Vulnerable Code ```python try: in_path = safe_path(args.input) except ValueError as e: print(f"Error: {e}", file=sys.stderr) return 2 ``` ```python if args.output: try: out_path = safe_path(args.output) except ValueError as e: print(f"Error: {e}", file=sys.stderr) return 2 if out_path.suffix.lower() not in (".json", ".md", ".csv"): print(f"Error: --output extension must be .json, .md, or .csv", file=sys.stderr) return 2 ``` ```python if out_path is not None: out_path.parent.mkdir(parents=True, exist_ok=True) ext = out_path.suffix.lower() if ext == ".json": out_path.write_text(json.dumps(summary, indent=2), encoding="utf-8") elif ext == ".csv": # Just the timeline as CSV with out_path.open("w", encoding="utf-8") as f: cols = ["bucket"] + sorted({lvl for v in timeline.values() for lvl in v}) f.write(",".join(cols) + "\n") for k in sorted(timeline): row = [k] + [str(timeline[k].get(lvl, 0)) for lvl in cols[1:]] f.write(",".join(row) + "\n") ``` ### Technical Analysis The report output is not checked against the input filesystem object. All supported output branches overwrite their destination. Although aggregation occurs before the write, selecting the input itself—or an alias to it—as the output replaces the original log with the generated report. Extension checks do not prevent this condition because source logs can already use one of the accepted extensions, and a symlink with an accepted suffix can point to the input. ### Attack Path 1. Supply a writable log file as the input. 2. Supply the sam ...[truncated 562 chars]
Remediation
## Remediation Suggestions - Compare resolved input and output paths before processing. - When both files exist, use `samefile()` to identify hard-link and symlink aliases. - Reject any output that identifies the source file. - Create reports in a secure temporary file and atomically rename them only after generation succeeds. - Explicitly define and enforce whether overwriting an unrelated existing report is allowed. - Add regression tests for direct, relative-path, symlink, and hard-link collisions.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/grep.py:89
Finding
grep.py Allows Filter Output to Replace the Input File## Vulnerability Details **File Location**: `scripts/grep.py:89-92, 136-143, 192-195` **Vulnerability Type**: Input/output path collision and unsafe file overwrite **Risk Level**: High ### Vulnerable Code ```python try: in_path = safe_path(args.input) except ValueError as e: print(f"Error: {e}", file=sys.stderr) return 2 ``` ```python out_path = None if args.output: try: out_path = safe_path(args.output) except ValueError as e: print(f"Error: {e}", file=sys.stderr) return 2 ``` ```python if out_path is not None: out_path.parent.mkdir(parents=True, exist_ok=True) with out_path.open("w", encoding="utf-8") as f: for ln in output_lines: f.write(ln + "\n") ``` ### Technical Analysis Filtered lines are collected in memory and then written using mode `"w"`. No check establishes that the output is distinct from the input. Consequently, after scanning finishes, the script can overwrite the source log with only the selected lines. Symlink aliases, hard links, and different lexical paths to the same file are also unaddressed. ### Attack Path 1. Invoke `grep.py` with a source log and filtering criteria. 2. Set `--output` to the input path or another path identifying the same file. 3. The path character checks pass. 4. The script reads and buffers its selected lines. 5. It opens the colliding output in write mode, replacing the source with the filtered subset. ### Impact Assessment No privilege escalation occurs; exploitation is constrained to files writable by the invoking process. The attacker can nevertheless erase all nonmatching records and alter the evidentiary integrity of the source log. This could conceal events or disrupt subsequent analysis.
Remediation
## Remediation Suggestions - Resolve and compare input and output filesystem identities before scanning. - Use `samefile()` where possible so hard links and symlinks are detected. - Reject colliding paths with an explicit error. - Write to a secure temporary file and atomically move it into place after all output has been produced. - Consider requiring an explicit overwrite flag before replacing any existing unrelated output file. - Test direct collisions, `..` aliases, symlinks, and hard links.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/parse.py:117
Finding
Untrusted Log Fields Can Trigger Spreadsheet Formula Injection in CSV and TSV Exports## Vulnerability Details **File Location**: `scripts/parse.py:117-137, 213-230, 242-248` **Vulnerability Type**: CSV and TSV formula injection **Risk Level**: Medium ### Vulnerable Code ```python def parse_row(line: str, fmt: str, pat: Optional[Pattern[str]], custom_pat: Optional[Pattern[str]]) -> Optional[Dict[str, str]]: if custom_pat is not None: m = custom_pat.search(line) if not m: return None return {k: ("" if v is None else v) for k, v in m.groupdict().items()} if fmt == "json-line": try: obj = json.loads(line) except json.JSONDecodeError: return None if not isinstance(obj, dict): return None return {k: ("" if v is None else str(v)) for k, v in obj.items()} ``` ```python def write_csv_buffered(rows: List[Dict[str, str]]) -> None: nonlocal rows_out if not rows: return # Compute union header in insertion order seen: set = set() cols: List[str] = [] for r in rows: for k in r: if k not in seen: seen.add(k); cols.append(k) if fields_filter: cols = [c for c in fields_filter if c in seen] delim = "\t" if fmt_out == "tsv" else "," with out_path.open("w", encoding="utf-8", newline="") as fout: w = csv.DictWriter(fout, fieldnames=cols, delimiter=delim, extrasaction="ignore") w.writeheader() for r in rows: w.writerow(r) rows_out += 1 ``` ```python if fmt_out == "jsonl": fout.write(json.dumps(row, ensure_ascii=False) + "\n") else: writer.writerow(row) rows_out += 1 ``` ### Technical Analysis Values originating from log lines, JSON objects, and custom regular-expression captures are written directly to CSV or TSV cells. CSV quoting protects the file structure but do ...[truncated 1425 chars]
Remediation
## Remediation Suggestions - Add a spreadsheet-safe CSV/TSV mode and enable it by default for reports intended for interactive spreadsheet use. - Before writing a cell, detect leading formula indicators after relevant leading whitespace and prefix the value with an apostrophe or another client-compatible neutralization character. - Apply neutralization consistently to all attacker-controlled fields, including JSON values, regex captures, generic messages, and unparsed lines. - Preserve an explicitly named raw-export mode where byte-faithful data is required, and document its risks. - Add test cases for values beginning with `=`, `+`, `-`, `@`, tabs, carriage returns, and leading whitespace. - Clearly warn users that untrusted raw CSV or TSV should be imported as text rather than opened directly.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/errors.py:203
Finding
Untrusted Log Samples Can Escape Markdown Code Fences in Generated Reports## Vulnerability Details **File Location**: `scripts/errors.py:203-213` **Vulnerability Type**: Markdown content injection **Risk Level**: Medium ### Vulnerable Code ```python lines.append("") lines.append(f"## Timeline ({args.bucket} buckets)") for k in sorted(timeline): parts = ", ".join(f"{lvl}={c}" for lvl, c in sorted(timeline[k].items())) lines.append(f"- {k}: {parts}") lines.append("") lines.append(f"## Top {len(top_groups)} message group(s)") for c, (fp, _) in zip([g[1] for g in top_groups], top_groups): lines.append(f"### {c}x") lines.append(f"```\n{sample_per_group[fp]}\n```") lines.append("") ``` ### Technical Analysis A raw log sample is embedded between fixed triple-backtick Markdown fences. The sample is attacker-controlled and is not escaped. If it contains a sequence of three backticks, it can terminate the intended code block and introduce arbitrary Markdown or renderer-supported HTML into the remainder of the report. The injection is stored in the generated `.md` file and becomes active when the report is rendered or pasted into a ticketing, documentation, or source-control platform. The exact effect depends on that platform's Markdown and HTML sanitization. ### Attack Path 1. An attacker causes a recognized warning or error log entry to contain a closing triple-backtick sequence. 2. The same entry includes Markdown content after that sequence, such as a deceptive link, image reference, mention, or false incident instruction. 3. `errors.py` selects the entry as a sample for a top message group. 4. The script interpolates the sample into a fixed triple-backtick fence. 5. The injected backticks close the code block, and the remaining payload is interpreted as Markdown when the report is rendered or pasted elsewhere. ### Impact Assessment This issue does not directly execute code in the log-analysis process or grant filesystem privileges. I ...[truncated 345 chars]
Remediation
## Remediation Suggestions - Do not place untrusted text inside a fixed-length Markdown fence. - Compute the longest consecutive backtick run in the sample and use a fence longer than that run. - Alternatively, render samples as four-space-indented code blocks or encode them in a representation that cannot alter Markdown structure. - If reports are intended for a particular platform, apply that platform's recommended Markdown and HTML sanitization rules. - Sanitize or disable raw HTML and potentially dangerous URL schemes where the report pipeline permits them. - Add tests containing triple backticks, longer backtick runs, HTML tags, image syntax, links, and mention syntax.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Lp3

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding
The skill advertises local file read/write behavior but does not declare any explicit tool scope such as permissions or allowed-tools. That weakens policy enforcement and user visibility around what filesystem access the skill expects, increasing the chance of overbroad or unintended file access when the skill is executed by an agent framework.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This tool includes raw sample log lines in JSON, Markdown, and stdout output, and those samples may contain secrets, personal data, tokens, session identifiers, IPs, file paths, or other sensitive operational details copied directly from the source logs. Because the skill’s purpose is local log inspection and report generation, exporting representative samples materially increases the chance of unintended data disclosure when reports are saved, shared, or checked into ticketing systems or repositories.

Missing User Warnings

Low
Confidence
85% confidence
Finding
This markdown file describes parsing, grepping, tailing, and exporting logs to CSV/JSONL/Markdown, but it does not warn users that logs often contain secrets, tokens, IPs, or PII and that the tool will reproduce that content in output files and summaries. Because this skill is specifically designed to inspect and reformat potentially sensitive logs, a brief privacy/data-handling warning would improve user awareness.

Intent-Code Divergence

Low
Confidence
98% confidence
Finding
The document elsewhere clearly states that `scripts/follow.py` exists in v0.2.0 and provides built-in tail-and-follow support, while L148 says built-in follower support is not yet available and may land in v0.2. This is an active contradiction in the skill's own documentation, even though it appears confined to a stale limitations note rather than the code behavior itself.

Missing User Warnings

Low
Confidence
76% confidence
Finding
This code creates parent directories and writes an output file, which is a safety-relevant filesystem modification. While the script's purpose implies output generation, there is no explicit confirmation prompt or user-facing warning near the write path about creating directories or overwriting output content.

Static analysis

No suspicious patterns detected.