Back to skill

Security audit

OpenClaw Security

Security checks for vulnerabilities and agentic risk

Overview

This local PII audit skill is mostly coherent, but it includes file and directory deletion features that are too broadly scoped for review-free installation.

Install only if you are comfortable with a local PII scanner that writes audit metadata and masked matches to disk. Treat audit logs and .scan-cache.json as sensitive. Avoid --delete-after-read except for temp files created specifically for that scan, run cleanup.py with --dry-run first, and do not point --audit-dir at broad or unrelated directories.

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
scripts/audit_worker.py:338
Finding

Unrestricted File Deletion Through --delete-after-read

Content
View full analysis

Vulnerability Details

File Location: scripts/audit_worker.py:338-383
Vulnerability Type: Arbitrary caller-selected file deletion
Risk Level: High

Vulnerable Code

python
parser.add_argument('--file',
                    help='Read content from file instead of stdin')
parser.add_argument('--text',
                    help='Inline text to scan (WARNING: visible in process list)')
parser.add_argument('--delete-after-read', action='store_true',
                    help='Delete the --file after reading (for temp-file workflow)')
python
elif args.file:
    try:
        with open(args.file, 'r', encoding='utf-8') as f:
            text = f.read(read_limit)
    except OSError as exc:
        print(f'[ERROR] Cannot read file {args.file}: {exc}', file=sys.stderr)
        sys.exit(1)
    if args.delete_after_read:
        try:
            os.remove(args.file)
        except OSError as exc:
            print(f'[WARN] Could not delete temp file {args.file}: {exc}',
                  file=sys.stderr)

Technical Analysis

The --file option accepts an unrestricted filesystem path. When --delete-after-read is present, the worker deletes that path without verifying that it is a temporary file created specifically for the scan.

The implementation does not:

  • Restrict deletion to a dedicated temporary directory.
  • Verify that the file was created or is owned by the current workflow.
  • Reject symbolic links or non-regular files.
  • Canonicalize the path and enforce containment within an approved root.
  • Require separate authorization for deletion of an existing file.
  • Delay deletion until input validation and scan processing have succeeded.

Consequently, the option provides a general file-deletion primitive for any file that the process can read and delete.

Attack Path

  1. An attacker, untrusted automation input, or confused Agent influences the --file ...[truncated 921 chars]
Remediation
View remediation

Remediation Suggestions

  • Create temporary scan files inside a dedicated private directory controlled by the worker.
  • Record securely created temporary files and only allow deletion of paths present in that record.
  • Resolve the candidate path and verify that it remains beneath the approved temporary root.
  • Reject symbolic links, directories, devices, and other non-regular files.
  • Open files using platform-appropriate no-follow protections where available.
  • Prefer accepting an already-open file descriptor instead of reopening and deleting a caller-supplied pathname.
  • Perform deletion only after successful input validation and processing.
  • Separate scanning from deletion authorization so that reading a file does not implicitly authorize its destruction.
  • Add tests proving that paths outside the temporary root and symbolic-link targets cannot be deleted.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/cleanup.py:61
Finding

Unrestricted Audit Root Permits Recursive Deletion of Unrelated Directories

Content
View full analysis

Vulnerability Details

File Location: scripts/cleanup.py:61-105
Vulnerability Type: Unsafe recursive directory deletion
Risk Level: High

Vulnerable Code

python
def cleanup(audit_dir, days, dry_run=False):
    audit_path = Path(audit_dir)
    if not audit_path.exists():
        print(f'Audit directory not found: {audit_dir}')
        return

    cutoff = datetime.now(timezone.utc) - timedelta(days=days)
    removed = 0

    for entry in sorted(audit_path.iterdir()):
        if not entry.is_dir() or not DATE_PATTERN.match(entry.name):
            continue
        try:
            dir_date = datetime.strptime(entry.name, '%Y-%m-%d').replace(
                tzinfo=timezone.utc)
        except ValueError:
            continue

        if dir_date < cutoff:
            if dry_run:
                print(f'[DRY-RUN] Would remove: {entry}')
            else:
                shutil.rmtree(entry)
                print(f'[REMOVED] {entry}')
            removed += 1
