(params: {
taskId: string;
artifactSessionDir: string;
projectArtifact: SubagentGitProjectPatchArtifact;
artifactLookupNote?: string;
})
| 590 | } |
| 591 | |
| 592 | async function resolvePatchPath(params: { |
| 593 | taskId: string; |
| 594 | artifactSessionDir: string; |
| 595 | projectArtifact: SubagentGitProjectPatchArtifact; |
| 596 | artifactLookupNote?: string; |
| 597 | }): Promise<{ patchPath: string; note?: string } | { error: string; note?: string }> { |
| 598 | const expectedPatchPath = getSubagentGitPatchMboxPath( |
| 599 | params.artifactSessionDir, |
| 600 | params.taskId, |
| 601 | params.projectArtifact.storageKey |
| 602 | ); |
| 603 | |
| 604 | if (!isPathInsideDir(params.artifactSessionDir, expectedPatchPath)) { |
| 605 | return { |
| 606 | error: "Invalid task_id.", |
| 607 | note: "task_id must not contain path traversal segments.", |
| 608 | }; |
| 609 | } |
| 610 | |
| 611 | const safeMboxPath = |
| 612 | typeof params.projectArtifact.mboxPath === "string" && |
| 613 | params.projectArtifact.mboxPath.length > 0 |
| 614 | ? isPathInsideDir(params.artifactSessionDir, params.projectArtifact.mboxPath) |
| 615 | ? params.projectArtifact.mboxPath |
| 616 | : undefined |
| 617 | : undefined; |
| 618 | |
| 619 | let patchPathNote = mergeNotes( |
| 620 | params.artifactLookupNote, |
| 621 | params.projectArtifact.mboxPath && !safeMboxPath |
| 622 | ? "Ignoring unsafe mboxPath in patch artifact metadata; using canonical patch location." |
| 623 | : undefined |
| 624 | ); |
| 625 | |
| 626 | const patchCandidates = [safeMboxPath, expectedPatchPath].filter( |
| 627 | (candidate): candidate is string => typeof candidate === "string" |
| 628 | ); |
| 629 | |
| 630 | let patchPath: string | null = null; |
| 631 | for (const candidate of patchCandidates) { |
| 632 | try { |
| 633 | const stat = await fsPromises.stat(candidate); |
| 634 | if (stat.isFile()) { |
| 635 | patchPath = candidate; |
| 636 | break; |
| 637 | } |
| 638 | } catch { |
| 639 | // try next candidate |
| 640 | } |
| 641 | } |
| 642 | |
| 643 | if (!patchPath) { |
| 644 | const checkedPaths = Array.from(new Set(patchCandidates)) |
| 645 | .map((candidate) => |
| 646 | isPathInsideDir(params.artifactSessionDir, candidate) |
| 647 | ? path.relative(params.artifactSessionDir, candidate) || path.basename(candidate) |
| 648 | : candidate |
| 649 | ) |
no test coverage detected