Back to skill

Security audit

Zero2ai Security Audit

Security checks for vulnerabilities and agentic risk

Overview

This security-audit skill is mostly purpose-aligned, but it can copy detected secrets into logs and has fail-open checks that can give users false confidence.

Review this skill before installing in any workflow that handles real credentials. If used, treat its output as sensitive, avoid saving JSON or terminal logs where others can read them, do not rely on a clean exit as proof that all files were scanned, and replace the hardcoded Aladdin path and reporting instruction with your own local configuration.

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

Warning
Location
scripts/audit.py:326
Finding
MEDIUM findings do not block documented publishing workflows<![CDATA[ ## Vulnerability Details **File Location**: `scripts/audit.py`, lines 326-331 **Vulnerability Type**: Fail-open security gate caused by inconsistent severity handling **Risk Level**: Medium ### Complete Code Snippet ```python has_high = bool(grouped.get('HIGH')) has_medium = bool(grouped.get('MEDIUM')) if has_high or (args.strict and (has_medium or grouped.get('LOW'))): sys.exit(1) sys.exit(0) ``` The documented commands in `SKILL.md` do not include `--strict`, although the documentation states that both HIGH and MEDIUM findings should block publishing or pushing. ### Technical Analysis The exit-status logic only returns failure when a HIGH finding exists, or when `--strict` is enabled and a MEDIUM or LOW finding exists. Consequently, MEDIUM findings produce exit code `0` under every documented commit, push, and publish workflow. This contradicts the documented contract in `SKILL.md`, which states that exit code `1` represents HIGH or MEDIUM findings and that MEDIUM findings must be fixed before publication. Systems integrating this scanner are likely to trust the process exit status rather than parse its human-readable output. ### Attack Path 1. An attacker or contributor adds content that matches a MEDIUM rule, such as an absolute home path or a refresh token pattern. 2. The documented command is run without `--strict`, for example: ```bash python3 scripts/audit.py /path/to/skill ``` 3. The scanner prints the MEDIUM finding. 4. The final condition evaluates to false because no HIGH finding exists and `args.strict` is false. 5. The process exits with status `0`. 6. A commit hook, CI pipeline, or publishing workflow interprets the scan as successful and permits the unsafe content to proceed. ### Impact Assessment This issue does not directly grant operating-system privileges or execute attacker-controlled code. Its scope is the integrity of the security gate: MEDIUM-risk content can pass automated validation and be committed, pus ...[truncated 155 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Change the default exit logic so HIGH and MEDIUM findings always block, while `--strict` additionally makes LOW findings blocking: ```python has_high = bool(grouped.get('HIGH')) has_medium = bool(grouped.get('MEDIUM')) has_low = bool(grouped.get('LOW')) if has_high or has_medium or (args.strict and has_low): sys.exit(1) sys.exit(0) ``` Add automated tests covering all combinations of HIGH, MEDIUM, and LOW findings with and without `--strict`. Ensure the implementation, command examples, and documented exit-code contract remain consistent. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/audit.py:234
Finding
The node_modules detection rule is unreachable<![CDATA[ ## Vulnerability Details **File Location**: `scripts/audit.py`, lines 17-18, 112-118, 219-223, and 234-237 **Vulnerability Type**: Unreachable security control caused by conflicting skip and detection logic **Risk Level**: Medium ### Complete Code Snippet ```python SKIP_DIRS = {'.git', 'node_modules', '__pycache__', '.venv', 'venv', 'dist', 'build', '.next'} ``` ```python { "id": "node-modules-in-skill", "severity": "MEDIUM", "description": "node_modules committed — should be gitignored", "path_pattern": r'node_modules/', "file_level": True, }, ``` ```python def should_skip(path): p = Path(path) for part in p.parts: if part in SKIP_DIRS: return True ext = ''.join(p.suffixes) return ext in SKIP_EXTENSIONS ``` ```python for f in files: if should_skip(f): continue all_findings.extend(scan_path_patterns(f)) ``` ### Technical Analysis The scanner defines a file-level rule intended to identify files under `node_modules/`. However, `node_modules` is also present in `SKIP_DIRS`. During directory scanning, `should_skip()` is called before `scan_path_patterns()`. Every file whose path contains a `node_modules` component is therefore skipped before the corresponding detection rule can run. This is a deterministic logic flaw rather than a probabilistic regex weakness: the control cannot trigger during ordinary recursive directory scans. ### Attack Path 1. A contributor places dependency files or arbitrary content under `node_modules/`. 2. The target directory is scanned recursively. 3. Each file under that directory is enumerated by `rglob('*')`. 4. `should_skip()` observes the `node_modules` path component and returns `True`. 5. The loop executes `continue` before calling `scan_path_patterns()`. 6. No `node-modules-in-skill` finding is emitted. 7. If no other blocking finding exists, the scan exits successfully. ### Impact Assessment No additional system privilege is directly obta ...[truncated 285 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Perform prohibited-path detection before applying content-scan exclusions: ```python for f in files: all_findings.extend(scan_path_patterns(f)) if should_skip(f): continue content = f.read_text() all_findings.extend(scan_file_content(f, content)) ``` A stronger design is to inspect directory names directly and emit one finding per prohibited directory, without enumerating or reading its entire contents. Separate the concepts of “do not scan file content” and “do not inspect this path for policy violations.” Add a regression test containing `node_modules/example/index.js` and verify that a MEDIUM finding and nonzero exit status are produced. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/audit.py:248
Finding
Git command failures are silently reported as clean scans<![CDATA[ ## Vulnerability Details **File Location**: `scripts/audit.py`, lines 248-256 **Vulnerability Type**: Fail-open subprocess error handling **Risk Level**: Medium ### Complete Code Snippet ```python def scan_git_files(mode='staged'): if mode == 'staged': result = subprocess.run(['git', 'diff', '--cached', '--name-only'], capture_output=True, text=True) else: result = subprocess.run(['git', 'diff', 'HEAD~1', '--name-only'], capture_output=True, text=True) files = [f.strip() for f in result.stdout.split('\n') if f.strip()] all_findings = [] for f in files: if os.path.exists(f): all_findings.extend(scan_directory(f)) return all_findings ``` ### Technical Analysis The return code and standard error from both Git subprocesses are ignored. If Git fails, `stdout` can be empty, which is converted into an empty file list. The scanner then returns no findings and ultimately prints a clean result with exit code `0`. Relevant failure conditions include execution outside a Git worktree, an unavailable `HEAD~1` in a repository with only one commit, repository corruption, or other Git errors. A security gate must distinguish “nothing unsafe was found” from “the requested scan could not be performed.” The use of a fixed argument list avoids shell command injection; the vulnerability is specifically the fail-open error handling. ### Attack Path 1. The scanner is invoked with `--staged` from an unintended working directory, or with `--last-commit` in a repository where `HEAD~1` does not exist. 2. The Git command returns a nonzero status and writes diagnostic information to stderr. 3. The implementation ignores both the nonzero status and stderr. 4. Empty stdout becomes an empty file list. 5. No files are scanned and no findings are returned. 6. The caller receives a clean message and exit code `0`. 7. A commit, push, or publication process proceeds despite the audit never examining the intended changes. ...[truncated 318 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Require successful subprocess completion and fail closed: ```python result = subprocess.run( command, capture_output=True, text=True, check=False, ) if result.returncode != 0: error = result.stderr.strip() or "Git command failed" print(f"Audit error: {error}", file=sys.stderr) sys.exit(2) ``` Also: - Resolve and validate the intended repository root before scanning. - Handle initial-commit repositories explicitly. - Consider using null-delimited Git output to safely support unusual filenames. - Add tests for execution outside a repository, missing `HEAD~1`, and other nonzero Git results. - Ensure operational errors always use exit code `2`, as documented. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/audit.py:234
Finding
Unreadable files are silently omitted from the audit<![CDATA[ ## Vulnerability Details **File Location**: `scripts/audit.py`, lines 234-243 **Vulnerability Type**: Fail-open file-processing error handling **Risk Level**: Medium ### Complete Code Snippet ```python for f in files: if should_skip(f): continue all_findings.extend(scan_path_patterns(f)) try: content = f.read_text(errors='ignore') all_findings.extend(scan_file_content(f, content)) except Exception: pass return all_findings ``` ### Technical Analysis All exceptions raised while reading or scanning a file are caught and discarded. The scanner neither records that the file was skipped nor returns an operational error. Permission errors, filesystem races, I/O failures, and unexpected scanning exceptions therefore become indistinguishable from successful clean scans. In addition, `errors='ignore'` silently discards undecodable byte sequences. While useful for robustness, this behavior can remove bytes from content before pattern matching and further reduce confidence that the complete file was inspected. Security validation should fail closed when an in-scope file cannot be scanned. ### Attack Path 1. An in-scope file containing sensitive content is present in the target tree. 2. The file is made unreadable to the audit process, changes during the scan, or triggers another read/scanning exception. 3. `read_text()` or `scan_file_content()` raises an exception. 4. The broad `except Exception` handler discards the error. 5. The scanner continues without reporting the omitted file. 6. If no other blocking finding exists, the process exits with status `0`. 7. The unexamined file can remain in content that is subsequently committed or published. ### Impact Assessment No new operating-system privileges are granted. The impact is a bypass of content inspection for one or more files, limited by the attacker's ability to influence file state or trigger a read failure. Secrets and prohibited patterns in omitte ...[truncated 78 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Replace the broad silent exception handler with explicit failure reporting: ```python try: content = f.read_text(encoding='utf-8', errors='strict') all_findings.extend(scan_file_content(f, content)) except (OSError, UnicodeError) as exc: raise RuntimeError(f"Unable to scan {f}: {type(exc).__name__}") from exc ``` At the command boundary, report a sanitized error and exit with status `2`. If binary or non-UTF-8 files are intentionally unsupported, detect and report that policy explicitly rather than silently omitting content. Maintain a count of discovered, scanned, skipped-by-policy, and failed files, and add tests proving that unreadable files cannot result in a clean exit. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/audit.py:190
Finding
Detected secrets are exposed in console and JSON output<![CDATA[ ## Vulnerability Details **File Location**: `scripts/audit.py`, lines 190-199 and 318-321 **Vulnerability Type**: Plaintext sensitive-data exposure through diagnostic output **Risk Level**: Medium ### Complete Code Snippet ```python findings.append({ "id": pattern['id'], "severity": pattern['severity'], "description": pattern['description'], "file": str(filepath), "line": lineno, "match": line.strip()[:120], "value_preview": value[:40] + ('...' if len(value) > 40 else ''), }) ``` ```python if item['value_preview']: print(f" → Value: {item['value_preview']}") print(f" → {item['match'][:100]}") ``` JSON output serializes the same sensitive fields: ```python print(json.dumps({'findings': unique, 'summary': {k: len(v) for k, v in grouped.items()}}, indent=2)) ``` ### Technical Analysis When a secret is detected, the scanner stores up to the first 40 characters of the matched value and up to 120 characters of the complete source line. Human-readable output prints the value preview and up to 100 characters of the line. JSON mode serializes both fields. Many credentials are 40 characters or shorter, so `value_preview` can disclose the full secret. Even when a token is longer, the source-line excerpt may contain the full value or enough material to facilitate misuse. Secret scanners should minimize replication of sensitive values because CI logs, terminal captures, Agent transcripts, and generated audit artifacts often have broader retention and access than the source file. ### Attack Path 1. A repository contains a credential matching one of the scanner's secret rules. 2. The scanner identifies the credential. 3. It copies the value and containing line into the finding object. 4. Human-readable or JSON output is captured by CI, an orchestration platform, a terminal logger, or an Agent transcript. 5. A user with access to those logs retrieves the complete secret or a substantial usable prefix. 6. If the credential ...[truncated 506 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not retain or print the matched value or complete source line. Findings should normally contain only: - Rule identifier - Severity - File path - Line number - A generic description For correlation, use a non-reversible keyed fingerprint rather than plaintext. If a visual hint is essential, reveal only a very small fixed number of boundary characters and redact the remainder: ```python def redact(value): if len(value) <= 8: return "[REDACTED]" return f"{value[:2]}...[REDACTED]...{value[-2:]}" ``` Remove the `match` field from normal and JSON output, or replace the sensitive span before storage. Treat JSON as sensitive by default, document secure log handling, prevent untrusted users from accessing scan artifacts, and rotate any credential already exposed in retained logs. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (9)

Credential Access

High
Category
Privilege Escalation
Content
---
name: zero2ai-security-audit
description: Security auditing for git commits, repos, and skills before publishing. Run automatically before any `git commit`, `git push`, or `clawhub publish`. Detects hardcoded secrets, API keys, tokens, absolute paths, committed node_modules, .env files, and other sensitive patterns. Use when reviewing code for security issues, pre-publishing skills, or investigating a potential secret exposure.
---

# Security Audit
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
---
name: zero2ai-security-audit
description: Security auditing for git commits, repos, and skills before publishing. Run automatically before any `git commit`, `git push`, or `clawhub publish`. Detects hardcoded secrets, API keys, tokens, absolute paths, committed node_modules, .env files, and other sensitive patterns. Use when reviewing code for security issues, pre-publishing skills, or investigating a potential secret exposure.
---

# Security Audit
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
{
        "id": "refresh-token",
        "severity": "MEDIUM",
        "description": "Possible refresh/access token value",
        "regex": r'(Atzr|Atza|ATNR)\|[A-Za-z0-9+/\|]{40,}',
    },
    {
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill instructs users to run a local Python script and references capabilities consistent with shell, file access, and environment interaction, but it does not declare any explicit tool scope or permissions boundary. This can cause the skill to operate with broader-than-expected authority, reducing transparency and increasing the chance of unsafe execution in automation contexts.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def scan_git_files(mode='staged'):
    if mode == 'staged':
        result = subprocess.run(['git', 'diff', '--cached', '--name-only'], capture_output=True, text=True)
    else:
        result = subprocess.run(['git', 'diff', 'HEAD~1', '--name-only'], capture_output=True, text=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
if mode == 'staged':
        result = subprocess.run(['git', 'diff', '--cached', '--name-only'], capture_output=True, text=True)
    else:
        result = subprocess.run(['git', 'diff', 'HEAD~1', '--name-only'], capture_output=True, text=True)

    files = [f.strip() for f in result.stdout.split('\n') if f.strip()]
    all_findings = []
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Low
Confidence
94% confidence
Finding
The documentation reveals a specific absolute local path in a user's home directory, which unnecessarily discloses workstation structure and username information. While not directly exploitable on its own, such environmental details can aid targeting, social engineering, or path-dependent attacks when combined with other information.

Natural-Language Policy Violations

Low
Confidence
78% confidence
Finding
The instruction 'Report to Aladdin immediately' is a natural-language policy constraint embedded in the skill documentation. It hard-codes an organization-specific escalation target without documenting context, alternatives, or opt-in, which can violate policy expectations when the skill is reused in other environments.

Missing User Warnings

Low
Confidence
83% confidence
Finding
This code invokes external git commands via subprocess.run to inspect staged or last-commit files. Although the operation is non-destructive, the file provides no inline comment, log output, or other user disclosure around this shell execution path.