python
def main():
    parser = argparse.ArgumentParser(description='Cleanup old audit logs')
    parser.add_argument('--days', type=int, default=7,
                        help='Retention period in days (default: 7)')
    parser.add_argument('--audit-dir', default=DEFAULT_AUDIT_DIR,
                        help='Audit directory path')
    parser.add_argument('--dry-run', action='store_true',
                        help='Show what would be removed without deleting')
    args = parser.parse_args()

    cleanup(args.audit_dir, args.days, args.dry_run)

Technical Analysis

The cleanup root can be selected through --audit-dir, and the program recursively deletes child directories based only on a date-shaped name and retention calculation.

There is no validation that the selected root is an authentic audit directory. Targets are not required to contain events.ndjson or any other expe ...[truncated 1403 chars]

Remediation
View remediation

Remediation Suggestions

  • Reject retention values below zero and establish a reasonable upper bound.
  • Canonicalize the configured audit root before performing any traversal.
  • Refuse dangerous roots such as a filesystem root, home directory, project root, or empty path.
  • Create a private audit-root marker during initialization and require that marker before cleanup.
  • Constrain cleanup to a configured and trusted audit base rather than accepting unrestricted paths.
  • Verify that every target is a direct child of the canonical audit root.
  • Require expected audit artifacts, such as events.ndjson, before considering a directory eligible.
  • Reject symbolic-link targets and avoid following links during validation or deletion.
  • Log a deletion plan and require explicit confirmation when a non-default root is used.
  • Add tests for negative retention, malicious roots, symbolic links, missing audit markers, and unrelated date-named directories.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/file_lock.py:37
Finding

Timeout-Based Lock Removal Breaks Mutual Exclusion

Content
View full analysis

Vulnerability Details

File Location: scripts/file_lock.py:37-79
Vulnerability Type: Race condition and unsafe lock ownership handling
Risk Level: Medium

Vulnerable Code

python
def acquire(self) -> None:
    deadline = time.monotonic() + self.timeout
    while True:
        try:
            self._fd = os.open(
                self.lock_path,
                os.O_CREAT | os.O_EXCL | os.O_WRONLY,
            )
            return  # lock acquired
        except (FileExistsError, OSError):
            if time.monotonic() >= deadline:
                # Timeout — attempt stale-lock recovery once
                self._remove_stale()
                try:
                    self._fd = os.open(
                        self.lock_path,
                        os.O_CREAT | os.O_EXCL | os.O_WRONLY,
                    )
                    return
                except (FileExistsError, OSError):
                    raise FileLockTimeout(
                        f'Could not acquire lock: {self.lock_path}'
                    )
            time.sleep(self.poll_interval)

def release(self) -> None:
    if self._fd is not None:
        try:
            os.close(self._fd)
        except OSError:
            pass
        self._fd = None
    self._remove_stale()

def _remove_stale(self) -> None:
    try:
        os.remove(self.lock_path)
    except OSError:
        pass

Technical Analysis

The lock implementation treats expiration of a local timeout as evidence that the existing lock is stale. A timeout does not establish that the owning process has terminated or abandoned its critical section.

A waiting process can therefore delete a lock file that is still owned by another live process and create a replacement lock. In addition, release() removes the lock pathname without verifying that the file still represents the releasing instance. An earlier owner ...[truncated 1272 chars]

Remediation
View remediation

Remediation Suggestions

  • Use native advisory locking, such as fcntl on POSIX and an appropriate Windows locking API.
  • Do not remove an existing lock solely because a wait timeout elapsed.
  • If file-based locking must be retained, write a cryptographically random owner token and process metadata into the lock.
  • Remove a lock only when the stored owner token matches the releasing instance.
  • Determine staleness using verified owner liveness and a conservative age policy.
  • Preserve the lock file descriptor for the entire critical section.
  • Use atomic replacement for cache writes to avoid partial JSON files.
  • Add multiprocess tests where one holder exceeds the timeout and confirm that no second holder enters concurrently.
  • Add tests proving that an old holder cannot remove a newer holder's lock.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/audit_worker.py:216
