Skip to content

[OMP] active-task 指针可以解析到项目外目录 #536

Description

@izumi0uu

问题

OMP 扩展读取 session 文件里的 current_task 后,直接执行 join(projectRoot, currentTask),没有验证解析后的目录是否仍在项目根目录或明确配置的受信任 Trellis 根目录内。

因此,一个形如 ../trellis-outside-task 的活动任务引用可以把 OMP 的 task 状态和文档读取到项目外。扩展随后会读取该目录的 task.jsonprd.mdinfo.md,并将这些内容放进 trellis-task-context

这不是远程攻击:修改本地 session 状态仍需要本地写权限。但扩展不应该把持久化指针当成可信路径,更不应该因此把另一个目录的任务文档送入模型上下文。

如何复现

  1. 在一个 Trellis OMP 项目中启动会话,使 .trellis/.runtime/sessions/ 中生成当前会话的 JSON 文件。

  2. 在项目旁边创建一个脱敏的任务目录:

    mkdir -p ../trellis-outside-task
    printf '{"title":"Outside task","status":"planning"}\n' \
      > ../trellis-outside-task/task.json
    printf 'OUTSIDE-PRD\n' > ../trellis-outside-task/prd.md
  3. 修改当前会话 JSON 的 current_task../trellis-outside-task。例如:

    python3 - "$HOME/.path/to/current-session.json" <<'PY'
    import json
    import sys
    from pathlib import Path
    
    path = Path(sys.argv[1])
    data = json.loads(path.read_text())
    data['current_task'] = '../trellis-outside-task'
    path.write_text(json.dumps(data))
    PY
  4. 重新触发 OMP 的 session start,或重新打开该会话。

  5. 检查 trellis-task-context 或当前任务状态。

实际结果

当前 OMP 扩展把项目外目录识别为 active task,并读取其中的 task.jsonprd.md。在上述复现中,任务上下文会出现 OUTSIDE-PRD,而该文件不在项目目录内。

同样的问题也适用于通过软链接逃逸的任务目录。项目内合法的 .trellis/tasks/... 引用则可以正常工作。

期望结果

OMP 应该通过统一的 task resolver 处理 active-task 引用:

  • 先规范化引用并解析真实路径;
  • 只允许项目根目录内的 task,或 .trellis/config.yaml 明确声明的受信任 Trellis 根目录;
  • 验证目标是目录,并且包含合法的 task.json
  • .. 越界、绝对路径、软链接逃逸或缺失目标都回退为 no_task
  • OMP dogfood copy 和 CLI 打包模板使用同一套边界检查。

当前 main 中的代码路径

main @ c8e327a99d8ae7bfcb67523ab89b0ca8c721e63a 中,active task 仍然直接拼接路径:

  • const currentTask = sessionData.current_task;
    if (typeof currentTask !== "string" || !currentTask)
    return { status: "no_task", taskDir: null, taskTitle: null };
    const taskDir = join(projectRoot, currentTask);
    const taskJsonPath = join(taskDir, "task.json");
    if (!existsSync(taskJsonPath)) return { status: "no_task", taskDir: null, taskTitle: null };
    let taskData: Record<string, unknown>;
    try {
    taskData = JSON.parse(readFileSync(taskJsonPath, "utf-8"));
    } catch {
    return { status: "no_task", taskDir: null, taskTitle: null };
    }
    return {
    status: typeof taskData.status === "string" ? taskData.status : "planning",
    taskDir,
    taskTitle: typeof taskData.title === "string" ? taskData.title : null,
  • dogfood copy:
    const currentTask = sessionData.current_task;
    if (typeof currentTask !== "string" || !currentTask)
    return { status: "no_task", taskDir: null, taskTitle: null };
    const taskDir = join(projectRoot, currentTask);
    const taskJsonPath = join(taskDir, "task.json");
    if (!existsSync(taskJsonPath)) return { status: "no_task", taskDir: null, taskTitle: null };
    let taskData: Record<string, unknown>;
    try {
    taskData = JSON.parse(readFileSync(taskJsonPath, "utf-8"));
    } catch {
    return { status: "no_task", taskDir: null, taskTitle: null };
    }
    return {
    status: typeof taskData.status === "string" ? taskData.status : "planning",
    taskDir,
    taskTitle: typeof taskData.title === "string" ? taskData.title : null,
    };

#534 和 PR #534 加固的是 Python CLI 的 task resolver,但没有修改 OMP extension 的这条直接读取路径,因此仍然可以在 OMP 中复现。

环境

  • Trellis: main @ c8e327a
  • OMP: 17.2.12
  • Node.js: v24.10.0
  • macOS: 26.5.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions