Back to skill

Security audit

Codex Profiler

Security checks for vulnerabilities and agentic risk

Overview

The skill has a real Codex account-management purpose, but it handles credentials, auth-store changes, and gateway restarts through risky temporary files and background shell scripts.

Install only if you are comfortable giving this skill access to Codex/OpenAI OAuth profile data and allowing it to change OpenClaw auth state. Prefer the documented gateway-native commands, avoid unsafe-direct flags, use only trusted profile names, and clean up /tmp/openclaw token or staged-auth artifacts after use.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/codex_auth.py:518
Finding
Shell Command Injection Through Unsanitized Profile Identifiers<![CDATA[ ## Vulnerability Details **File Location**: `scripts/codex_auth.py:518-560` **Vulnerability Type**: Shell command injection in a generated Bash script **Risk Level**: High ### Vulnerable Code ```python def safe_profile_slug(profile_id: str) -> str: return (profile_id or "unknown").replace(":", "_") ``` ```python if args.queue_apply: os.makedirs("/tmp/openclaw", exist_ok=True) safe_profile = safe_profile_slug(profile_id) payload_path = f"/tmp/openclaw/codex-auth-apply-{safe_profile}.json" script_path = f"/tmp/openclaw/codex-auth-apply-{safe_profile}.sh" log_path = f"/tmp/openclaw/codex-auth-apply-{safe_profile}.log" status_path = per_profile_status_path(profile_id) write_json_atomic(payload_path, {"profile": profile_id, "tokens": tokens}) py = shlex.quote(sys.executable or "python3") this_file = shlex.quote(os.path.abspath(__file__)) payload_q = shlex.quote(payload_path) auth_q = shlex.quote(args.auth_path) log_q = shlex.quote(log_path) allow_default_flag = " --allow-default" if (profile_id == "openai-codex:default" and args.allow_default) else "" script = f"""#!/usr/bin/env bash set -euo pipefail {{ echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] codex-auth apply start profile={profile_id}"; {py} {this_file} apply --payload {payload_q} --auth-path {auth_q}{allow_default_flag}; echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] codex-auth apply done profile={profile_id}"; }} >> {log_q} 2>&1 """ Path(script_path).write_text(script, encoding="utf-8") os.chmod(script_path, 0o700) launcher = "nohup" unit_name = f"codex-auth-apply-{safe_profile}-{int(time.time())}" try: sd = subprocess.run( ["systemd-run", "--user", "--unit", unit_name, "--collect", "/bin/bash", script_path], capture_output=True, text=True, timeout=15, ) if sd.returncode == 0: launcher = "systemd-run" else: subprocess ...[truncated 2791 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce a strict profile identifier allowlist before using a profile value: ```python import re PROFILE_RE = re.compile(r"^[A-Za-z0-9._-]{1,64}$") def validate_profile_suffix(value: str) -> str: if not PROFILE_RE.fullmatch(value): raise ValueError("Invalid profile identifier") return value ``` 2. Explicitly reject path separators, whitespace, control characters, shell metacharacters, backticks, dollar signs, and newline characters. 3. Do not generate executable shell scripts containing dynamic data. Implement the queued apply operation in Python and invoke subprocesses with fixed argument arrays. 4. If shell generation cannot immediately be removed, shell-quote every dynamic value with `shlex.quote()` and avoid placing dynamic values inside executable shell syntax. 5. Derive filenames and systemd unit names from a cryptographic random identifier rather than directly from a profile name. 6. Apply the same validation to profile identifiers loaded from `auth-profiles.json`, since that file may contain externally modified or previously malicious values. 7. Add regression tests using values containing `$(...)`, backticks, semicolons, quotes, newlines, spaces, `../`, and absolute paths. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/codex_auth.py:525
Finding
OAuth Access and Refresh Tokens Persist in Predictable Temporary Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/codex_auth.py:134-140, 197-201, 332-354, 518-525` **Vulnerability Type**: Insecure temporary storage of plaintext OAuth credentials **Risk Level**: High ### Vulnerable Code ```python def write_json_atomic(path, data): tmp = path + ".tmp" with open(tmp, "w", encoding="utf-8") as f: json.dump(data, f, indent=2) f.write("\n") os.replace(tmp, path) ``` ```python def save_pending(profile_id, verifier, state): pending = read_json(PENDING_PATH) pending[profile_id] = { "verifier": verifier, "state": state, "createdAt": int(time.time() * 1000) } os.makedirs(os.path.dirname(PENDING_PATH), exist_ok=True) write_json_atomic(PENDING_PATH, pending) ``` ```python stage_dir = "/tmp/openclaw" os.makedirs(stage_dir, exist_ok=True) ts = int(time.time()) staged_cfg = os.path.join(stage_dir, f"openclaw.staged.{ts}.json") staged_auth = os.path.join(stage_dir, f"auth-profiles.staged.{ts}.json") try: if os.path.exists(OPENCLAW_CONFIG_PATH): shutil.copy2(OPENCLAW_CONFIG_PATH, staged_cfg) else: write_json_atomic(staged_cfg, {}) if os.path.exists(auth_path): shutil.copy2(auth_path, staged_auth) else: write_json_atomic(staged_auth, {}) cfg = read_json(staged_cfg) ensure_profile_declared_in_config_obj(cfg, profile_id) write_json_atomic(staged_cfg, cfg) store = read_json(staged_auth) before_profiles = copy.deepcopy((store.get("profiles") or {})) store = upsert_auth_profile_obj(store, profile_id, tokens) assert_only_target_profile_changed( before_profiles, store.get("profiles") or {}, profile_id, "apply_auth", ) write_json_atomic(staged_auth, store) ``` ```python if args.queue_apply: os.makedirs("/tmp/openclaw", exist_ok=True) safe_profile = safe_profile_slug(profile_id) payload_path = f"/tmp/openclaw/codex-auth-apply-{safe_p ...[truncated 2866 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Avoid storing access or refresh tokens under a shared temporary directory whenever possible. 2. Create a private runtime directory owned by the current user with mode `0700`. 3. Create every secret-bearing file atomically with exclusive mode `0600`, for example by using `os.open()` with `O_CREAT | O_EXCL` and mode `0o600`. 4. Verify file ownership, type, and permissions before reading or replacing existing files. Reject symlinks and files owned by another user. 5. Use randomized filenames rather than predictable profile-derived names. 6. Delete payloads, staged stores, pending PKCE records, scripts, and unnecessary backups immediately after successful verification. 7. Perform cleanup in `finally` blocks so artifacts are also removed following failures and exceptions. 8. Store only the minimum required data in queued payloads. Where practical, pass an opaque identifier to a protected credential service rather than serializing refresh tokens. 9. Apply restrictive permissions to backup files and define a short retention policy. 10. Add automated tests that inspect artifact permissions and confirm cleanup after successful and failed operations. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/codex_auth.py:501
Finding
OAuth State Validation Is Bypassable When the Callback Omits State<![CDATA[ ## Vulnerability Details **File Location**: `scripts/codex_auth.py:501-510` **Vulnerability Type**: Incomplete OAuth state validation **Risk Level**: Medium ### Vulnerable Code ```python pending = load_pending(profile_id) if not pending: print(json.dumps({ "ok": False, "error": "no_pending_flow", "profile": profile_id, }, indent=2)) sys.exit(2) code, state = parse_callback_input(args.callback_url) if not code: print(json.dumps({"ok": False, "error": "missing_code"}, indent=2)) sys.exit(2) if pending.get("state") and state and pending["state"] != state: print(json.dumps({"ok": False, "error": "state_mismatch"}, indent=2)) sys.exit(2) tokens = exchange_code(code, pending["verifier"]) ``` ### Technical Analysis The state mismatch condition is evaluated only when both the stored state and callback state are nonempty: ```python pending.get("state") and state and pending["state"] != state ``` If the callback omits the `state` parameter, `state` is false and the mismatch check is skipped. The code then proceeds to exchange the authorization code. OAuth state is intended to bind the authorization response to the initiating flow and should be mandatory once generated. An absent state must be treated as a validation failure, not as permission to skip validation. PKCE provides an additional binding mechanism and limits exploitation because the authorization code must be compatible with the stored verifier. It does not make optional state validation correct, and callbacks must satisfy both checks. The pending flow also includes a `createdAt` value, but this code does not enforce an expiration or consume the pending record after use. ### Attack Path 1. A legitimate OAuth flow is started for a profile, creating a stored PKCE verifier and state. 2. An attacker or confused intermediary obtains or modifies a callback carrying a valid authorization code for that pending PKCE flow. 3. The attacker removes ...[truncated 993 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require a callback state and enforce exact equality with the stored state: ```python import hmac expected_state = pending.get("state") if not expected_state or not state or not hmac.compare_digest(state, expected_state): print(json.dumps({"ok": False, "error": "state_mismatch"}, indent=2)) sys.exit(2) ``` 2. Reject callbacks missing either `code` or `state`. 3. Validate that full callback URLs use the expected scheme, host, port, and path: ```text http://localhost:1455/auth/callback ``` 4. Enforce a short expiration using `createdAt`. 5. Consume and delete the pending state after the first success or terminal failure to prevent replay. 6. Store pending OAuth records in a user-private file with mode `0600`. 7. Add tests for missing state, empty state, mismatched state, expired state, replayed callbacks, and malformed callback URLs. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (23)

Tainted flow: 'req' from os.getenv (line 297, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
"""
    req = urllib.request.Request(endpoint, method="HEAD", headers={"User-Agent": "CodexBar"})
    try:
        with urllib.request.urlopen(req, timeout=timeout_sec):
            return True
    except urllib.error.HTTPError as e:
        # Host/path reachable; endpoint answered with HTTP status.
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.getenv (line 297, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
start = time.time()
        req = urllib.request.Request(endpoint, headers=headers)
        try:
            with urllib.request.urlopen(req, timeout=timeout_sec) as resp:
                body = resp.read().decode("utf-8", errors="replace")
                elapsed_ms = int((time.time() - start) * 1000)
                parse_error = False
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
s_finish.add_argument("--auth-path", default=AUTH_PATH_DEFAULT)
    s_finish.add_argument("--queue-apply", action="store_true", default=True, help="Queue stop/write/start apply script in background (strict default)")
    s_finish.add_argument("--allow-default", action="store_true", help="Required to mutate/auth the default profile")
    s_finish.add_argument("--allow-unsafe-direct", action="store_true", help="Allow direct in-process writes without off-host stop/write/start (not recommended)")

    s_apply = sub.add_parser("apply")
    s_apply.add_argument("--payload", required=True)
Confidence
75% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
s_finish.add_argument("--auth-path", default=AUTH_PATH_DEFAULT)
    s_finish.add_argument("--queue-apply", action="store_true", default=True, help="Queue stop/write/start apply script in background (strict default)")
    s_finish.add_argument("--allow-default", action="store_true", help="Required to mutate/auth the default profile")
    s_finish.add_argument("--allow-unsafe-direct", action="store_true", help="Allow direct in-process writes without off-host stop/write/start (not recommended)")

    s_apply = sub.add_parser("apply")
    s_apply.add_argument("--payload", required=True)
Confidence
75% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
The utility writes executable shell scripts and staged auth data under /tmp and then launches them asynchronously via systemd-run or nohup. In the context of a usage-checking script, this is excessive and risky because it expands the attack surface, leaves sensitive operational artifacts on disk, and enables background execution of auth-store mutation and process-control actions beyond the original invocation lifecycle.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
This script is presented as a Codex usage-checking utility, but it also supports detaching or hard-deleting auth profiles, stopping a gateway process, overwriting the auth store, and restarting the gateway. That is dangerous because it combines a read-oriented status tool with destructive credential-store mutation and service control, creating an unusually powerful and abusable capability if invoked by an agent or user in the wrong context.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
)
    ap.add_argument("--dry-run", action="store_true", help="Show delete result without writing auth file")
    ap.add_argument("--ack-gateway-restart", action="store_true", help="Required confirmation: mutation will stop gateway, write auth store, and restart gateway")
    ap.add_argument("--allow-unsafe-direct", action="store_true", help="Allow direct in-process auth write for delete mutation (not recommended)")
    ap.add_argument("--format", choices=["json", "text"], default="json", help="Output format for usage checks")
    args = ap.parse_args()
Confidence
95% confidence
Finding
The presence of an --allow-unsafe-direct flag explicitly enables bypassing the safer queued/off-host path and permits direct writes to the auth store. That is dangerous because it normalizes an unsafe mode for destructive credential-state mutation, increasing the chance of accidental or agent-driven misuse against sensitive authentication material.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
openclaw models auth order get --provider openai-codex --agent <agent-id>
```

Never skip verification. If results mismatch expectation, do not hand-edit files; diagnose and re-apply via gateway-native commands.

## How to run
```bash
Confidence
85% 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.

External Transmission

Medium
Category
Data Exfiltration
Content
TOKEN_URL = "https://auth.openai.com/oauth/token"
REDIRECT_URI = "http://localhost:1455/auth/callback"
SCOPE = "openid profile email offline_access"
JWT_CLAIM_PATH = "https://api.openai.com/auth"
OPENCLAW_CONFIG_PATH = str(Path.home() / ".openclaw" / "openclaw.json")
BACKUP_DIR = "/tmp/openclaw/safety-backups"
APPLY_STATUS_PATH = "/tmp/openclaw/codex-auth-apply-last.json"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The auth store persists access and refresh tokens, expiry, and account identifiers to disk without any encryption, permission hardening, or explicit user-facing warning in this path. If the auth file is readable by other local processes or included in backups, long-lived refresh tokens could be stolen and abused for account access.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_cmd(cmd):
    return subprocess.run(cmd, capture_output=True, text=True)


def stop_gateway_processes():
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
def stop_gateway_processes():
    # Avoid `openclaw gateway stop` because CLI config parsing may fail.
    subprocess.run(["pkill", "-TERM", "-f", "^openclaw-gateway$"], capture_output=True, text=True)
    time.sleep(3)
    subprocess.run(["pkill", "-KILL", "-f", "^openclaw-gateway$"], capture_output=True, text=True)
    time.sleep(1)
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
# Avoid `openclaw gateway stop` because CLI config parsing may fail.
    subprocess.run(["pkill", "-TERM", "-f", "^openclaw-gateway$"], capture_output=True, text=True)
    time.sleep(3)
    subprocess.run(["pkill", "-KILL", "-f", "^openclaw-gateway$"], capture_output=True, text=True)
    time.sleep(1)
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
# Prefer direct gateway binary when present.
    gw_bin = shutil.which("openclaw-gateway")
    if gw_bin:
        subprocess.Popen([gw_bin], stdout=lf, stderr=lf, stdin=subprocess.DEVNULL, start_new_session=True)
        return log_path

    # Fallback for installs where only the CLI is on PATH.
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 not oc_bin:
        raise FileNotFoundError("Neither 'openclaw-gateway' nor 'openclaw' found on PATH")

    subprocess.run([oc_bin, "gateway", "start"], stdout=lf, stderr=lf, stdin=subprocess.DEVNULL, check=False)
    return log_path
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The finish flow writes OAuth access and refresh tokens into a JSON payload under /tmp/openclaw before launching the apply step. Temporary files under /tmp can be exposed to other local users or recovered later if permissions are not explicitly restricted, and this code does not set secure file modes or provide clear disclosure to the user at the write point.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
launcher = "nohup"
            unit_name = f"codex-auth-apply-{safe_profile}-{int(time.time())}"
            try:
                sd = subprocess.run(
                    ["systemd-run", "--user", "--unit", unit_name, "--collect", "/bin/bash", script_path],
                    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
def launch_offhost_script(script_path, unit_name):
    try:
        sd = subprocess.run(
            ["systemd-run", "--user", "--unit", unit_name, "--collect", "/bin/bash", script_path],
            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
except Exception:
        pass

    subprocess.Popen(["nohup", "bash", script_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
    return "nohup", ""
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
except Exception:
        pass

    subprocess.Popen(["nohup", "bash", script_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
    return "nohup", ""
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
except Exception:
        pass

    subprocess.Popen(["nohup", "bash", script_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
    return "nohup", ""
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Session Persistence

Medium
Category
Rogue Agent
Content
sleep 1

cp {staged_q} {auth_q}
nohup openclaw-gateway >/tmp/openclaw/openclaw-gateway-offhost-delete-start.log 2>&1 < /dev/null &
sleep 5

python3 - <<'PY'
Confidence
84% confidence
Finding
The script deliberately restarts openclaw-gateway in the background with nohup, creating persistence of a service-control side effect after the invoking context exits. In an agent skill, that is risky because it allows the tool to alter long-lived system state and keep processes running independently of user review or containment boundaries.

Internal Network Request

Medium
Category
Server-Side Request Forgery
Content
profiles = auth.get('profiles') or {{}}
status['present_after'] = [t for t in status['targets'] if t in profiles]
try:
  with urllib.request.urlopen('http://127.0.0.1:18789/health', timeout=4) as r:
    status['gateway_health'] = r.read().decode('utf-8', errors='replace')[:200]
except Exception as e:
  status['gateway_health'] = f'error:{{e}}'
Confidence
70% confidence
Finding
Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Static analysis

No suspicious patterns detected.