把用户传入的路径解析为绝对路径。支持相对项目根的路径,缺 .md 时自动补。 安全约束: - 解析后的路径必须落在 PROJECT_ROOT 内——拒绝绝对路径以及含 ../ 回到 PROJECT_ROOT 之外的相对路径,防止 web 端 ?path= 触发的路径遍历。 - 拒绝 my_thoughts/ 下的目标——人类专属区不可被读路径(outline / blocks / read-section / read-block 等)暴露内容(CLAUDE.md:my_thoughts 只读且不暴露)。 允许
(arg: str)
| 1474 | |
| 1475 | |
| 1476 | def resolve_doc_path(arg: str) -> Path: |
| 1477 | """把用户传入的路径解析为绝对路径。支持相对项目根的路径,缺 .md 时自动补。 |
| 1478 | |
| 1479 | 安全约束: |
| 1480 | - 解析后的路径必须落在 PROJECT_ROOT 内——拒绝绝对路径以及含 ../ |
| 1481 | 回到 PROJECT_ROOT 之外的相对路径,防止 web 端 ?path= 触发的路径遍历。 |
| 1482 | - 拒绝 my_thoughts/ 下的目标——人类专属区不可被读路径(outline / blocks / |
| 1483 | read-section / read-block 等)暴露内容(CLAUDE.md:my_thoughts 只读且不暴露)。 |
| 1484 | 允许 wiki/ 与 raw/。 |
| 1485 | """ |
| 1486 | if Path(arg).is_absolute(): |
| 1487 | raise ValueError(f"路径越界(拒绝绝对路径):{arg}") |
| 1488 | arg = strip_workspace_prefix(arg) |
| 1489 | p = (PROJECT_ROOT / arg).resolve() |
| 1490 | try: |
| 1491 | rel = p.relative_to(PROJECT_ROOT.resolve()) |
| 1492 | except ValueError: |
| 1493 | raise ValueError(f"路径越界(必须在 PROJECT_ROOT 内):{arg}") |
| 1494 | # my_thoughts 人类专属区:不可读不暴露(即便 ../ 绕进来也挡) |
| 1495 | if rel.parts and rel.parts[0] == "my_thoughts": |
| 1496 | raise ValueError(f"human-only 区不可读(my_thoughts 人类专属):{arg}") |
| 1497 | if not p.exists() and p.suffix == "": |
| 1498 | candidate = p.with_suffix(".md") |
| 1499 | if candidate.exists(): |
| 1500 | return candidate |
| 1501 | return p |
| 1502 | |
| 1503 | |
| 1504 | def load_or_build_outline(md_path: Path) -> dict: |
no test coverage detected