Back to skill

Security audit

Openclaw Action

Security checks for vulnerabilities and agentic risk

Overview

This security-scanning action is purpose-aligned overall, but it fetches and runs mutable remote scanner code while making inaccurate trust claims, so it needs review before use.

Review before installing. Prefer a version that vendors the scanner code or pins each downloaded scanner to immutable commits with verified hashes, and require corrected documentation. If you use this version, run it in an isolated CI job with read-only repository permissions, no unrelated secrets, and the narrowest workspace path practical.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T03 · Remote Payload Retrieval and Execution

Error
Location
action.yml:49
Finding

Unpinned Remote Scanner Scripts Are Downloaded and Executed

Content
View full analysis

Vulnerability Details

File Location: action.yml:49-58, with execution occurring through action.yml:64-72 and scripts/scan.py:23-30, 159-165
Vulnerability Type: Remote payload retrieval and execution
Risk Level: High

Vulnerable Code

yaml
- name: Download OpenClaw scanners
  shell: bash
  run: |
    mkdir -p ${{ runner.temp }}/openclaw-scanners

    # Download scanner scripts directly from GitHub
    curl -sL https://raw.githubusercontent.com/AtlasPA/openclaw-sentry/main/scripts/sentry.py \
      -o ${{ runner.temp }}/openclaw-scanners/sentry.py
    curl -sL https://raw.githubusercontent.com/AtlasPA/openclaw-bastion/main/scripts/bastion.py \
      -o ${{ runner.temp }}/openclaw-scanners/bastion.py
    curl -sL https://raw.githubusercontent.com/AtlasPA/openclaw-egress/main/scripts/egress.py \
      -o ${{ runner.temp }}/openclaw-scanners/egress.py

The downloaded files are subsequently made available to the local orchestrator:

yaml
- name: Run security scans
  id: scan
  shell: bash
  env:
    WORKSPACE: ${{ inputs.workspace }}
    SCANNERS_DIR: ${{ runner.temp }}/openclaw-scanners
  run: |
    python3 "${{ github.action_path }}/scripts/scan.py"

The orchestrator executes the downloaded scripts:

python
result = subprocess.run(
    [sys.executable, str(scanner_path), command, "-w", str(workspace), "--json"],
    capture_output=True,
    text=True,
    timeout=300,
)

The Bastion scanner is similarly executed:

python
result = subprocess.run(
    [sys.executable, str(bastion_path), "--workspace", str(workspace), "scan", "--json"],
    capture_output=True,
    text=True,
    timeout=300,
)

Technical Analysis

The Action retrieves three Python programs from mutable main branches and executes them during every workflow run. The URLs are not pinned to immutable reviewed commit hashes, and the downloaded content is not authenticated through a hard-coded checksum or cryptographic signature.

Consequently, the ...[truncated 2724 chars]

Remediation
View remediation

Remediation Suggestions

  1. Vendor the scanner implementations into this repository. Review and distribute the exact source code executed by the Action so that the audited package and effective runtime payload are identical.

  2. If downloading remains unavoidable, use immutable references. Pin each scanner URL to a full Git commit SHA rather than main or a movable tag.

  3. Verify integrity before execution. Maintain reviewed SHA-256 digests or cryptographic signatures in this repository, verify every downloaded file, and abort on any mismatch.

  4. Make downloads fail safely. Use options such as:

    bash
    curl --fail --show-error --silent --location \
      "https://raw.githubusercontent.com/OWNER/REPO/FULL_COMMIT_SHA/scripts/scanner.py" \
      -o "$destination"
    

    Stop the workflow if any download fails or produces unexpected content.

  5. Reduce ambient permissions. Recommend or enforce a read-only workflow token where feasible:

    yaml
    permissions:
      contents: read
    

    Avoid exposing deployment credentials, package-publishing tokens, or unrelated secrets to the scan job.

  6. Isolate scanner execution. Run scanners in a sandbox or container with a read-only workspace, no unnecessary host mounts, no secrets, and outbound networking disabled.

  7. Validate the execution target. Ensure scanner paths are regular files in the expected directory and reject symlinks or unexpected file types before invocation.

  8. Review updates explicitly. Introduce scanner upgrades as ordinary reviewed changes that update vendored source or pinned commits and expected hashes.

T08 · Insecure Dependencies

Warning
Location
README.md:114
Finding

Documentation Misrepresents Runtime Network and Supply-Chain Trust

Content
View full analysis

Vulnerability Details

File Location: README.md:114-120
Vulnerability Type: Inaccurate dependency and network trust claims
Risk Level: Medium

Vulnerable Documentation

markdown
## Trust Model

This action is designed to be auditable:

- **No binaries** — All logic is plain-text Python, readable in [scripts/scan.py](scripts/scan.py)
- **No dependencies** — Python stdlib only. No `pip install`, no supply chain risk
- **No network calls** — Scanners run locally. Nothing phones home
- **No persistence** — Downloads fresh scanner scripts each run, doesn't cache or store anything

The scanner scripts are fetched directly from the individual tool repositories:
- [openclaw-sentry](https://github.com/AtlasPA/openclaw-sentry)
- [openclaw-bastion](https://github.com/AtlasPA/openclaw-bastion)
- [openclaw-egress](https://github.com/AtlasPA/openclaw-egress)

Technical Analysis

The statements that the Action has “no supply chain risk” and makes “no network calls” conflict with its implementation. action.yml performs three network downloads at runtime and relies on externally maintained, mutable scanner programs.

“Nothing phones home” may be intended to describe the scanners' expected handling of scanned data, but that property cannot be verified from this package because the scanner sources are fetched dynamically and are not pinned or authenticated.

The phrase “downloads fresh scanner scripts each run” describes the behavior but understates its security significance: freshness from mutable branches makes execution non-reproducible and allows the runtime payload to change without a corresponding update to this Action.

Attack Path

  1. A user evaluates the Action based on the documented claim that it has no network calls or supply-chain risk.
  2. The user grants the scan job access to a sensitive repository or leaves broader workflow-token permissions and secrets available.
  3. At runtime, the Action retrieves mutable external programs th ...[truncated 686 chars]
Remediation
View remediation

Remediation Suggestions

  1. Replace “No dependencies” with a precise explanation that the local orchestrator uses only the Python standard library but currently relies on three externally downloaded scanner scripts.
  2. Replace “No network calls” with an explicit list of runtime network requests and their purpose.
  3. Remove the claim of “no supply chain risk.”
  4. Document whether scanner code can make outbound requests and which technical control enforces that property.
  5. State that external code is pinned and integrity-verified after implementing those controls.
  6. Document the minimum recommended GitHub token permissions and advise users not to expose unrelated secrets to the scan job.
  7. Clarify that prompt-injection markers shown at README.md:43 are detection signatures, not instructions to the Agent.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (14)

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The README makes a materially misleading trust claim: it states there are 'No network calls' while also saying scanner scripts are 'downloaded fresh' from remote repositories each run. For a CI security action, hidden or minimized runtime code fetching creates a supply-chain risk because the code actually executed can change outside the pinned action version and may exfiltrate repository data or secrets.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The action's stated purpose is scanning a workspace, but it includes a broad runtime capability to retrieve and execute remote code, which is not necessary if the scanner logic is packaged with the action. In a GitHub Actions context, this materially increases supply-chain risk because the downloaded scripts execute with the job's permissions, potentially exposing repository contents, secrets, and tokens.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The README claims 'No dependencies' and that all logic is plain-text local Python, but it also says scanner scripts are fetched from external repositories. That means the action depends on remote unbundled code at execution time, undermining auditability and reproducibility and exposing users to upstream compromise or silent behavior changes.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The README does not prominently warn that scanner scripts are downloaded from external repositories at runtime, despite presenting the action as safe and auditable. In the context of a security-scanning GitHub Action that may run with repository access and CI secrets, omission of this behavior can mislead users into trusting a much larger supply-chain boundary than disclosed.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding

The skill advertises operational behavior that implies shell, environment, and file-writing capabilities, but it does not declare any explicit tool scope such as permissions or allowed-tools. That mismatch weakens least-privilege controls and can cause a host agent to grant broader access than reviewers expect, increasing the chance of misuse if the skill or surrounding automation is compromised.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · SKILL.md (reported line 70)May include surrounding context.

md
- Generate a summary report

It will NOT:
- Automatically modify your code
- Quarantine or delete files
- Make any changes to your repository

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

This composite action downloads Python scanner code from external GitHub repositories at runtime and then executes it in the CI environment. Because the code is fetched from mutable remote locations rather than being vendored or pinned to immutable digests/commits, any compromise of those repositories, branches, or the network path could result in arbitrary code execution inside workflows that invoke this security scanner.

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/scan.py (reported line 22)May include surrounding context.

python
"""Run a scanner and capture its JSON output."""
    try:
        # Each scanner outputs JSON to stdout when given --json flag
        result = subprocess.run(
            [sys.executable, str(scanner_path), command, "-w", str(workspace), "--json"],
            capture_output=True,
            text=True,

Tainted flow: 'workspace' from os.environ.get (line 124, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/scan.py (reported line 22)May include surrounding context.

python
"""Run a scanner and capture its JSON output."""
    try:
        # Each scanner outputs JSON to stdout when given --json flag
        result = subprocess.run(
            [sys.executable, str(scanner_path), command, "-w", str(workspace), "--json"],
            capture_output=True,
            text=True,

Tainted flow: 'summary_file' from os.environ.get (line 58, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/scan.py (reported line 62)May include surrounding context.

python
if not summary_file:
        return

    with open(summary_file, "a") as f:
        f.write("## OpenClaw Security Scan Results\n\n")

        if not findings:

Tainted flow: 'output_file' from os.environ.get (line 116, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/scan.py (reported line 118)May include surrounding context.

python
"""Set a GitHub Actions output variable."""
    output_file = os.environ.get("GITHUB_OUTPUT")
    if output_file:
        with open(output_file, "a") as f:
            f.write(f"{name}={value}\n")

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/scan.py (reported line 162)May include surrounding context.

python
if bastion_path.exists():
            # bastion uses subparser: --workspace before scan
            try:
                result = subprocess.run(
                    [sys.executable, str(bastion_path), "--workspace", str(workspace), "scan", "--json"],
                    capture_output=True,
                    text=True,

Tainted flow: 'bastion_path' from os.environ.get (line 158, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
71% confidence
Finding

bastion_path is derived from SCANNERS_DIR, an environment-controlled path, and is executed directly as Python code. In CI contexts that process untrusted workflow inputs or run on less-trusted self-hosted runners, an attacker who can influence SCANNERS_DIR or place a malicious bastion.py in that location can achieve arbitrary code execution inside the job.

Content

Scanner excerpt · scripts/scan.py (reported line 162)May include surrounding context.

python
if bastion_path.exists():
            # bastion uses subparser: --workspace before scan
            try:
                result = subprocess.run(
                    [sys.executable, str(bastion_path), "--workspace", str(workspace), "scan", "--json"],
                    capture_output=True,
                    text=True,

External Script Fetching

Low
Category
Supply Chain
Confidence
94% confidence
Finding

Using curl to fetch executable scripts from raw.githubusercontent.com at runtime is an external script fetching pattern that creates an integrity gap. Even if the current URLs are legitimate, the lack of pinning and verification means the fetched script contents can change over time, turning this action into a vehicle for unreviewed code execution in CI.

Content

Scanner excerpt · action.yml (reported line 53)May include surrounding context.

yaml
mkdir -p ${{ runner.temp }}/openclaw-scanners

        # Download scanner scripts directly from GitHub
        curl -sL https://raw.githubusercontent.com/AtlasPA/openclaw-sentry/main/scripts/sentry.py \
          -o ${{ runner.temp }}/openclaw-scanners/sentry.py
        curl -sL https://raw.githubusercontent.com/AtlasPA/openclaw-bastion/main/scripts/bastion.py \
          -o ${{ runner.temp }}/openclaw-scanners/bastion.py

Static analysis

No suspicious patterns detected.