Finding

Deterministic Truncated Content Hashes Enable Offline PII Confirmation

Content
View full analysis

Vulnerability Details

File Location: scripts/audit_worker.py:216-230
Vulnerability Type: Recoverable fingerprinting of low-entropy sensitive content
Risk Level: Medium

Vulnerable Code

python
content_hash = hashlib.sha256(text.encode('utf-8')).hexdigest()[:16]
now = datetime.now(timezone.utc).isoformat()
base_fields = {
    "event_id": str(uuid.uuid4()),
    "session_id": session_id,
    "source_type": source_type,
    "detector_version": VERSION,
    "content_hash": content_hash,
    "input_chars": input_chars,
    "truncated": truncated,
    "created_at": now,
}

Technical Analysis

The worker persists the first 16 hexadecimal characters of an unsalted SHA-256 digest of the complete scanned text. This is a deterministic 64-bit fingerprint.

Cryptographic hashing does not provide confidentiality for low-entropy or predictable inputs. Phone numbers, identifiers, email addresses, and short templated messages may have sufficiently small candidate spaces for offline dictionary comparison. An attacker with access to audit records can hash likely candidates and determine whether a candidate matches a stored fingerprint.

Truncating the digest also increases collision probability. Because the same value is used for cache deduplication, a collision can cause distinct content to be treated as previously scanned during the applicable cache period.

Attack Path

  1. An attacker obtains read access to .scan-cache.json or an events.ndjson audit file.
  2. The attacker identifies a likely message format or a constrained set of possible PII values.
  3. Candidate messages are generated offline.
  4. SHA-256 is calculated for every candidate and truncated to the first 16 hexadecimal characters.
  5. Candidate fingerprints are compared with logged content_hash values.
  6. A match confirms, with high probability for a constrained dictionary, that the guessed content was scanned.
  7. Indep ...[truncated 581 chars]
Remediation
View remediation

