Back to skill

Security audit

Migrate to Codex

Security checks for vulnerabilities and agentic risk

Overview

This looks like a real Claude-to-Codex migration tool, but it has high-impact persistent config writes and a confirmed path-containment flaw that can write or delete outside the selected target through symlinks.

Install only if you are comfortable reviewing a migration that can change persistent Codex behavior. Run --scan-only, --plan, and --dry-run first; avoid --replace unless you have checked the target tree for symlinked .agents, .agents/skills, .codex, and .codex/agents paths; and manually review generated .codex/config.toml, .codex/hooks.json, .agents/skills, and .codex/agents before relying on them.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/cli.py:453
Finding

Target-directory symlinks permit writes and recursive deletion outside the authorized migration root

Content
View full analysis

Vulnerability Details

File Location: scripts/cli.py:453-470, 884-892
Vulnerability Type: Symlink traversal during artifact deployment and orphan cleanup
Risk Level: High

Vulnerable Code

python
def write_artifact(artifact: PlannedArtifact, target_root: Path) -> None:
    target_path = target_root / artifact.relative_path
    target_path.parent.mkdir(parents=True, exist_ok=True)

    if isinstance(artifact.payload, GeneratedText):
        if target_path.is_symlink():
            target_path.unlink()
        target_path.write_text(artifact.payload.content)
        return

    if isinstance(artifact.payload, SourceSymlink):
        if target_path.exists() or target_path.is_symlink():
            target_path.unlink()
        target_path.symlink_to(symlink_target(artifact.payload.source_path, target_path))
        return

    if target_path.is_symlink():
        target_path.unlink()
    shutil.copy2(artifact.payload.source_path, target_path)
python
if not args.dry_run:
    for artifact in deployment_plan.artifacts:
        write_artifact(artifact, deployment_target_root)
    if deploy_mode == DeployMode.REPLACE:
        for orphan in deployment_plan.orphaned_skill_dirs:
            shutil.rmtree(orphan)
        for orphan in deployment_plan.orphaned_agent_files:
            orphan.unlink()

Technical Analysis

The deployment code constructs destinations by appending an artifact-relative path to the user-selected target root, but it does not resolve the resulting path and verify that it remains inside that root.

Although write_artifact() handles a symlink at the final destination, it does not reject symlinks in parent components. For example, if .agents/skills is a symlink to a directory elsewhere in the filesystem, writing .agents/skills/example/SKILL.md follows that parent symlink. The resulting write occurs outside the selected migration target.

The replace-mode cleanup has the same containment weakness. Orphan path ...[truncated 2034 chars]

Remediation
View remediation

Remediation Suggestions

  1. Resolve the target root once and reject it if it is itself a symlink:

    python
    resolved_root = target_root.resolve(strict=True)
    
  2. Before every write, copy, symlink creation, unlink, or recursive deletion, resolve the candidate's existing parent and verify containment:

    python
    resolved_parent = target_path.parent.resolve(strict=True)
    resolved_parent.relative_to(resolved_root)
    

    Reject the operation if containment validation raises ValueError.

  3. Explicitly reject symlinks in every destination path component rather than checking only target_path.is_symlink().

  4. Use no-follow or directory-descriptor-based filesystem operations where supported to reduce time-of-check/time-of-use races.

  5. For --replace, validate each deletion candidate immediately before deletion and require it to resolve beneath the exact expected root:

    • skills beneath <target>/.agents/skills;
    • agents beneath <target>/.codex/agents.
  6. Do not define every unplanned directory or file as removable. Maintain a manifest of artifacts generated by this migrator and restrict cleanup to manifest-owned paths.

  7. Refuse recursive deletion if the candidate or any relevant parent is a symlink. Apply the same policy during planning and during the real execution, since planning alone cannot prevent a later symlink substitution.

  8. Add regression tests covering symlinked .agents, .agents/skills, .codex, and .codex/agents parents for both normal deployment and --replace.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (21)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The implementation is focused on one specific migration task: converting supported Claude Code hooks into Codex hook config. It parses hook entries from Claude settings files, filters/rewrites supported events and command handlers, records unsupported fields, and generates .codex/hooks.json plus a migration report entry. The declared purpose instead mentions migrating instruction files, skills, agents, and MCP config, but does not mention hooks at all. That omission is material because hook migration is the primary behavior of this code chunk, not merely an internal detail of migrating the listed items.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding

