Back to skill

Security audit

RedactKit - AI Privacy Scrubber

Security checks for vulnerabilities and agentic risk

Overview

This is a local reversible redaction tool, but verified redaction bugs and sensitive plaintext mapping/report behavior make it risky to trust for privacy protection without review.

Review this skill before installing for any real sensitive data. Reversible redaction is disclosed, but mapping files are effectively secret vaults in plaintext; store them outside shared, synced, or source-controlled folders and protect or encrypt them. Do not rely on this version as a complete scrubber because verified bugs can leave phone numbers and standalone tokens exposed, and avoid report mode in logged environments.

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
redact_patterns.py:96
Finding
Generic API keys may remain exposed because unmatched capture groups produce invalid replacement boundaries<![CDATA[ ## Vulnerability Details **File Location**: `redact_patterns.py:96-99`; `redact_kit.py:93-101` **Vulnerability Type**: Incorrect capture-group handling causing incomplete or failed secret redaction **Risk Level**: High ### Vulnerable Code ```python # redact_patterns.py:96-99 pattern=re.compile( r'\b[A-Za-z0-9_-]{32,}\b|' # Generic long alphanumeric r'(?:api[_-]?key|apikey|token|secret)["\']?\s*[:=]\s*["\']?([A-Za-z0-9_-]{16,})' ), ``` ```python # redact_kit.py:93-101 for match in pattern.pattern.finditer(text): # Get match value if match.groups(): # If there are groups, use the first group value = match.group(1) start = match.start(1) end = match.end(1) else: value = match.group(0) start = match.start() end = match.end() ``` ### Technical Analysis The API-key regular expression has two alternatives. The first alternative detects a standalone token of 32 or more characters but does not populate capture group 1. The second alternative detects a labeled secret and places its value in capture group 1. The redaction engine uses `match.groups()` to decide whether group 1 should be used. That method returns the tuple of groups defined by the entire regular expression, even when a particular group did not participate in the selected alternative. For a standalone token matched by the first alternative: - `match.group(1)` returns `None`. - `match.start(1)` and `match.end(1)` return `-1`. - The replacement logic subsequently operates on invalid semantic boundaries. - Report mode can fail when it evaluates `match.original_value[:30]` on `None`. As a result, the output can be corrupted while the original token remains partially or fully exposed. This defeats the primary security purpose of the project. ### Attack Path 1. A user processes text containing a standalone token of at least 32 characters, such as a long API credential without an `api_key=`, `token=`, or similar prefix ...[truncated 1095 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not infer the intended replacement value merely from the existence of capture groups. - Prefer regular expressions in which the complete sensitive value is consistently represented by `match.group(0)`. - Alternatively, use one named group for each alternative and select the first group whose value is not `None`. - Validate that `start >= 0`, `end >= start`, and `value is not None` before creating a `RedactionMatch`. Fail closed if these invariants are violated. - Separate generic and labeled API-key detection into distinct `RedactionPattern` objects if their match semantics differ. - Add regression tests for both alternatives, including standalone long tokens, labeled tokens, quoted values, and report mode. - Verify that the original credential is absent from `redacted_text`, not merely that a match object was produced. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
redact_patterns.py:42
Finding
Phone-number redaction replaces only the first captured component<![CDATA[ ## Vulnerability Details **File Location**: `redact_patterns.py:42-45`; `redact_kit.py:93-101` **Vulnerability Type**: Partial PII redaction caused by incompatible regular-expression capture semantics **Risk Level**: High ### Vulnerable Code ```python # redact_patterns.py:42-45 pattern=re.compile( r'(?:\+?1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})|' r'\+[0-9]{1,3}[-.\s]?[0-9]{1,14}' ), ``` ```python # redact_kit.py:93-101 for match in pattern.pattern.finditer(text): # Get match value if match.groups(): # If there are groups, use the first group value = match.group(1) start = match.start(1) end = match.end(1) else: value = match.group(0) start = match.start() end = match.end() ``` ### Technical Analysis The US phone-number alternative divides the number into three capturing groups: area code, exchange, and subscriber number. The generic redaction engine assumes that the first capture group always represents the entire sensitive value. For an input such as `555-123-4567`, group 1 contains only `555`. The engine therefore replaces only the area code with a placeholder while leaving `123-4567` in the output. Prefixes and formatting outside group 1 can also remain visible. The international alternative contains no participating capture group but is part of the same compiled expression. It can consequently trigger the same unmatched-group behavior described for generic API keys, because the expression still defines groups globally. ### Attack Path 1. A document contains a standard phone number such as `555-123-4567`. 2. The phone pattern matches the complete number but captures its components separately. 3. The engine chooses only group 1 as the redaction range. 4. The resulting output contains a placeholder for `555` while retaining `123-4567`. 5. The user shares the output under the assumption that the complete phone number was removed. 6. A recipient c ...[truncated 795 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Convert internal phone-number component groups to noncapturing groups and redact the complete `match.group(0)` range. - If component captures are needed for validation, wrap the complete number in a named group and configure the engine to use that explicit group. - Define US and international phone formats as separate patterns if their capture semantics cannot be made identical. - Add exact-output tests for: - `555-123-4567` - `(555) 123-4567` - `+1 555 123 4567` - Representative international numbers - Assert in tests that no original phone-number digits remain outside the intended placeholder. - Add engine-level validation that rejects unmatched groups and negative offsets. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
redact_kit.py:176
Finding
Restoration mappings containing original secrets are written as plaintext with process-default permissions<![CDATA[ ## Vulnerability Details **File Location**: `redact_kit.py:176-185` **Vulnerability Type**: Insecure storage of sensitive information **Risk Level**: Medium ### Vulnerable Code ```python mapping_data = { 'mapping_id': mapping_id, 'matches': [asdict(m) for m in self.mappings[mapping_id]] } with open(output_path, 'w', encoding='utf-8') as f: json.dump(mapping_data, f, indent=2) ``` ### Technical Analysis Every `RedactionMatch` is serialized with `asdict`, including `original_value`. The resulting mapping can therefore contain plaintext passwords, API keys, Social Security numbers, phone numbers, email addresses, and financial data. The file is created using ordinary `open(..., 'w')`. Its effective permissions depend on the parent directory, operating system, and process umask. The implementation does not: - Enforce owner-only permissions. - Use exclusive creation to prevent unintended overwrite or link-based file replacement. - Encrypt mapping contents. - Warn when the destination is shared or located beside distributable output. - Perform an atomic secure write. Although plaintext storage is documented as a limitation, it remains a concrete confidentiality risk because the saved file is effectively a recovery copy of all redacted values. ### Attack Path 1. A user invokes redaction with `--mapping` or supplies a mapping path through the API. 2. The program writes all original values to plaintext JSON. 3. The destination is located in a shared directory, permissive workspace, synchronized folder, backup set, or source repository. 4. Another local user, process, synchronization recipient, or repository reader obtains the mapping file. 5. The attacker directly reads `original_value` entries without needing to break the placeholder scheme. 6. The attacker can also apply the mapping to redacted documents to reconstruct the original content. ### Impact Assessment The issue does not independently elevate operating-system privileges. It ...[truncated 374 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create mapping files with owner-only permissions, such as mode `0600` on POSIX systems, rather than relying solely on the process umask. - Use exclusive and atomic creation where appropriate, followed by an atomic rename from a securely created temporary file. - Refuse or explicitly confirm overwriting an existing mapping. - Add authenticated encryption for mappings, with a key supplied separately from the mapping file. - Avoid storing encryption keys in source code, command history, or the same directory as encrypted mappings. - Warn users when mapping files are stored inside the redacted output tree or another potentially shared directory. - Provide a secure deletion and retention policy, while documenting filesystem limitations affecting guaranteed deletion. - Ensure mapping directories receive restrictive permissions as well as individual files. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
redact_kit.py:415
Finding
Report mode prints detected credentials and personal data to standard error<![CDATA[ ## Vulnerability Details **File Location**: `redact_kit.py:415-417` **Vulnerability Type**: Sensitive information exposure through diagnostic output **Risk Level**: Medium ### Vulnerable Code ```python if args.report: print("\n🔍 Report mode - showing matches:\n", file=sys.stderr) for match in result.matches[:10]: # First 10 print(f" [{match.pattern_name}] {match.original_value[:30]} → {match.placeholder}", file=sys.stderr) ``` ### Technical Analysis Report mode prints up to the first 30 characters of as many as ten detected original values. These values are precisely the data the application is intended to protect, including passwords, API keys, PII, and financial identifiers. Writing to standard error does not make the information private. Standard error is routinely captured by: - CI/CD systems. - Terminal session recording. - Container and orchestration logging. - Process supervisors. - Shell redirection. - Centralized monitoring and observability agents. For credentials shorter than or equal to 30 characters, the complete credential may be exposed. Longer credentials can still reveal a usable token prefix or enough information to assist correlation and targeted attacks. ### Attack Path 1. A user runs the tool with `--report` to inspect a sensitive file without writing redacted output. 2. The engine detects a password, API token, SSN, email address, or other sensitive value. 3. The CLI prints up to 30 characters of the original value to standard error. 4. The execution environment captures standard error in a terminal transcript, CI log, container log, or monitoring platform. 5. A person or service with access to those logs retrieves the sensitive value. 6. If the disclosed value is a credential, it can be used with all permissions already assigned to that credential. ### Impact Assessment The vulnerability exposes up to ten detected values per invocation through diagnostic channels. It does not grant additional local ...[truncated 307 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not print original sensitive values by default. - Report only the pattern name, category, sensitivity, source location, match length, and placeholder. - If identification is necessary, display a strongly masked representation that reveals only a minimal number of characters. - Consider using a keyed fingerprint for correlation without exposing the original value. - Require a clearly named explicit unsafe option before displaying plaintext, and emit a warning about log capture. - Ensure exceptions and debug messages also avoid embedding source text or original match values. - Add tests that capture stdout and stderr and assert that known secrets never appear in default or report-mode output. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
Findings (14)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The documented purpose emphasizes safe local redaction before sending content to AI, but the described behavior also includes restoration of original sensitive values from local mapping files, which is a separate high-risk capability. This mismatch is dangerous because users may trust the skill as a one-way privacy scrubber while it actually preserves and later re-injects secrets, increasing the chance of accidental disclosure or unsafe downstream handling.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
**Workaround:** 
- Store mapping files in encrypted folders (FileVault, BitLocker, LUKS)
- Manually encrypt JSON files (GPG, openssl)
- Restrict filesystem permissions (chmod 600)

---
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README promotes reversible redaction and quickly introduces mapping files that preserve the original secrets, but the core usage flow does not foreground that these mapping files are themselves highly sensitive artifacts. Users may treat the redacted output as safe while accidentally storing, syncing, or sharing the mapping file, which completely defeats the privacy goal and can re-expose all redacted data.

Lp3

Medium
Category
MCP Least Privilege
Confidence
82% confidence
Finding
The skill demonstrates file read/write capability by saving and loading redaction mappings, but it does not declare any tool scope or permissions. Undeclared file access is risky because it obscures the skill's actual capabilities from reviewers and users, especially when the files contain sensitive originals needed for restoration.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill instructs users to save a mapping file that links placeholders back to original sensitive values, but it provides no warning that this file effectively contains the secrets and PII in recoverable form. If that file is left unprotected, copied, logged, committed, or read by another process, the redaction is undermined and sensitive data can be fully exposed.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The engine stores reversible mappings of redacted content in process memory, and each mapping contains the original sensitive values. In a privacy-scrubbing tool, this materially increases exposure because secrets and PII remain accessible after redaction and may be disclosed through crashes, debugging, memory inspection, or later persistence to disk.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The mapping file serializes original sensitive values directly to disk, creating a concentrated plaintext record of secrets and PII. In the context of a privacy scrubber, this is especially dangerous because users may assume the tool reduces exposure, while the optional restoration feature can instead create a new high-value artifact that is easier to steal than the original distributed data.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The CLI displays matched sensitive substrings directly to stdout, which can re-expose the very secrets or PII the tool is meant to help identify. In shell histories, terminal logs, CI logs, screen recordings, or shared consoles, this behavior can leak credentials and personal data beyond the original input context.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The manifest describes a privacy scrubber that scans data and redacts PII, secrets, and sensitive information locally. This file implements the inverse capability: loading stored original values from mapping files and restoring redacted content back to its sensitive form, including batch restoration of directories. That restoration behavior is not reflected in the manifest's stated scope.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
A privacy scrubber's stated purpose is to reduce exposure of sensitive data before sending it to AI. The ability to restore whole files and directories to their original sensitive contents creates a data-recovery capability that is qualitatively different from scanning/redacting and is not explicitly justified by the manifest description.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The tool restores sensitive values and writes them to plaintext output files without any explicit warning, confirmation, or safety controls. In a privacy-focused skill, this can cause users to unintentionally re-materialize secrets, PII, or credentials on disk, increasing the chance of accidental disclosure through backups, shared directories, logs, or later AI uploads.

Scope Creep

Low
Category
Excessive Agency
Content
**USE AT YOUR OWN RISK.**

- The author(s) are NOT liable for any damages, losses, or consequences arising from 
  the use or misuse of this software — including but not limited to financial loss, 
  data loss, security breaches, business interruption, or any indirect/consequential damages.
- This software does NOT constitute financial, legal, trading, or professional advice.
- Users are solely responsible for evaluating whether this software is suitable for
Confidence
70% confidence
Finding
Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Intent-Code Divergence

Low
Confidence
88% confidence
Finding
Line L097 states `offset` tracks position changes from replacements, implying match coordinates are adjusted as text is modified. In reality, `offset` remains zero throughout, and replacements are instead made safely by sorting matches in reverse order, so the comment/documented intent contradicts the actual implementation.

Missing User Warnings

Low
Confidence
79% confidence
Finding
In single-file mode, the tool defaults to writing a new file using the input path plus '.redacted', and batch mode defaults to writing output unless report mode is enabled. While file-writing is part of the tool's purpose, the invocation path lacks a clear confirmation or prominent warning that execution will create output artifacts and possibly mapping files.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
redact_patterns.py:92