Back to skill

Security audit

OpenClaw TDD

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent local TDD helper, but its documentation overstates some features and its temporary report files should be handled more safely.

Install only if you want a local Python-focused TDD helper. Treat the advertised Jest, Go, mutation, and full cycle features as incomplete, and avoid relying on its fixed /tmp report files in multi-user or high-assurance environments until they are hardened.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/runner.py:65
Finding

Predictable Shared Temporary File Used for Pytest Reports

Content
View full analysis

Vulnerability Details

File Location: scripts/runner.py:65-85
Vulnerability Type: Predictable temporary file, symlink race, and stale report consumption
Risk Level: Medium

Vulnerable Code

python
# Try to get JSON output
cmd.append('--json-report')
cmd.append('--json-report-file=/tmp/pytest_report.json')

start_time = time.time()
result = subprocess.run(cmd, capture_output=True, text=True, cwd=self.root)
elapsed = time.time() - start_time

# Parse results
passed = result.returncode == 0

# Parse output
output = result.stdout + result.stderr

# Extract test counts from output
passed_count = output.count(' PASSED')
failed_count = output.count(' FAILED')
error_count = output.count(' ERROR')
skipped_count = output.count(' SKIPPED')

# Try to read JSON report
try:
    json_report = Path('/tmp/pytest_report.json')
    if json_report.exists():
        report_data = json.loads(json_report.read_text())
        passed_count = report_data.get('summary', {}).get('passed', passed_count)
        failed_count = report_data.get('summary', {}).get('failed', failed_count)
except:
    pass

Technical Analysis

The test runner always instructs pytest to write its JSON report to the globally predictable path /tmp/pytest_report.json. Shared temporary directories can be modified by other local users. An attacker can therefore create this pathname before the victim invokes the runner, including as a symbolic link to another file.

When the pytest JSON-report plugin opens the supplied path, it may follow a pre-existing symbolic link and truncate or overwrite the linked file with the privileges of the user running the Skill. The target must still be writable by that user, but the attacker can influence which writable file is affected.

The runner also does not remove an existing report before starting pytest or verify that a fresh report was generated. If pytest or the reporting plugin f ...[truncated 1692 chars]

Remediation
View remediation

Remediation Suggestions

  • Create a private, unpredictable report path with tempfile.TemporaryDirectory() or tempfile.NamedTemporaryFile().
  • Place the report inside a process-specific temporary directory with permissions restricted to the invoking user.
  • Pass the generated pathname to --json-report-file rather than using a fixed /tmp filename.
  • Delete or invalidate any old report before invoking pytest.
  • Read the report only if the subprocess completed and a fresh regular file was produced.
  • Where supported, verify that the report is not a symbolic link and is owned by the current user.
  • Clean up the temporary directory in a finally block.
  • Avoid a bare except; catch specific exceptions and report parsing or integrity failures.

Example hardening pattern:

python
import tempfile

with tempfile.TemporaryDirectory(prefix='tdd-pytest-') as temp_dir:
    report_path = Path(temp_dir) / 'pytest-report.json'
    cmd.extend([
        '--json-report',
        f'--json-report-file={report_path}',
    ])

    result = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        cwd=self.root,
    )

    if report_path.is_file():
        report_data = json.loads(report_path.read_text(encoding='utf-8'))

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/coverage.py:20
Finding

Predictable Shared Temporary File Used for Coverage Reports

Content
View full analysis

Vulnerability Details

File Location: scripts/coverage.py:20-45
Vulnerability Type: Predictable temporary file, symlink race, and stale coverage report consumption
Risk Level: Medium

Vulnerable Code

python
source = source_dir or 'src'

# Run pytest with coverage
cmd = [
    'python', '-m', 'pytest',
    '--cov=' + source,
    '--cov-report=term-missing',
    '--cov-report=json:/tmp/coverage.json',
    '-q'
]

result = subprocess.run(cmd, capture_output=True, text=True, cwd=self.root)

# Parse terminal output
output = result.stdout + result.stderr

# Extract coverage percentage
coverage_match = re.search(r'TOTAL\s+\d+\s+\d+\s+(\d+)%', output)
total_coverage = int(coverage_match.group(1)) if coverage_match else 0

# Parse JSON report
file_coverages = {}
try:
    json_path = Path('/tmp/coverage.json')
    if json_path.exists():
        data = json.loads(json_path.read_text())
        for filename, info in data.get('files', {}).items():
            file_coverages[filename] = {
                'coverage': info.get('percent_covered', 0),
                'missing_lines': info.get('missing_lines', [])
            }

Technical Analysis

Coverage output is always written to the fixed, globally visible path /tmp/coverage.json. The implementation does not securely create this file, verify its ownership or type, remove stale content, or ensure that the parsed report was generated by the current subprocess.

A local attacker can reserve this predictable pathname as a symbolic link. When the coverage plugin writes its JSON output, it may follow the link and overwrite a file writable by the victim. The same fixed path is also vulnerable to concurrent execution races.

If pytest or the coverage plugin fails before replacing the report, the code still checks whether /tmp/coverage.json exists and parses it. Consequently, a stale or attacker-created report can supply arbitra ...[truncated 1426 chars]

Remediation
View remediation

Remediation Suggestions

  • Generate coverage reports in a private temporary directory created with Python's tempfile module.
  • Use an unpredictable, process-specific report pathname rather than /tmp/coverage.json.
  • Parse the report only after verifying that the pytest subprocess produced a fresh regular file.
  • Validate report ownership and reject symbolic links where platform APIs permit.
  • Remove the temporary report reliably after parsing.
  • Treat subprocess or report-generation failures explicitly rather than accepting any pre-existing report.
  • Use separate temporary paths for concurrent invocations.

Example hardening pattern:

python
import tempfile

with tempfile.TemporaryDirectory(prefix='tdd-coverage-') as temp_dir:
    report_path = Path(temp_dir) / 'coverage.json'
    cmd = [
        'python', '-m', 'pytest',
        f'--cov={source}',
        '--cov-report=term-missing',
        f'--cov-report=json:{report_path}',
        '-q',
    ]

    result = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        cwd=self.root,
    )

    if report_path.is_file():
        data = json.loads(report_path.read_text(encoding='utf-8'))
Vulnerability Patterns
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

Advertising a full TDD assistant while lacking core features like coverage tracking, red-green-refactor guidance, and jest/go support creates a trust and safety issue. In agentic environments, capability inflation can bypass human scrutiny and lead downstream systems to grant this skill inappropriate authority or use it in incompatible repositories.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

Advertising a full TDD assistant while lacking core features like coverage tracking, red-green-refactor guidance, and jest/go support creates a trust and safety issue. In agentic environments, capability inflation can bypass human scrutiny and lead downstream systems to grant this skill inappropriate authority or use it in incompatible repositories.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

Advertising a full TDD assistant while lacking core features like coverage tracking, red-green-refactor guidance, and jest/go support creates a trust and safety issue. In agentic environments, capability inflation can bypass human scrutiny and lead downstream systems to grant this skill inappropriate authority or use it in incompatible repositories.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill documents commands that read files, write test files, and invoke shell-based test execution, but it does not declare any explicit tool scope such as permissions or allowed-tools. In an agent environment, this can cause the runtime to grant broader-than-expected capabilities or leave operators unable to evaluate what the skill is allowed to do, increasing the risk of unintended file modification or command execution.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This markdown file contains core user-facing instructions and feature descriptions in Chinese, while the manifest header is in English. Because the skill does not state that it is intentionally Chinese-only or offer a language/locale option, it creates a language policy concern under the natural-language policy rules.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The roadmap marks advertised features as not yet implemented, directly contradicting earlier sections that present them as available. This inconsistency is dangerous because it undermines operator trust and can cause users or orchestrators to depend on controls or workflows that do not actually exist.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/coverage.py (reported line 34)May include surrounding context.

python
'-q'
        ]

        result = subprocess.run(cmd, capture_output=True, text=True, cwd=self.root)

        # Parse terminal output
        output = result.stdout + result.stderr

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/runner.py (reported line 68)May include surrounding context.

python
'-q'
        ]

        result = subprocess.run(cmd, capture_output=True, text=True, cwd=self.root)

        # Parse terminal output
        output = result.stdout + result.stderr

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/runner.py (reported line 122)May include surrounding context.

python
'-q'
        ]

        result = subprocess.run(cmd, capture_output=True, text=True, cwd=self.root)

        # Parse terminal output
        output = result.stdout + result.stderr

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/coverage.py (reported line 90)May include surrounding context.

python
'-q'
        ]

        result = subprocess.run(cmd, capture_output=True, cwd=self.root)

        if result.returncode in (0, 5):  # 5 = no tests collected
            output_path = self.root / output_dir / 'index.html'

Static analysis

No suspicious patterns detected.