The declared description presents a broad migration capability covering instruction files, skills, agents, and MCP configuration into Codex project/global files. The supplied code chunk is much narrower: it identifies the first supported instruction file, checks for Claude-specific content markers to decide symlink suitability, and reports AGENTS.md size thresholds. Those behaviors support instruction migration, but they do not carry out the broader migration tasks described, nor do they touch skills, agents, or MCP config. This is a material description-behavior mismatch due to overclaiming the scope and primary functionality of this code chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description says this skill migrates supported files and configurations into Codex-managed locations. However, the provided code chunk is narrowly focused on plugin migration reporting: it calls report_manual_paths with plugin manual-path constants and its docstring explicitly states it only reports plugin surfaces needing manual migration. This is a materially different primary purpose from performing migration of supported instruction files, skills, agents, and MCP config.

Content

No source excerpt is available for this finding.

Agent Config Directory Access

High
Category
Agent Snooping
Confidence
96% confidence
Finding

Directing writes to ~/.codex/config.toml exposes persistent global agent configuration to autonomous modification. A compromised or mistaken migration could silently change future tool behavior, servers, or policy-affecting settings across all projects.

Content

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

md
## Autonomy

Keep going until the selected migration is completely done: run the migrator, inspect the report, fix migrated Codex instructions/skills/agents/MCP config, and re-run checks without stopping to ask for confirmation of the next step. If the user has selected a target, do not ask before creating, editing, replacing, or deleting generated Codex artifacts in that target (`AGENTS.md`, `.codex/`, `.agents/`, or `~/.codex/`). Preserve unrelated existing Codex config entries in `.codex/config.toml` or `~/.codex/config.toml`, such as `notify`, `projects`, `marketplaces`, or unrelated MCP servers; do not ask about them unless they fail validation or directly conflict with the migration. Do not edit source Claude Code files (`.claude/`, `~/.claude/`, `.mcp.json`, or `.claude.json`), unrelated project code, secrets, or another repository.

## Migration Order

Agent Config Directory Access

High
Category
Agent Snooping
Confidence
96% confidence
Finding

Directing writes to ~/.codex/config.toml exposes persistent global agent configuration to autonomous modification. A compromised or mistaken migration could silently change future tool behavior, servers, or policy-affecting settings across all projects.

Content

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

md
## Autonomy

Keep going until the selected migration is completely done: run the migrator, inspect the report, fix migrated Codex instructions/skills/agents/MCP config, and re-run checks without stopping to ask for confirmation of the next step. If the user has selected a target, do not ask before creating, editing, replacing, or deleting generated Codex artifacts in that target (`AGENTS.md`, `.codex/`, `.agents/`, or `~/.codex/`). Preserve unrelated existing Codex config entries in `.codex/config.toml` or `~/.codex/config.toml`, such as `notify`, `projects`, `marketplaces`, or unrelated MCP servers; do not ask about them unless they fail validation or directly conflict with the migration. Do not edit source Claude Code files (`.claude/`, `~/.claude/`, `.mcp.json`, or `.claude.json`), unrelated project code, secrets, or another repository.

## Migration Order

MCP Config Access

High
Category
Agent Snooping
Confidence
93% confidence
Finding

Referencing .mcp.json and .claude.json as migration sources is sensitive because these files may contain tool/server definitions that affect agent connectivity and execution context. Even though the skill says not to edit the source files, it still instructs reading and transforming high-trust MCP configuration into active Codex settings.

Content

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

md
## Autonomy

Keep going until the selected migration is completely done: run the migrator, inspect the report, fix migrated Codex instructions/skills/agents/MCP config, and re-run checks without stopping to ask for confirmation of the next step. If the user has selected a target, do not ask before creating, editing, replacing, or deleting generated Codex artifacts in that target (`AGENTS.md`, `.codex/`, `.agents/`, or `~/.codex/`). Preserve unrelated existing Codex config entries in `.codex/config.toml` or `~/.codex/config.toml`, such as `notify`, `projects`, `marketplaces`, or unrelated MCP servers; do not ask about them unless they fail validation or directly conflict with the migration. Do not edit source Claude Code files (`.claude/`, `~/.claude/`, `.mcp.json`, or `.claude.json`), unrelated project code, secrets, or another repository.

## Migration Order

MCP Config Access

High
Category
Agent Snooping
Confidence
93% confidence
Finding

Referencing .mcp.json and .claude.json as migration sources is sensitive because these files may contain tool/server definitions that affect agent connectivity and execution context. Even though the skill says not to edit the source files, it still instructs reading and transforming high-trust MCP configuration into active Codex settings.

Content

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

md
## Autonomy

Keep going until the selected migration is completely done: run the migrator, inspect the report, fix migrated Codex instructions/skills/agents/MCP config, and re-run checks without stopping to ask for confirmation of the next step. If the user has selected a target, do not ask before creating, editing, replacing, or deleting generated Codex artifacts in that target (`AGENTS.md`, `.codex/`, `.agents/`, or `~/.codex/`). Preserve unrelated existing Codex config entries in `.codex/config.toml` or `~/.codex/config.toml`, such as `notify`, `projects`, `marketplaces`, or unrelated MCP servers; do not ask about them unless they fail validation or directly conflict with the migration. Do not edit source Claude Code files (`.claude/`, `~/.claude/`, `.mcp.json`, or `.claude.json`), unrelated project code, secrets, or another repository.

## Migration Order

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
2. Read `references/differences.md` (and refresh Codex docs if its `Docs last checked` date is old).

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
2. Read `references/differences.md` (and refresh Codex docs if its `Docs last checked` date is old).

Agent Config Directory Access

High
Category
Agent Snooping
Confidence
90% confidence
Finding

Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Content

Scanner excerpt · references/differences.md (reported line 82)May include surrounding context.

md
| Source | Codex | Migration behavior | Caveat |
| --- | --- | --- | --- |
| `hooks` in `~/.claude/settings.json`, `.claude/settings.json`, or `.claude/settings.local.json` | `.codex/hooks.json` + `[features].codex_hooks = true` | Partial conversion | Review behavior before relying on migrated hooks; Claude and Codex hook runtimes are not 1:1. |
| `Notification` | `notify` | Manual rewrite only | `notify` is a turn-complete notification command, not a general lifecycle hook or approval-prompt hook. |
| `PreToolUse` | `PreToolUse` in `.codex/hooks.json` | Partial conversion | Codex currently runs PreToolUse for shell commands only and blocks only `permissionDecision: "deny"`, legacy `decision: "block"`, or exit code `2`. |
| `PostToolUse` | `PostToolUse` in `.codex/hooks.json` | Partial conversion | Codex currently runs PostToolUse for shell commands only; `decision: "block"` becomes model feedback, and `continue: false` stops execution. Formatting or fixups that Claude tied to `Edit`/`Write` should move to a `Stop` hook, because only Bash is matched for `PostToolUse`. |

Self-Modification

High
Category
Rogue Agent
Confidence
85% confidence
Finding

Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.

Content

Scanner excerpt · scripts/cli.py (reported line 750)May include surrounding context.

python
"--mcp", action="store_true", help="Write MCP/settings to config.toml."
    )
    parser.add_argument(
        "--skills", action="store_true", help="Write skills under .agents/skills."
    )
    parser.add_argument(
        "--subagents", action="store_true", help="Write agents under .codex/agents."

MCP Config Access

High
Category
Agent Snooping
Confidence
90% confidence
Finding

Skill accesses MCP server configuration files (mcp.json). MCP configs contain server URLs, authentication tokens, and tool definitions — reading them allows the skill to discover and potentially abuse other tool integrations.

Content

Scanner excerpt · scripts/migrate/settings.py (reported line 19)May include surrounding context.

python
)

CLAUDE_MCP_JSON_RELATIVE = (
    Path(".mcp.json"),
    Path(".claude.json"),
)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
81% confidence
Finding

