Back to skill

Security audit

Ward Codex Session Repair

Security checks for vulnerabilities and agentic risk

Overview

The skill is a mostly coherent Codex session repair tool, but it has target-validation and install-supply-chain issues users should review before installing.

Review this skill before installing. Only run it on a clearly identified session UUID, keep Codex fully closed during apply, confirm the dry-run report shows the expected embedded session metadata, and keep the generated backups. Prefer a pinned or locally verified install instead of the unpinned npx command.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T08 · Insecure Dependencies

Warning
Location
SKILL.md:253
Finding
Unpinned Third-Party Installation Command Creates a Supply-Chain Risk## Vulnerability Details **File Location**: `SKILL.md:253` **Vulnerability Type**: Unpinned third-party dependency and mutable remote source **Risk Level**: Medium ### Vulnerable Code ```text npx skills add WardLu/skills --skill codex-cross-provider-session-repair --global --agent codex --yes ``` ### Technical Analysis The documented installation command invokes the `skills` npm package without specifying a package version. It also installs Skill content from the mutable `WardLu/skills` GitHub repository without pinning a commit hash or verifying an archive checksum or signature. As a result, the code installed by this command can differ from the artifact covered by this audit. The `--yes` option further suppresses interactive confirmation. This creates a supply-chain trust boundary involving both the npm package and the remote repository. The audited project itself does not retrieve or execute a remote payload during its normal repair workflow. The risk is specifically associated with the documented installation command. ### Attack Path 1. An attacker compromises the npm package, its publishing credentials, the referenced GitHub repository, or a relevant upstream account. 2. The attacker publishes a modified package version or changes the repository content. 3. A user executes the documented unpinned `npx skills add ... --yes` command. 4. `npx` resolves the mutable package version, and the installer retrieves mutable repository content. 5. The modified content is executed during installation or installed globally under the user's Agent Skill directory. 6. The malicious Skill subsequently executes with the privileges of the user running the agent. ### Impact Assessment Successful exploitation could install or execute arbitrary code with the current user's privileges. The affected scope includes files, credentials, agent configuration, and other resources accessible to that user. The command does not itself request administrative privileges, so the dir ...[truncated 64 chars]
Remediation
## Remediation Suggestions 1. Pin the npm package to a reviewed version, for example by using an explicit `package@version` reference. 2. Pin the Skill repository to an immutable commit hash or signed release rather than a mutable branch. 3. Publish a SHA-256 checksum or cryptographic signature for the distributable archive and require verification before installation. 4. Remove `--yes` from the recommended command when practical so users can review installation details. 5. Document the expected package version, repository commit, archive digest, and installed file manifest. 6. Prefer installation from a locally downloaded and verified release artifact.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/repair.py:43
Finding
Primary Rollout Lookup Does Not Validate the Embedded Session Identity## Vulnerability Details **File Location**: `scripts/repair.py:43-45` **Vulnerability Type**: Improper target validation before destructive file modification **Risk Level**: Medium ### Vulnerable Code ```python exact = sorted(sessions.rglob(f"rollout-*-{session_id}.jsonl")) if exact: return exact[0] ``` The returned path can later be rewritten by logic including: ```python stale = set(report["stale_ids_present_as_reasoning"]) needs_write = remove_reasoning != "none" or fix_provider or fix_model_turn or fix_model backup = backup_file(path, "session-repair") if needs_write else None ``` ```python if record.get("type") == "response_item" and payload.get("type") == "reasoning": item_id = payload.get("id") if remove_reasoning == "all" or (remove_reasoning == "stale" and item_id in stale): removed += 1 continue ``` ### Technical Analysis When a filename matches `rollout-*-<session_id>.jsonl`, `find_rollout()` immediately returns the first sorted match. It does not verify that a `session_meta` record inside that file contains the requested session ID. Embedded metadata validation only occurs in the fallback path used when no filename match exists. Apply mode also does not require `target_session_meta` to contain a matching record before permitting all repair operations. This behavior conflicts with the documented safety requirement that the filename must not be trusted by itself. Imported, renamed, duplicated, or attacker-planted rollout files can therefore cause the tool to inspect and rewrite a conversation other than the requested target. In particular, `--remove-reasoning all`, model-setting repair, and rollback removal operate on the selected file regardless of whether its embedded session identity matches. A backup is created before rewriting, which supports recovery, but it does not prevent the integrity violation or accidental disclosure caused by inspecting the wrong file. ### Attack Path 1. A rollout file belongi ...[truncated 1248 chars]
Remediation
## Remediation Suggestions 1. Parse every filename-matched candidate before returning it. 2. Require at least one `session_meta` record whose `payload.id` exactly equals the requested session ID. 3. Reject candidates containing conflicting session identities rather than selecting the first sorted match. 4. Fail closed if multiple rollout files independently claim the same requested session ID, unless the user explicitly selects one. 5. Repeat the embedded identity check immediately before backup and replacement to reduce time-of-check/time-of-use risk. 6. In apply mode, require a nonempty, unambiguous `target_session_meta` result for every operation that rewrites the rollout. 7. Include the validated embedded session ID and selected path in verification output. 8. Add tests for renamed files, duplicate candidates, conflicting metadata, and files with no matching `session_meta`.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/wait_and_repair.py:347
Finding
Post-Repair Verification Omits Requested Provider, Model-Turn, and Configuration Invariants## Vulnerability Details **File Location**: `scripts/wait_and_repair.py:347-364` **Vulnerability Type**: Incomplete security-state verification leading to false success **Risk Level**: Low ### Vulnerable Code ```python def _verify_post_report( report: dict[str, object], remove_reasoning: str, fix_model: bool = False, model: str | None = None, ) -> None: if report["json_parse_errors"]: raise RepairApplyError("post-repair verification found malformed JSONL") if remove_reasoning == "all" and report["reasoning_item_count"] != 0: raise RepairApplyError( f"post-repair verification found {report['reasoning_item_count']} local reasoning records" ) if remove_reasoning == "stale" and report["stale_ids_present_as_reasoning"]: raise RepairApplyError("post-repair verification found stale IDs still present as local reasoning") if fix_model: if not model: raise RepairApplyError("model repair verification requires a target model") root_thread = report.get("root_thread") if not isinstance(root_thread, dict) or root_thread.get("model") != model: raise RepairApplyError("post-repair verification found the target DB model was not updated") structured_models = report.get("structured_model_values") or [] if any(value != model for value in structured_models): raise RepairApplyError("post-repair verification found an old structured rollout model") ``` The result is then marked verified: ```python post_report = repair.inspect_session(codex_home, session_id) _verify_post_report(post_report, remove_reasoning, fix_model, model) backup = str(sorted(new_backups)[-1]) reporter.update( "verified", "修复完成并验证通过,现在可以重新打开 Codex。 / Repair complete and verified; you can reopen Codex now.", backup=backup, line_count=post_report["line_count"], reasoning_item_count=post_report["reasoning_item_count"], historical_remote_stale_i ...[truncated 2347 chars]
Remediation
## Remediation Suggestions 1. Pass every requested operation and target value to `_verify_post_report()`. 2. For provider repair, require all target `session_meta` provider values and the target database provider field to equal the requested provider. 3. Treat a missing target metadata record or missing expected database column as a verification failure when that repair was requested. 4. For model-turn repair, require `model_turn.model_turn_risk` to be false and `last_effective_role` to be `user`, consistent with the documented contract. 5. For remote-compaction repair, parse `config.toml` after writing and verify that the effective `[features]` value is `remote_compaction_v2 = false`. 6. Record per-operation postconditions in the status JSON rather than relying on backup creation as general proof of success. 7. Mark the run `verified` only when every requested postcondition passes. 8. Add negative tests in which repair functions return success but perform zero applicable updates.
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • 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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (13)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared purpose focuses on analyzing Codex Desktop session artifacts such as JSONL rollouts, sqlite databases, config.toml, and logs, then performing scoped repairs for invalid sessions. The actual code does none of that. It is a generic installer: it determines source and destination directories, prevents installing onto the source itself, creates the destination, renames any existing target to a backup name, and recursively copies files into place. This is a materially different primary purpose and accesses different resources than declared, so it is a clear mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared purpose centers on forensic diagnosis and repair of invalid Codex Desktop sessions after provider switching or migrated conversations. The actual code performs no session inspection, no SQLite/config/log analysis, no backup-first repair, and no restart verification guidance. Its sole function is to package the current skill directory into a .skill archive. That is a materially different primary purpose and capability set from the description, so this is a clear mismatch.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The invocation condition is overly broad because it says to use the skill even when a user only wants inspection or explanation. In context, this skill is designed to modify local session files and databases, so broad triggering increases the chance of unnecessary access to sensitive local data or premature repair actions before clear user consent.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The file mandates a specific mixed-language user-facing message: Chinese followed by English. This is a language/locale policy issue because it forces output language behavior without asking the user for their preferred language.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The instructions require bilingual states such as 'Waiting / 等待中' and 'Verified / 已验证' as the authoritative status labels. This hard-codes language behavior rather than offering a locale choice or documenting a justified regional constraint.

