Back to skill

Security audit

Codex Bridge

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Codex bridge, but it grants broad background coding-agent authority and has confirmed local path and storage safety gaps.

Install only if you intentionally want OpenClaw to launch local Codex jobs that can edit and run code in selected project directories. Use simple safe task IDs, avoid secrets in prompts, inspect or clean ~/.codex-bridge regularly, and prefer running it in a contained workspace until task-ID validation and restrictive file permissions are added.

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

Error
Location
bridge.py:339
Finding

Unvalidated Task Identifier Permits Path Traversal Outside Bridge Storage

Content
View full analysis
None: task_dir = TASKS_DIR / task_id task_dir.mkdir(parents=True, exist_ok=True) (task_dir / "bridge.pid").write_text(str(os.getpid())) write_json_atomic( task_dir / "task.json", { "task_id": task_id, "workdir": workdir, "prompt": prompt, "created_at": now_iso(), "round": 0, }, ) (task_dir / "output.log").touch() (task_dir / "events.jsonl").touch() (task_dir / "bridge.log").touch() ``` `bridge.py:415-417`: ```python def sigterm_handler(signum: int, frame: Any) -> None: task_dir = TASKS_DIR / args.task_id write_status(task_dir, "error", "Bridge process terminated by signal") ``` `codex-bridge-dispatch.sh:63-68`: ```bash if [[ -d "$TASKS_DIR/$TASK_ID" ]]; then echo "Error: task '$TASK_ID' already exists at $TASKS_DIR/$TASK_ID" exit 1 fi mkdir -p "$TASKS_DIR/$TASK_ID" ``` `codex-bridge-answer.sh:42-57`: ```bash TASK_DIR="$TASKS_DIR/$TASK_ID" if [[ ! -d "$TASK_DIR" ]]; then echo "Error: task '$TASK_ID' not found" exit 1 fi if [[ -f "$TASK_DIR/status.json" ]]; then STATUS=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('status',''))" "$TASK_DIR/status.json" 2>/dev/null || echo "") if [[ "$STATUS" != "waiting_for_answer" ]]; then echo "Warning: task status is '$STATUS', not 'waiting_for_answer'" fi fi TMP_FILE="$TASK_DIR/answer.json.tmp" ANSWER_FILE="$TASK_DIR/answer.json" ``` `codex-bridge-status.sh:75-112`: ```bash TASK_DIR="$TASKS_DIR/$TASK_ID" if [[ ! -d "$TASK_DIR" ]]; ...[truncated 3331 chars]
Remediation
View remediation
str: if not TASK_ID_RE.fullmatch(task_id): raise ValueError("Invalid task identifier") if task_id in {".", ".."}: raise ValueError("Invalid task identifier") return task_id ``` 2. Resolve the destination and enforce containment before every access: ```python tasks_root = TASKS_DIR.resolve() task_dir = (tasks_root / validate_task_id(task_id)).resolve() if task_dir.parent != tasks_root: raise ValueError("Task path escapes task storage") ``` 3. Apply equivalent validation in all three shell entry points, or centralize path resolution in a single Python helper used by every operation. 4. Explicitly reject `/`, `\`, `..`, control characters, and absolute paths in task identifiers. 5. Reject symbolic-link task directories and sensitive files. Where supported, use descriptor-relative filesystem operations and no-follow semantics to reduce time-of-check/time-of-use and symlink risks. 6. Add automated tests covering: - `../` traversal. - Absolute paths. - Nested separators. - Symlinked task directories. - Valid identifiers at minimum and maximum lengths. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
bridge.py:42
Finding

Sensitive Task Artifacts Are Created Without Enforced Restrictive Permissions

Content
View full analysis
None: path.parent.mkdir(parents=True, exist_ok=True) tmp = path.with_suffix(path.suffix + ".tmp") tmp.write_text(json.dumps(data, indent=2, default=str)) tmp.rename(path) def read_json(path: Path) -> dict[str, Any] | None: if not path.exists(): return None return json.loads(path.read_text()) def append_text(path: Path, text: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) with path.open("a", encoding="utf-8") as f: f.write(text) ``` `bridge.py:339-354`: ```python def run_bridge(task_id: str, workdir: str, prompt: str) -> None: task_dir = TASKS_DIR / task_id task_dir.mkdir(parents=True, exist_ok=True) (task_dir / "bridge.pid").write_text(str(os.getpid())) write_json_atomic( task_dir / "task.json", { "task_id": task_id, "workdir": workdir, "prompt": prompt, "created_at": now_iso(), "round": 0, }, ) (task_dir / "output.log").touch() (task_dir / "events.jsonl").touch() (task_dir / "bridge.log").touch() ``` ### Technical Analysis The bridge persists potentially sensitive information in plaintext, including: - Original user prompts and working-directory paths. - Clarification questions and answers. - Codex responses and errors. - JSON event streams. - Command summaries and bridge execution logs. Directories are created with `mkdir(..., exist_ok=True)`, and files are created through `write_text`, `open`, or `touch` without explicitly enforcing `0700` directory permissions or `0600` file permissions. Their effective access modes therefore depend on the process umask and existing ...[truncated 1555 chars]
Remediation
View remediation
Vulnerability Patterns
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (5)

YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]

High
Category
YARA Match
Confidence
80% confidence
Finding

YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Content

Scanner excerpt · bridge.py (reported line 404)May include surrounding context.

python
write_status(task_dir, "error", "Question detected but resume session id missing")
            return

        detail = capture.errors[-1] if capture.errors else f"Codex exited with status {returncode}"
        write_result(task_dir, "error", detail, capture, returncode)
        write_status(task_dir, "error", detail)
        return


def main() -> None:
    parser = argparse.ArgumentParser(description="OpenClaw <-> Codex bridge")
    parser.add_argument("--task-id", required=True, help="Unique task identifier")
    parser.add_argument("--workdir", required=True, help="Working directory for Codex")
    parser.add_argument("--prompt", required=True, help="Prompt to send to Codex")
    args = parser.parse_args()

    if not Path(args.workdir).is_dir():
        print(f"Workdir does not exist: {args.workdir}", file=sys.stderr)
        sys.exit(2)

    def sigterm_handler(signum: int, frame: Any) -> None:
        task_dir = TASKS_DIR / args.task_id
        write_status(task_dir, "erro

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding

The skill explicitly instructs the operator to run local shell scripts that dispatch tasks to a coding agent, poll status, and write answers, which implies shell execution plus file reads/writes to bridge state. Because the skill does not declare any tool scope such as permissions or allowed-tools, there is no explicit containment or least-privilege boundary for these capabilities, increasing the risk of unintended filesystem access or command execution through the delegated Codex workflow.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
83% confidence
Finding

The skill is designed around asynchronous background execution with persisted state under ~/.codex-bridge/tasks// and explicit resume behavior for the same Codex session. Persisting prompts, outputs, clarifying questions, and resumed session state can retain sensitive project data or user inputs longer than necessary, and a resumed agent session may continue acting on stale or attacker-influenced context if task directories are accessible or tampered with.

Content

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

md
## When NOT to Use

- Quick factual questions or explanations
- Small code snippets that OpenClaw can write directly
- Non-coding tasks
- Tasks that should not invoke a local coding agent/CLI

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 · bridge.py (reported line 239)May include surrounding context.

python
capture = RunCapture()
        append_text(self.bridge_log, f"\n[{now_iso()}] RUN {' '.join(cmd)}\n")

        proc = subprocess.Popen(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,

Session Persistence

Medium
Category
Rogue Agent
Confidence
65% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · codex-bridge-dispatch.sh (reported line 70)May include surrounding context.

sh
mkdir -p "$TASKS_DIR/$TASK_ID"

nohup python3 "$SKILL_DIR/bridge.py" \
    --task-id "$TASK_ID" \
    --workdir "$WORKDIR" \
    --prompt "$PROMPT" \

Static analysis

No suspicious patterns detected.