The skill clearly instructs the agent to read and write files, including configuration under project and home directories, but it does not declare an explicit tool scope or allowed-tools boundary. That mismatch increases the chance of overly broad execution in environments that rely on metadata-based permission scoping.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
91% confidence
Finding

The instruction to proceed without asking before creating, editing, replacing, or deleting artifacts removes a human approval checkpoint for impactful configuration changes. In a migration context, that autonomy raises the likelihood of unintended persistence, destructive edits, or unsafe interpretation of ambiguous inputs.

Content

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

md
## Autonomy

Keep going until the selected migration is completely done: run the migrator, inspect the report, fix migrated Codex instructions/skills/agents/MCP config, and re-run checks without stopping to ask for confirmation of the next step. If the user has selected a target, do not ask before creating, editing, replacing, or deleting generated Codex artifacts in that target (`AGENTS.md`, `.codex/`, `.agents/`, or `~/.codex/`). Preserve unrelated existing Codex config entries in `.codex/config.toml` or `~/.codex/config.toml`, such as `notify`, `projects`, `marketplaces`, or unrelated MCP servers; do not ask about them unless they fail validation or directly conflict with the migration. Do not edit source Claude Code files (`.claude/`, `~/.claude/`, `.mcp.json`, or `.claude.json`), unrelated project code, secrets, or another repository.

## Migration Order

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill authorizes creating, editing, replacing, or deleting generated artifacts without an explicit warning or confirmation checkpoint. Because the target includes configuration and agent files, this can lead to destructive or hard-to-review changes in user or project environments.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% 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 · SKILL.md (reported line 67)May include surrounding context.

md
| `Not Added` | `Hook` Notification | Codex does not have an equivalent notification hook |
    | `Not Added` | `Plugin` team-macros | Plugin needs manual setup |

    `Status` must be `Added`, `Check before using`, or `Not Added`. Use `Added` when a Codex-facing artifact was created or changed and needs no special review. Use `Check before using` when a Codex-facing artifact was created or changed but the migration changed semantics, inferred behavior, preserved tool rules as guidance, or dropped unsupported behavior. Use `Not Added` when a source artifact was detected but no Codex-facing artifact was created. `Item` combines the artifact type and concrete item name in one cell. Artifact type must be singular: `Skill`, `Slash command`, `Subagent`, `MCP`, `Hook`, or `Plugin`. Wrap the artifact type in inline code; write the item name as plain text after it. `Notes` is always required; never leave it empty. Keep notes short, plain, and literal. Avoid internal implementation terms such as runtime expansion. Prefer phrases like `Converted into a Codex skill`, `Added as a Codex subagent`, `Added to Codex config`, `Converted into a Codex hook`, `Converted, but some Claude hook behavior differs in Codex`, `Codex does not have an equivalent notification hook`, `Plugin needs manual setup`, or `Plugin marketplace needs manual setup`.

## Self-Healing Loop

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The documented commands include non-dry-run invocations that write to both project and user configuration, but the examples do not prominently warn that they modify persistent settings. Users or downstream agents may execute them as routine inspection steps and unintentionally alter local environments.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/cli.py (reported line 581)May include surrounding context.

python
components = {
        component
        for component in ("mcp", "skills", "subagents")
        if getattr(args, component, False)
    }
    if not components:
        return DEFAULT_COMPONENTS

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/migrate/common.py (reported line 212)May include surrounding context.

python
setattr(
                self,
                field_name,
                getattr(self, field_name) + getattr(other, field_name),
            )

    def render(self, deploy_mode: object, dry_run: bool) -> str:

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/migrate/common.py (reported line 224)May include surrounding context.

python
]
        for summary_field in dataclass_fields(self):
            field_name = summary_field.name
            value = getattr(self, field_name)
            label = SUMMARY_LABELS.get(field_name, field_name.replace("_", " "))
            lines.append(f"  {label}: {value}")
        return "\n".join(lines)

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

The module docstring explains that it converts Claude MCP/settings JSON into Codex config TOML, which implies reading existing configuration and rewriting .codex/config.toml. However, this file contains no explicit user-facing warning, confirmation prompt, or inline disclosure near the described config-rewrite behavior, despite the operation affecting user configuration data.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.