Rp1

Medium
Category
MCP Rug Pull
Confidence
82% confidence
Finding
The installation command uses `npx skills add ...` without pinning an exact package/version, which can cause users to fetch whatever version is current at install time. That creates a supply-chain risk: a compromised upstream package, unexpected update, or dependency substitution could execute unreviewed code during installation.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if sys.platform != "darwin":
        raise RuntimeError("--open-terminal is currently supported on macOS only")
    applescript = build_terminal_applescript(command, session_id=session_id)
    subprocess.run(["osascript", "-e", applescript], check=True)


def build_parser() -> argparse.ArgumentParser:
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
print(build_conversation_notice(args.timeout))
        print(f"已打开终端状态窗口:{status_file};最长等待 {args.timeout:g} 秒 / wait limit {args.timeout:g}s")
        return 0
    return subprocess.run(worker_argv, cwd=skill_dir).returncode


if __name__ == "__main__":
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The file hard-codes Chinese and English status labels and action text for user-facing output, and later emits bilingual prompts and reminders throughout the workflow. This imposes a specific language/locale behavior without offering user choice or explaining a required regional constraint, which matches the language/locale policy violation criteria.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
fails, the exception is propagated and the caller must not apply a repair.
    """
    if os.name == "nt":
        completed = subprocess.run(
            ["tasklist", "/fo", "csv", "/nh"],
            check=True,
            capture_output=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
continue
        return processes

    completed = subprocess.run(
        ["ps", "-axo", "pid=,comm="],
        check=True,
        capture_output=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Intent-Code Divergence

Low
Confidence
89% confidence
Finding
The comment says the fallback checks session_meta records "without reading any other Codex artifacts," which implies a narrower inspection approach. In reality, the code recursively opens and reads every candidate rollout JSONL file under the sessions directory to locate a matching session_meta payload, so the documentation misstates what data is being read.

Static analysis

No suspicious patterns detected.