Remediation Suggestions

  • Replace the plain digest with full-length HMAC-SHA-256.
  • Generate a cryptographically random local HMAC key and store it with owner-only permissions outside the audit records.
  • Use separate domain-separated keys or HMAC contexts for persistent logging and cache deduplication.
  • Do not truncate the HMAC when it is used as a cache key.
  • Consider omitting persistent content fingerprints entirely if audit correlation does not require them.
  • Rotate the HMAC key according to the retention policy when long-term cross-record correlation is unnecessary.
  • Document that hashed low-entropy PII remains sensitive metadata and protect audit and cache files accordingly.
  • Add tests confirming that identical content remains deduplicated while raw SHA-256 candidate matching no longer works without the secret key.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (18)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The skill is presented primarily as a PII scanner, but the documented behavior also includes local log storage, cache mutation, and deletion of files/directories through cleanup. That mismatch can cause operators to invoke it in higher-trust contexts than warranted, leading to unexpected modification or deletion of local data under the guise of a security audit.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The skill is presented primarily as a PII scanner, but the documented behavior also includes local log storage, cache mutation, and deletion of files/directories through cleanup. That mismatch can cause operators to invoke it in higher-trust contexts than warranted, leading to unexpected modification or deletion of local data under the guise of a security audit.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill is presented primarily as a PII scanner, but the documented behavior also includes local log storage, cache mutation, and deletion of files/directories through cleanup. That mismatch can cause operators to invoke it in higher-trust contexts than warranted, leading to unexpected modification or deletion of local data under the guise of a security audit.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill is presented primarily as a PII scanner, but the documented behavior also includes local log storage, cache mutation, and deletion of files/directories through cleanup. That mismatch can cause operators to invoke it in higher-trust contexts than warranted, leading to unexpected modification or deletion of local data under the guise of a security audit.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The skill is presented primarily as a PII scanner, but the documented behavior also includes local log storage, cache mutation, and deletion of files/directories through cleanup. That mismatch can cause operators to invoke it in higher-trust contexts than warranted, leading to unexpected modification or deletion of local data under the guise of a security audit.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill is presented primarily as a PII scanner, but the documented behavior also includes local log storage, cache mutation, and deletion of files/directories through cleanup. That mismatch can cause operators to invoke it in higher-trust contexts than warranted, leading to unexpected modification or deletion of local data under the guise of a security audit.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The skill is presented primarily as a PII scanner, but the documented behavior also includes local log storage, cache mutation, and deletion of files/directories through cleanup. That mismatch can cause operators to invoke it in higher-trust contexts than warranted, leading to unexpected modification or deletion of local data under the guise of a security audit.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The skill is presented primarily as a PII scanner, but the documented behavior also includes local log storage, cache mutation, and deletion of files/directories through cleanup. That mismatch can cause operators to invoke it in higher-trust contexts than warranted, leading to unexpected modification or deletion of local data under the guise of a security audit.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill documentation describes shell execution, file reads/writes, environment-variable configuration, and deletion behavior, but the manifest declares no explicit tool scope or permission boundaries. In an agent setting, undocumented or unbounded access increases the chance of unintended filesystem or shell actions, especially because the skill handles sensitive content and retention cleanup.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The invocation guidance uses broad phrases like 'security scan', 'background audit', and 'privacy audit', which could cause the agent to trigger the skill on large amounts of session, prompt, context, or knowledge-base content by default. In a PII-oriented skill, overbroad activation increases the risk of unnecessary processing, storage of metadata, and local persistence of sensitive-content derivatives.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill manifest emphasizes scanning user input, prompts, context, and knowledge-base content for PII across regions and data types. This file performs no detection or scanning logic; it removes old audit directories via shutil.rmtree and prunes cache entries, which is materially different from the described operational behavior.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest describes a multi-region async PII detection skill that scans session content for sensitive data. This script instead deletes dated audit-log directories and rewrites a scan-cache file, which is an administrative retention/maintenance function rather than a direct PII-detection capability.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This code detects and extracts social account identifiers from arbitrary input text, which is privacy-relevant user data. The file contains no confirmation prompt, user-facing logging, or explanatory comment/docstring near the detection flow that warns users their text will be scanned for social account information.

Content

No source excerpt is available for this finding.

Unbounded Resource Access

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Content

Scanner excerpt · tests/test_all.py (reported line 738)May include surrounding context.

python
with open(self.lock_path, 'w') as f:
            f.write('stale')
        # Should recover (via timeout + stale removal)
        lock = FileLock(self.lock_path, timeout=0.2, poll_interval=0.05)
        lock.acquire()
        lock.release()

Unbounded Resource Access

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Content

Scanner excerpt · tests/test_all.py (reported line 746)May include surrounding context.

python
with open(self.lock_path, 'w') as f:
            f.write('stale')
        # Should recover (via timeout + stale removal)
        lock = FileLock(self.lock_path, timeout=0.2, poll_interval=0.05)
        lock.acquire()
        lock.release()

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · tests/test_all.py (reported line 989)May include surrounding context.

python
# The cutoff should use UTC
        now_utc = datetime.now(timezone.utc)
        # A directory from 8 days ago should be cleaned with 7-day retention
        eight_ago = (now_utc - __import__('datetime').timedelta(days=8))
        old_name = eight_ago.strftime('%Y-%m-%d')
        old_dir = Path(self.tmpdir) / old_name
        old_dir.mkdir()

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

The document includes a dedicated Chinese overview and the rest of the README continues in English, but it does not explicitly state that language selection is optional or user-driven. Under the policy for natural-language violations, forcing a specific language or locale without opt-in can be an issue unless the locale constraint is clearly justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The file presents a substantial instruction section under '中文速览(PII 审计)' in Chinese, but does not state that language selection is optional or region-specific. Under the policy, forcing a specific language or locale without user opt-in can be a natural-language policy violation.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.