Skip to content

新增 Codex 适配入口,并补充项目级输出路由兼容 - #11

Open
ROOT94-MAX wants to merge 3 commits into
LING71671:mainfrom
ROOT94-MAX:codex/codex-only-adapter
Open

新增 Codex 适配入口,并补充项目级输出路由兼容#11
ROOT94-MAX wants to merge 3 commits into
LING71671:mainfrom
ROOT94-MAX:codex/codex-only-adapter

Conversation

@ROOT94-MAX

@ROOT94-MAX ROOT94-MAX commented Jul 6, 2026

Copy link
Copy Markdown

变更概述

这个 PR 为 open-reverselab 增加了一套可选的 Codex 专属适配层,但不替换原有的 START_HERE 工作流。

本次改动包括:

  • 新增 CODEX.bat,作为 Codex 用户的首选入口
  • 新增 LAUNCHER.bat,作为可选总入口
  • 新增 scripts/codex/ 适配层,包括 open_reverselab_codex MCP 桥接服务
  • 新增 docs/codex/ 文档
  • 新增 tests/codex/test_codex_support.py
  • 修复 open_reverselab_codex_support.py 中上游工具调用可能卡住超时的问题
  • ReverseLabToolsMCP 增加项目级输出目录路由兼容

MCP 桥接说明

这次新增的 open_reverselab_codex 不是单独复制一套 MCP 工具,而是在 Codex 侧增加一层桥接:

  • 提供 codex_enable_project_mode
  • 提供 codex_disable_project_mode
  • 提供 codex_project_mode_status
  • 提供 codex_repair_project_mode

同时,这层桥接会自动包装上游 ReverseLabToolsMCP 的现有工具,并补充可选 project_root 参数,用于把产物路由到当前项目,而不是默认写回 open-reverselab 仓库。

项目级输出路由主要通过以下环境变量完成:

  • REVERSELAB_LAB_ROOT
  • REVERSELAB_DATA_ROOT
  • REVERSELAB_OUTPUT_NAMESPACE

这样可以让上游工具继续复用原有实现,同时把 notes / reports / exports / patches / projects / samples / cases 等产物写入当前项目的 namespaced 目录。

当前范围与行为

  • CODEX.bat 是 Codex 用户的首选入口
  • LAUNCHER.bat 是可选总入口
  • 当前实现里,LAUNCHER.batCodex 菜单会进入新的 Codex 适配层
  • Claude其他 CLI 菜单目前仍然只是回到原版 START_HERE 流程(未测试使用)
  • 本 PR 不替换原作者现有的 START_HERE 工作流
  • README 只补充最小入口说明,详细使用方法放在 docs/codex/

验证

已在干净 worktree 中完成以下检查:

  • python scripts/misc/public_release_check.py --staged
  • python -m pytest tests/codex/test_codex_support.py

说明

  • 这个 PR 目前聚焦在 Codex 方向
  • 如果这个方向合适,后续可以继续补充 Claude / 其他 CLI 的真实适配,而不是当前这种 START_HERE 透传方式
  • AI 辅助起草说明:本 PR 文案由 AI 辅助生成,并已结合本地改动人工复核

Summary by CodeRabbit

  • New Features
    • Added optional Windows launchers for a Codex-focused, menu-based workflow (including project-scoped mode).
    • Introduced Codex project management capabilities such as setup, repair, status, backup, and restore.
  • Bug Fixes
    • Improved output organization by relocating generated artifacts under the new data-root scheme (with optional output namespacing).
    • Improved CTF tooling path/wrapper selection and expanded IDE-local ignores.
  • Documentation
    • Expanded English and Chinese setup/usage guides, including Codex-only workflow and updated legal terms.
  • Tests
    • Added automated coverage for Codex adapter management flows.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a05ac2d-4581-4a3f-9226-d4a283bdbeed

📥 Commits

Reviewing files that changed from the base of the PR and between 10587c0 and 75e1902.

📒 Files selected for processing (4)
  • README.md
  • README.zh.md
  • tools/skills/mcp/ReverseLabToolsMCP/reverselab_mcp/config.py
  • tools/skills/mcp/ReverseLabToolsMCP/reverselab_mcp/tools/web_ctf.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • README.zh.md
  • README.md

📝 Walkthrough

Walkthrough

This PR adds Codex launchers, interactive management flows, a project-scoped MCP adapter, backup and binding support, tests, and documentation. It also separates ReverseLabToolsMCP lab discovery from data output roots and updates CTF path and wrapper resolution.

Changes

Codex Launcher and MCP Adapter

Layer / File(s) Summary
Launcher entrypoints and interactive routing
CODEX.bat, LAUNCHER.bat, scripts/codex/*.ps1
Adds batch wrappers and PowerShell menus for Codex management, START_HERE execution, project operations, and backup/restore flows.
Codex manager and support operations
scripts/codex/python/open_reverselab_codex_manager.py, scripts/codex/python/open_reverselab_codex_support.py
Adds CLI commands and support logic for global MCP configuration, project binding, backups, upgrades, status, and upstream tool invocation.
Project-scoped MCP wrapper
scripts/codex/python/open_reverselab_codex_mcp.py
Routes upstream MCP tools to context-selected project roots and exposes project-mode tools plus diagnostic CLI modes.
Adapter validation and workflow documentation
tests/codex/*, README*, docs/codex/*, .gitignore
Tests adapter lifecycle behavior and documents platform setup, launcher usage, project binding, restoration, and local state directories.

ReverseLabToolsMCP Data Root Refactor

Layer / File(s) Summary
Configuration root separation
tools/skills/mcp/ReverseLabToolsMCP/reverselab_mcp/config.py
Introduces DATA_ROOT and OUTPUT_NAMESPACE, reroutes output directories, and updates filesystem root lists.
CTF path and wrapper resolution
tools/skills/mcp/ReverseLabToolsMCP/reverselab_mcp/tools/web_ctf.py
Uses centralized data roots for CTF paths, validates manifest/request paths under DATA_ROOT, and adds platform wrapper candidate reporting and selection.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Launcher as CODEX.bat / LAUNCHER.bat
  participant Entry as codex_entry.ps1
  participant Manager as open_reverselab_codex_manager.py
  participant Support as open_reverselab_codex_support.py
  participant MCP as open_reverselab_codex_mcp.py

  User->>Launcher: Start Codex workflow
  Launcher->>Entry: Invoke PowerShell entrypoint
  Entry->>Manager: Run install, bind, status, or restore command
  Manager->>Support: Execute adapter operation
  Support-->>Manager: Return JSON status and confirmation data
  Manager-->>Entry: Return payload and exit code
  Entry-->>User: Display result or request confirmation
  MCP->>Support: Resolve project root and invoke project-mode operation
  Support-->>MCP: Return routed operation result
Loading

Possibly related PRs

Suggested reviewers: cursoragent

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了新增 Codex 入口与项目级输出路由兼容两项核心变更。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (4)
scripts/codex/python/open_reverselab_codex_mcp.py (1)

33-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reaching into FastMCP's private _tool_manager._tools instead of the public list_tools() API.

upstream.mcp._tool_manager._tools accesses an internal, underscore-prefixed attribute of the upstream FastMCP server. The SDK exposes a public accessor for this exact purpose — return self._tools.get(name) def list_tools(self) -> list[Tool]: """List all registered tools.""" returns the list of registered Tool objects. Relying on the private dict makes this wrapper fragile to internal refactors in fastmcp/mcp releases.

🛠️ Suggested fix
 def _load_upstream_tools() -> dict[str, Any]:
     upstream = _load_upstream_module()
-    return upstream.mcp._tool_manager._tools
+    return {tool.name: tool for tool in upstream.mcp._tool_manager.list_tools()}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/codex/python/open_reverselab_codex_mcp.py` around lines 33 - 36, The
_load_upstream_tools helper is reaching into FastMCP internals via
upstream.mcp._tool_manager._tools, which should be replaced with the public
list_tools() API. Update _load_upstream_tools to use the upstream mcp object's
supported tool accessor, and adjust any callers that expect a raw dict so they
work with the returned Tool objects or convert them explicitly if needed. Keep
the change localized around _load_upstream_tools and the code that consumes its
result.
tests/codex/test_codex_support.py (1)

47-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a regression test for the single-line developer_instructions edge case.

Given the pattern already established here (test_upsert_and_remove_global_instructions_and_server), it'd be cheap to add a case where original contains developer_instructions = "short text" (single-line, not triple-quoted) to lock in whatever fix is applied for the duplicate-key risk flagged in open_reverselab_codex_support.py.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/codex/test_codex_support.py` around lines 47 - 69, Add a regression
case in test_upsert_and_remove_global_instructions_and_server for the
single-line developer_instructions form, since
ensure_global_server_and_instructions/remove_global_instructions can hit a
duplicate-key path when the input already has developer_instructions = "short
text". Update the test to start from an original config with that exact
single-line key, then verify ensure_global_server_and_instructions still inserts
the global block and server cleanly and remove_global_server_and_instructions
restores the original developer_instructions entry without duplication or
corruption.
scripts/codex/codex_entry.ps1 (2)

39-108: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate command-execution logic.

Invoke-ManagerJson and Invoke-PythonJson are nearly identical (build args, invoke python, parse JSON with the same fallback). Consider having Invoke-ManagerJson delegate to Invoke-PythonJson with $Context.ManagerScript to remove the duplication.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/codex/codex_entry.ps1` around lines 39 - 108, `Invoke-ManagerJson`
and `Invoke-PythonJson` duplicate the same Python invocation and JSON parsing
flow; refactor `Invoke-ManagerJson` to call `Invoke-PythonJson` using
`$Context.ManagerScript` and `$ManagerArgs`, keeping the shared execution/parse
fallback logic in one place. Preserve the existing return shape (`ExitCode`,
`Output`, `Payload`) and the Python resolution/error handling in
`Resolve-PythonCommand` so both paths behave identically.

55-56: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Merging stderr into stdout before JSON parsing risks corrupting the payload.

2>&1 | Out-String combines stdout and stderr before ConvertFrom-Json. If the invoked Python script writes anything to stderr (warnings, tracebacks) alongside its JSON stdout, parsing will fail and fall back to raw_output/parse_error — degrading gracefully but losing the structured status the UI relies on (e.g., Show-StartupSummary reading .global_install_ready). Capturing stderr separately would keep JSON parsing reliable.

Also applies to: 91-92

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/codex/codex_entry.ps1` around lines 55 - 56, The JSON parsing path in
codex_entry.ps1 is currently mixing stderr into stdout, which can corrupt the
payload before ConvertFrom-Json runs. Update the command execution around the
$output/$exitCode capture so stdout is preserved for JSON parsing and stderr is
captured separately or handled independently, then only feed the clean stdout
into the existing parsing/fallback logic. Apply the same fix anywhere else the
same pattern appears, including the later duplicated block referenced by the
comment, so downstream consumers like Show-StartupSummary can keep reading
structured fields reliably.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/codex/codex_entry.ps1`:
- Around line 10-22: Resolve-PythonCommand currently returns any available
python or py executable without verifying it meets the required Python 3.10+
version. Update the version-checking logic in Resolve-PythonCommand so it
inspects the selected interpreter’s version before returning Exe/Args, and only
accepts 3.10 or newer; if you keep the current behavior, adjust the later Python
3.10+ error message to match. Use the Resolve-PythonCommand function and the
subsequent version check/error path as the main touchpoints.

In `@scripts/codex/python/open_reverselab_codex_manager.py`:
- Around line 140-150: The restore subcommands currently invoke destructive
helpers without any confirmation gate. Update the argument setup in
open_reverselab_codex_manager.py for backup_restore and upgrade_batch_restore so
they require the same safeguard used by bind_project, unbind_project, and
repair_project, such as a confirmation text or --yes flag, before
command_backup_restore or command_upgrade_batch_restore can run.

In `@scripts/codex/python/open_reverselab_codex_mcp.py`:
- Around line 60-68: The async wrapper in _make_upstream_wrapper is calling
support.invoke_upstream_tool inline even though that helper blocks on
subprocess.run, which can stall the event loop. Update wrapper to offload the
invoke_upstream_tool call via asyncio.to_thread, keeping the existing
ctx/project_root/target_project flow and preserving the project_root_used field
handling on dict results.

In `@scripts/codex/python/open_reverselab_codex_support.py`:
- Around line 360-368: `find_assignment_span` only detects triple-quoted values,
so `upsert_developer_instructions` can miss an existing single-line
`developer_instructions` entry and insert a duplicate key. Update the matching
logic in `find_assignment_span` (and any callers such as
`upsert_developer_instructions`) to recognize both single-line quoted strings
and triple-quoted strings, then replace the existing assignment instead of
inserting a new one when `developer_instructions` is already present.
- Around line 960-1001: The managed .gitignore block in ensure_gitignore_block
and remove_gitignore_block is missing a closing marker, so the regex can
overmatch and remove user-added entries. Add an explicit end/footer marker to
the managed ignore block, then update both functions to replace/remove content
only between the header and footer, similar to the
PROJECT_BLOCK_BEGIN/PROJECT_BLOCK_END handling used elsewhere in
open_reverselab_codex_support.py.
- Around line 761-785: The confirmation flow does not surface the privileged
Codex runtime settings emitted by project_codex_ctf_config_text() and
project_codex_managed_block(), so update confirm_text() to show the
approval_policy and sandbox_mode values before proceeding, or change the flow so
dangerous mode must be explicitly opted into. Use the existing PROJECT_CODEX_*
helpers and the confirm_text() prompt generation to keep the preview consistent
with the runtime configuration.

In `@tools/skills/mcp/ReverseLabToolsMCP/reverselab_mcp/config.py`:
- Around line 22-28: Validate OUTPUT_NAMESPACE before it is used in _data_dir as
a path segment, since the current os.environ.get("REVERSELAB_OUTPUT_NAMESPACE",
"").strip() value can contain traversal like ../outside and escape the intended
DATA_ROOT scope. Add a strict check in config.py, using the OUTPUT_NAMESPACE
constant and _data_dir(name), to allow only a safe namespace format (for
example, a single relative segment with no separators or dot segments) and
reject or ignore invalid values before constructing the Path.

In `@tools/skills/mcp/ReverseLabToolsMCP/reverselab_mcp/tools/web_ctf.py`:
- Line 249: Sanitize the case name before building the directory path in
web_ctf.py: the current case_dir assignment in the case-creation flow can be
abused with path traversal values. Reuse the existing _safe_case_name(...)
helper, as already done in ctf_save_request, so the name used by the case
directory logic is normalized and confined under CASES_DIR; update the relevant
case directory construction in the same flow to use the sanitized name instead
of the raw input.

---

Nitpick comments:
In `@scripts/codex/codex_entry.ps1`:
- Around line 39-108: `Invoke-ManagerJson` and `Invoke-PythonJson` duplicate the
same Python invocation and JSON parsing flow; refactor `Invoke-ManagerJson` to
call `Invoke-PythonJson` using `$Context.ManagerScript` and `$ManagerArgs`,
keeping the shared execution/parse fallback logic in one place. Preserve the
existing return shape (`ExitCode`, `Output`, `Payload`) and the Python
resolution/error handling in `Resolve-PythonCommand` so both paths behave
identically.
- Around line 55-56: The JSON parsing path in codex_entry.ps1 is currently
mixing stderr into stdout, which can corrupt the payload before ConvertFrom-Json
runs. Update the command execution around the $output/$exitCode capture so
stdout is preserved for JSON parsing and stderr is captured separately or
handled independently, then only feed the clean stdout into the existing
parsing/fallback logic. Apply the same fix anywhere else the same pattern
appears, including the later duplicated block referenced by the comment, so
downstream consumers like Show-StartupSummary can keep reading structured fields
reliably.

In `@scripts/codex/python/open_reverselab_codex_mcp.py`:
- Around line 33-36: The _load_upstream_tools helper is reaching into FastMCP
internals via upstream.mcp._tool_manager._tools, which should be replaced with
the public list_tools() API. Update _load_upstream_tools to use the upstream mcp
object's supported tool accessor, and adjust any callers that expect a raw dict
so they work with the returned Tool objects or convert them explicitly if
needed. Keep the change localized around _load_upstream_tools and the code that
consumes its result.

In `@tests/codex/test_codex_support.py`:
- Around line 47-69: Add a regression case in
test_upsert_and_remove_global_instructions_and_server for the single-line
developer_instructions form, since
ensure_global_server_and_instructions/remove_global_instructions can hit a
duplicate-key path when the input already has developer_instructions = "short
text". Update the test to start from an original config with that exact
single-line key, then verify ensure_global_server_and_instructions still inserts
the global block and server cleanly and remove_global_server_and_instructions
restores the original developer_instructions entry without duplication or
corruption.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 86c96a46-4e05-4154-b1be-f20e0d7bd5ab

📥 Commits

Reviewing files that changed from the base of the PR and between f2079b0 and 10587c0.

📒 Files selected for processing (15)
  • .gitignore
  • CODEX.bat
  • LAUNCHER.bat
  • README.md
  • README.zh.md
  • docs/codex/CODEX_ONLY.md
  • docs/codex/CODEX_USAGE.zh-CN.md
  • scripts/codex/codex_entry.ps1
  • scripts/codex/launcher_entry.ps1
  • scripts/codex/python/open_reverselab_codex_manager.py
  • scripts/codex/python/open_reverselab_codex_mcp.py
  • scripts/codex/python/open_reverselab_codex_support.py
  • tests/codex/test_codex_support.py
  • tools/skills/mcp/ReverseLabToolsMCP/reverselab_mcp/config.py
  • tools/skills/mcp/ReverseLabToolsMCP/reverselab_mcp/tools/web_ctf.py

Comment on lines +10 to +22
function Resolve-PythonCommand {
$python = Get-Command python -ErrorAction SilentlyContinue
if ($python) {
return @{ Exe = $python.Source; Args = @() }
}

$py = Get-Command py -ErrorAction SilentlyContinue
if ($py) {
return @{ Exe = $py.Source; Args = @("-3") }
}

return $null
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does PEP 604 union syntax (X | Y) require from __future__ import annotations to run on Python 3.8/3.9?

💡 Result:

Whether PEP 604 union syntax (X | Y) requires from future import annotations to run on Python 3.8/3.9 depends entirely on how the syntax is used. The short answer is: you cannot use PEP 604 syntax as an actual operator in Python 3.8 or 3.9 because those versions lack the necessary runtime implementation [1][2]. However, if the syntax is used strictly within type annotations, you can use from future import annotations to avoid runtime errors [1]. Here is the technical breakdown: 1. Runtime Evaluation: PEP 604 is a feature of Python 3.10 and later [1][2]. If you attempt to use the | operator between types (e.g., int | str) anywhere that the Python interpreter evaluates the expression—such as in isinstance calls, variable assignments, or function bodies—it will raise a TypeError in Python 3.8 and 3.9 [1]. 2. Type Annotations with from future import annotations: If you only use this syntax in type annotations (e.g., def func(x: int | str): pass), you can use from future import annotations [1]. This import enables "postponed evaluation" (PEP 563), which tells Python to treat annotations as strings rather than evaluating them at definition time [3][1]. Because the interpreter does not evaluate the expression, the | operator is never executed, and the code will run without error [1]. 3. Runtime Dependency: Note that even if your code runs, any external tool or library that inspects your annotations at runtime (like Pydantic, FastAPI, or custom reflection tools) will likely fail if it attempts to resolve those annotations, because the underlying Python 3.8/3.9 interpreter does not know how to handle the | operator [1]. In summary, from future import annotations is not a "magic" way to backport PEP 604; it is a way to hide the syntax from the interpreter during evaluation [1]. For any use case involving runtime evaluation or reflection, PEP 604 is not supported on Python 3.8 or 3.9 [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== codex_entry.ps1 relevant lines =="
nl -ba scripts/codex/codex_entry.ps1 | sed -n '1,180p'

echo
echo "== search for python version checks and annotations =="
rg -n --hidden --glob '!**/.git/**' -e 'from __future__ import annotations|Path \| None|sys\.version_info|Python 3\.10\+' scripts . 2>/dev/null | sed -n '1,220p'

Repository: LING71671/open-reverselab

Length of output: 240


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== scripts/codex/codex_entry.ps1 (lines 1-180) =="
sed -n '1,180p' scripts/codex/codex_entry.ps1 | cat -n

echo
echo "== search for python version checks and annotations =="
rg -n --hidden --glob '!**/.git/**' \
  -e 'from __future__ import annotations' \
  -e 'Path \| None' \
  -e 'Python 3\.10\+' \
  -e 'sys\.version_info' \
  scripts . 2>/dev/null | sed -n '1,240p'

Repository: LING71671/open-reverselab

Length of output: 19807


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== python version requirements / markers =="
rg -n --hidden --glob '!**/.git/**' \
  -e 'python_requires' \
  -e 'requires-python' \
  -e '3\.10\+' \
  -e '>= *3\.10' \
  -e '>= *3\.11' \
  -e '>= *3\.12' \
  -e 'match [A-Za-z_]' \
  scripts/codex pyproject.toml setup.cfg setup.py 2>/dev/null | sed -n '1,240p'

echo
echo "== codex python files outline =="
for f in scripts/codex/python/open_reverselab_codex_manager.py scripts/codex/python/open_reverselab_codex_mcp.py scripts/codex/python/open_reverselab_codex_support.py; do
  if [ -f "$f" ]; then
    echo "-- $f --"
    sed -n '1,40p' "$f" | cat -n
  fi
done

Repository: LING71671/open-reverselab

Length of output: 1026


Check the Python version before returning a command
scripts/codex/codex_entry.ps1:10 only checks that python/py exists, so it can hand back an older interpreter while the script still says Python 3.10+ 未找到. If 3.10+ is required, reject versions below that here; otherwise relax the error text.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/codex/codex_entry.ps1` around lines 10 - 22, Resolve-PythonCommand
currently returns any available python or py executable without verifying it
meets the required Python 3.10+ version. Update the version-checking logic in
Resolve-PythonCommand so it inspects the selected interpreter’s version before
returning Exe/Args, and only accepts 3.10 or newer; if you keep the current
behavior, adjust the later Python 3.10+ error message to match. Use the
Resolve-PythonCommand function and the subsequent version check/error path as
the main touchpoints.

Comment on lines +140 to +150
backup_restore = sub.add_parser("backup-restore", help="Restore a local adapter backup")
backup_restore.add_argument("backup_id")
backup_restore.set_defaults(func=command_backup_restore)

upgrade_batch_list = sub.add_parser("upgrade-batch-list", help="List recorded project upgrade batches")
upgrade_batch_list.set_defaults(func=command_upgrade_batch_list)

upgrade_batch_restore = sub.add_parser("upgrade-batch-restore", help="Restore one project from an upgrade batch")
upgrade_batch_restore.add_argument("batch_id")
upgrade_batch_restore.add_argument("project_id")
upgrade_batch_restore.set_defaults(func=command_upgrade_batch_restore)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== files ==\n'
git ls-files | rg '^(scripts/codex/python/open_reverselab_codex_manager\.py|scripts/codex/python/open_reverselab_codex_support\.py|codex_entry\.ps1)$'

printf '\n== manager outline ==\n'
ast-grep outline scripts/codex/python/open_reverselab_codex_manager.py --view expanded || true

printf '\n== support outline ==\n'
ast-grep outline scripts/codex/python/open_reverselab_codex_support.py --view expanded || true

printf '\n== entry ps1 search ==\n'
rg -n "Show-PreviewAndConfirm|RESTORE|backup-restore|upgrade-batch-restore|--confirm-text|confirm" codex_entry.ps1 scripts/codex/python/open_reverselab_codex_manager.py scripts/codex/python/open_reverselab_codex_support.py

Repository: LING71671/open-reverselab

Length of output: 15531


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in scripts/codex/python/open_reverselab_codex_manager.py scripts/codex/python/open_reverselab_codex_support.py codex_entry.ps1; do
  if [ -f "$f" ]; then
    echo "== $f =="
    wc -l "$f"
  fi
done

printf '\n== manager relevant lines ==\n'
sed -n '120,190p' scripts/codex/python/open_reverselab_codex_manager.py

printf '\n== support relevant lines ==\n'
sed -n '1,220p' scripts/codex/python/open_reverselab_codex_support.py

printf '\n== entry relevant lines ==\n'
sed -n '350,450p' codex_entry.ps1

Repository: LING71671/open-reverselab

Length of output: 9422


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== manager command implementations ==\n'
sed -n '1,120p' scripts/codex/python/open_reverselab_codex_manager.py

printf '\n== support restore functions ==\n'
sed -n '560,690p' scripts/codex/python/open_reverselab_codex_support.py

printf '\n== support confirmation helpers around project mode ==\n'
sed -n '1288,1410p' scripts/codex/python/open_reverselab_codex_support.py

Repository: LING71671/open-reverselab

Length of output: 14362


Add a confirmation gate to the restore subcommands. backup-restore and upgrade-batch-restore at scripts/codex/python/open_reverselab_codex_manager.py:140-150 call destructive restore helpers directly, with no --confirm-text/--yes guard like bind-project, unbind-project, and repair-project. Add the same safeguard here so direct CLI calls can’t overwrite or delete files by accident.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/codex/python/open_reverselab_codex_manager.py` around lines 140 -
150, The restore subcommands currently invoke destructive helpers without any
confirmation gate. Update the argument setup in open_reverselab_codex_manager.py
for backup_restore and upgrade_batch_restore so they require the same safeguard
used by bind_project, unbind_project, and repair_project, such as a confirmation
text or --yes flag, before command_backup_restore or
command_upgrade_batch_restore can run.

Comment on lines +60 to +68
def _make_upstream_wrapper(tool_name: str, tool_obj: Any):
async def wrapper(*args, ctx: Context, project_root: str = "", **kwargs):
roots = await _context_roots(ctx)
target_project = _selected_project_root(project_root, roots)
result = support.invoke_upstream_tool(tool_name, kwargs, target_project)
if isinstance(result, dict):
result.setdefault("project_root_used", str(target_project))
return result

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== open_reverselab_codex_mcp.py ==\n'
sed -n '1,140p' scripts/codex/python/open_reverselab_codex_mcp.py | cat -n

printf '\n== open_reverselab_codex_support.py ==\n'
sed -n '1,260p' scripts/codex/python/open_reverselab_codex_support.py | cat -n

printf '\n== Search for invoke_upstream_tool usage and async/thread handling ==\n'
rg -n "invoke_upstream_tool|to_thread|run_in_executor|subprocess\.run|async def wrapper|FastMCP" scripts/codex/python -S

Repository: LING71671/open-reverselab

Length of output: 17110


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== invoke_upstream_tool and nearby helpers ==\n'
sed -n '1700,1815p' scripts/codex/python/open_reverselab_codex_support.py | cat -n

printf '\n== direct subprocess.run sites in support ==\n'
sed -n '1538,1568p' scripts/codex/python/open_reverselab_codex_support.py | cat -n
sed -n '1778,1805p' scripts/codex/python/open_reverselab_codex_support.py | cat -n

Repository: LING71671/open-reverselab

Length of output: 7586


Offload upstream tool execution from the async wrapper scripts/codex/python/open_reverselab_codex_mcp.py:61-67wrapper is async, but it calls support.invoke_upstream_tool(...) inline, and that helper runs a blocking subprocess.run(...) with no timeout. Move the call to asyncio.to_thread(...) so one long upstream scan doesn’t stall other MCP requests on the same event loop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/codex/python/open_reverselab_codex_mcp.py` around lines 60 - 68, The
async wrapper in _make_upstream_wrapper is calling support.invoke_upstream_tool
inline even though that helper blocks on subprocess.run, which can stall the
event loop. Update wrapper to offload the invoke_upstream_tool call via
asyncio.to_thread, keeping the existing ctx/project_root/target_project flow and
preserving the project_root_used field handling on dict results.

Comment on lines +360 to +368
def find_assignment_span(text: str, key: str) -> tuple[int, int, str] | None:
pattern = re.compile(
rf'(?ms)^(?P<prefix>\s*{re.escape(key)}\s*=\s*)(?P<quote>"""|\'\'\')(?P<body>.*?)(?P=quote)\s*$'
)
match = pattern.search(text)
if not match:
return None
return match.start(), match.end(), match.group("body")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

developer_instructions upsert only recognizes triple-quoted strings — risk of duplicate-key TOML corruption.

find_assignment_span matches only """..."""/'''...''' values. If a user's existing ~/.codex/config.toml already has developer_instructions = "short text" (a common single-line form), find_assignment_span returns None, and upsert_developer_instructions falls through to inserting a brand-new developer_instructions = """...""" assignment before the first [ header — producing two developer_instructions keys in the same document. TOML forbids duplicate keys, so this can render the user's global Codex config unparsable after running install/repair.

🛠️ Suggested direction
 def find_assignment_span(text: str, key: str) -> tuple[int, int, str] | None:
     pattern = re.compile(
         rf'(?ms)^(?P<prefix>\s*{re.escape(key)}\s*=\s*)(?P<quote>"""|\'\'\')(?P<body>.*?)(?P=quote)\s*$'
     )
     match = pattern.search(text)
     if not match:
-        return None
+        # Fall back to detecting single-line quoted assignments so we don't
+        # blindly insert a second `developer_instructions = ...` key.
+        single_line = re.compile(
+            rf'(?m)^\s*{re.escape(key)}\s*=\s*(?:"(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)*\')\s*$'
+        )
+        single_match = single_line.search(text)
+        if single_match:
+            raise ValueError(
+                f"{key} exists as a single-line string; refusing to auto-migrate to a managed block"
+            )
+        return None
     return match.start(), match.end(), match.group("body")

Longer term, this file hand-rolls TOML editing with regexes across ~10 functions (global block, gitignore block, project codex block). Consider adopting a round-trip TOML library (e.g. tomlkit) to eliminate this whole class of quoting/escaping edge cases.

Also applies to: 405-425

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 360-362: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.compile(
rf'(?ms)^(?P\s*{re.escape(key)}\s*=\s*)(?P"""|''')(?P.?)(?P=quote)\s$'
)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/codex/python/open_reverselab_codex_support.py` around lines 360 -
368, `find_assignment_span` only detects triple-quoted values, so
`upsert_developer_instructions` can miss an existing single-line
`developer_instructions` entry and insert a duplicate key. Update the matching
logic in `find_assignment_span` (and any callers such as
`upsert_developer_instructions`) to recognize both single-line quoted strings
and triple-quoted strings, then replace the existing assignment instead of
inserting a new one when `developer_instructions` is already present.

Comment on lines +761 to +785
def project_codex_ctf_config_text() -> str:
return "\n".join(
[
"# open-reverselab Codex CTF runtime template",
f'model_instructions_file = {toml_basic_string(PROJECT_CODEX_PROMPT)}',
'approval_policy = "never"',
'sandbox_mode = "danger-full-access"',
"",
]
)


def project_codex_managed_block() -> str:
return "\n".join(
[
PROJECT_CODEX_BLOCK_BEGIN,
"# Managed by open-reverselab CODEX.bat / LAUNCHER.bat",
"# Source reference: .codex/open-reverselab.ctf.config.toml",
f'model_instructions_file = {toml_basic_string(PROJECT_CODEX_PROMPT)}',
'approval_policy = "never"',
'sandbox_mode = "danger-full-access"',
PROJECT_CODEX_BLOCK_END,
]
).strip()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '700,820p' scripts/codex/python/open_reverselab_codex_support.py
rg -n "enable|repair|danger-full-access|approval_policy|sandbox_mode|confirm" scripts/codex/python/open_reverselab_codex_support.py

Repository: LING71671/open-reverselab

Length of output: 7643


🏁 Script executed:

sed -n '840,915p' scripts/codex/python/open_reverselab_codex_support.py
sed -n '1110,1410p' scripts/codex/python/open_reverselab_codex_support.py

Repository: LING71671/open-reverselab

Length of output: 15527


Surface the runtime privilege change in the confirmation flow

project_codex_ctf_config_text() and project_codex_managed_block() always write approval_policy = "never" and sandbox_mode = "danger-full-access" for enable/repair, but confirm_text() only asks for <project> open-reverselab Codex 模式. Expose those flags in the preview/confirmation, or make the dangerous mode opt-in.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/codex/python/open_reverselab_codex_support.py` around lines 761 -
785, The confirmation flow does not surface the privileged Codex runtime
settings emitted by project_codex_ctf_config_text() and
project_codex_managed_block(), so update confirm_text() to show the
approval_policy and sandbox_mode values before proceeding, or change the flow so
dangerous mode must be explicitly opted into. Use the existing PROJECT_CODEX_*
helpers and the confirm_text() prompt generation to keep the preview consistent
with the runtime configuration.

Comment on lines +960 to +1001
def ensure_gitignore_block(project_root: Path) -> dict[str, Any]:
if not (project_root / ".git").exists():
return {"updated": False, "reason": "not-a-git-project", "path": str(project_root / ".gitignore")}

gitignore = project_root / ".gitignore"
existing = read_text(gitignore)
block = MANAGED_IGNORE_HEADER + "\n" + "\n".join(MANAGED_IGNORE_ENTRIES)
if MANAGED_IGNORE_HEADER in existing:
updated = re.sub(
rf"{re.escape(MANAGED_IGNORE_HEADER)}.*?(?=\n\n|\Z)",
block,
existing,
flags=re.S,
).rstrip() + "\n"
else:
updated = existing.rstrip()
if updated:
updated += "\n\n"
updated += block + "\n"
write_text(gitignore, updated)
return {"updated": True, "path": str(gitignore)}


def remove_gitignore_block(project_root: Path) -> dict[str, Any]:
gitignore = project_root / ".gitignore"
if not gitignore.exists():
return {"updated": False, "path": str(gitignore)}
existing = read_text(gitignore)
if MANAGED_IGNORE_HEADER not in existing:
return {"updated": False, "path": str(gitignore)}
updated = re.sub(
rf"\n?{re.escape(MANAGED_IGNORE_HEADER)}.*?(?=\n\n|\Z)",
"",
existing,
flags=re.S,
).strip()
if updated:
write_text(gitignore, updated + "\n")
else:
gitignore.unlink()
return {"updated": True, "path": str(gitignore)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Gitignore managed block has no closing marker — repeated ensure_gitignore_block runs can delete unrelated user entries.

Unlike the AGENTS.md/.codex/config.toml managed blocks (which use explicit BEGIN/END markers), the ignore block only has MANAGED_IGNORE_HEADER with no footer. The update regex HEADER.*?(?=\n\n|\Z) relies on finding a blank line or end-of-file to bound the block. If a user (or another tool) appends lines directly below the managed entries without a blank-line separator — a very natural thing to do in a .gitignore — the next repair/upgrade call (apply_project_bindingensure_gitignore_block) will silently consume and delete that trailing content along with the managed block.

🛠️ Suggested direction
 MANAGED_IGNORE_HEADER = "# open-reverselab Codex managed ignore entries"
+MANAGED_IGNORE_FOOTER = "# open-reverselab Codex managed ignore entries end"

and bound both ensure_gitignore_block/remove_gitignore_block regexes between header and footer, mirroring the PROJECT_BLOCK_BEGIN/PROJECT_BLOCK_END pattern used elsewhere in this file.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 967-972: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.sub(
rf"{re.escape(MANAGED_IGNORE_HEADER)}.*?(?=\n\n|\Z)",
block,
existing,
flags=re.S,
)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)


[warning] 989-994: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.sub(
rf"\n?{re.escape(MANAGED_IGNORE_HEADER)}.*?(?=\n\n|\Z)",
"",
existing,
flags=re.S,
)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/codex/python/open_reverselab_codex_support.py` around lines 960 -
1001, The managed .gitignore block in ensure_gitignore_block and
remove_gitignore_block is missing a closing marker, so the regex can overmatch
and remove user-added entries. Add an explicit end/footer marker to the managed
ignore block, then update both functions to replace/remove content only between
the header and footer, similar to the PROJECT_BLOCK_BEGIN/PROJECT_BLOCK_END
handling used elsewhere in open_reverselab_codex_support.py.

Comment on lines +22 to +28
OUTPUT_NAMESPACE = os.environ.get("REVERSELAB_OUTPUT_NAMESPACE", "").strip()


def _data_dir(name: str) -> Path:
if OUTPUT_NAMESPACE:
return DATA_ROOT / name / OUTPUT_NAMESPACE
return DATA_ROOT / name

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Validate REVERSELAB_OUTPUT_NAMESPACE before using it as a path segment.

A namespace like ../outside makes _data_dir("exports") resolve outside the intended DATA_ROOT / exports tree, which breaks the project-scoped routing boundary.

Proposed fix
 OUTPUT_NAMESPACE = os.environ.get("REVERSELAB_OUTPUT_NAMESPACE", "").strip()
+if OUTPUT_NAMESPACE:
+    namespace_path = Path(OUTPUT_NAMESPACE)
+    if (
+        namespace_path.is_absolute()
+        or namespace_path.name != OUTPUT_NAMESPACE
+        or OUTPUT_NAMESPACE in {".", ".."}
+    ):
+        raise ValueError("REVERSELAB_OUTPUT_NAMESPACE must be a single safe path segment")
 
 
 def _data_dir(name: str) -> Path:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
OUTPUT_NAMESPACE = os.environ.get("REVERSELAB_OUTPUT_NAMESPACE", "").strip()
def _data_dir(name: str) -> Path:
if OUTPUT_NAMESPACE:
return DATA_ROOT / name / OUTPUT_NAMESPACE
return DATA_ROOT / name
OUTPUT_NAMESPACE = os.environ.get("REVERSELAB_OUTPUT_NAMESPACE", "").strip()
if OUTPUT_NAMESPACE:
namespace_path = Path(OUTPUT_NAMESPACE)
if (
namespace_path.is_absolute()
or namespace_path.name != OUTPUT_NAMESPACE
or OUTPUT_NAMESPACE in {".", ".."}
):
raise ValueError("REVERSELAB_OUTPUT_NAMESPACE must be a single safe path segment")
def _data_dir(name: str) -> Path:
if OUTPUT_NAMESPACE:
return DATA_ROOT / name / OUTPUT_NAMESPACE
return DATA_ROOT / name
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/skills/mcp/ReverseLabToolsMCP/reverselab_mcp/config.py` around lines 22
- 28, Validate OUTPUT_NAMESPACE before it is used in _data_dir as a path
segment, since the current os.environ.get("REVERSELAB_OUTPUT_NAMESPACE",
"").strip() value can contain traversal like ../outside and escape the intended
DATA_ROOT scope. Add a strict check in config.py, using the OUTPUT_NAMESPACE
constant and _data_dir(name), to allow only a safe namespace format (for
example, a single relative segment with no separators or dot segments) and
reject or ignore invalid values before constructing the Path.

def ctf_new_challenge(name: str, url: str = "") -> dict:
"""Create a new CTF challenge case directory."""
case_dir = REVERSE_ROOT / "cases" / name
case_dir = CASES_DIR / name

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Sanitize name before creating the case directory.

case_dir = CASES_DIR / name lets an MCP caller pass values like ../other-case and create/write outside CASES_DIR. Reuse _safe_case_name(...) here like ctf_save_request already does for request paths.

Proposed fix
 def ctf_new_challenge(name: str, url: str = "") -> dict:
     """Create a new CTF challenge case directory."""
-    case_dir = CASES_DIR / name
+    safe_name = _safe_case_name(name)
+    case_dir = (CASES_DIR / safe_name).resolve()
+    ensure_under(case_dir, [CASES_DIR], "case directory")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case_dir = CASES_DIR / name
def ctf_new_challenge(name: str, url: str = "") -> dict:
"""Create a new CTF challenge case directory."""
safe_name = _safe_case_name(name)
case_dir = (CASES_DIR / safe_name).resolve()
ensure_under(case_dir, [CASES_DIR], "case directory")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/skills/mcp/ReverseLabToolsMCP/reverselab_mcp/tools/web_ctf.py` at line
249, Sanitize the case name before building the directory path in web_ctf.py:
the current case_dir assignment in the case-creation flow can be abused with
path traversal values. Reuse the existing _safe_case_name(...) helper, as
already done in ctf_save_request, so the name used by the case directory logic
is normalized and confined under CASES_DIR; update the relevant case directory
construction in the same flow to use the sanitized name instead of the raw
input.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant