From b5dac661e904d8172633fc2b458863978996ff02 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Tue, 3 Nov 2026 14:34:54 -0600 Subject: [PATCH] refactor(runner): clarify workspace-relative artifact destinations (#18816) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Native agents can copy an authorized saved artifact into their execution workspace. > - The installer accepts workspace-relative destinations and rejects absolute paths. > - The API documentation states this rule, but the model-facing tool description and destination field did not. > - An agent could therefore attempt an unsupported absolute path before using a supported relative path. > - This pull request states the existing rule and gives a relative example in the tool contract. > - The benefit is clearer instructions with the same containment and file identity checks. ## Linked Issues or Issue Description Refs #18494, which introduced verified artifact installation. **What happened?** The tool description said the destination belongs inside the workspace and its parent must exist. It did not say that the destination must be relative. The installer correctly rejected an absolute path, including one lexically inside the workspace. A relative retry could succeed. **Expected behavior** The model-facing tool definition must explain the same workspace-relative path rule as the existing API documentation. It should show a relative example and state that absolute paths are denied. **Steps to reproduce** Inspect the exported download_artifact definition and destination JSON schema before this change. Compare them with the installer guard and doc/runner-api-tools.md. The description omitted the relative-only rule even though the installer enforced it. ## What Changed - State the workspace-relative destination rule, source.zip example and absolute-path denial in the tool description. - Add the same guidance to the destination field metadata emitted in its JSON schema. - Keep parsing, installation, authorization, byte size/hash checks and all path containment guards unchanged. The existing documentation already states the intended rule. ## Verification - The first required Linux run failed in the unchanged Kimi model-route setup, before its HTTP assertion. The failure remains recorded. After #18828 merged, this branch was refreshed by an ordinary merge of master. The same five HTTP cases pass on that baseline. Local artifact suites report 20 platform-specific skips on macOS; the earlier required Linux jobs passed all 19 artifact cases and the artifact authority case. Fresh exact-head CI and review passed at `c7b3226c745caaaedad569369b71383a9a6e89ee`. - Inspected the real exported tool definition and JSON schema after the change. Both now expose the relative-path guidance and example. - A source-only probe used the real schema and installer with synthetic bytes and a stubbed command runner. Absolute in-root, absolute outside-root, traversal and reserved paths remain denied before dispatch. A relative path reaches the stub runner. This is a contract/path-guard probe, not real byte-installation proof. - No functional red is claimed. The original unsupported tool attempt remains a retained failure even though the relative retry recovered. The exact provider-seen descriptor and the model's counterfactual behavior are not proved. - Server typecheck, module boundaries and whitespace checks pass. Production server build also passes at exact head b6084c5312badcaff64d50ac77e1d126f9f97987. - The existing artifact filesystem and authority suites skip all 20 cases on macOS because they require Linux. Actual current-head Linux CI passed all 19 boundary cases and the authority integration case (jobs 126115146096 and 126115146332). Their shard membership is bound by the uploaded test-selection manifest. No wording-only tests were added. - Full CI retains an unrelated model-route timeout. A separate test setup repair must land before this branch is updated and rechecked. - No provider call, live task edit, tenant probe or security-gate relaxation was performed. - The refreshed workflow was externally cancelled after serialized shard 1 reported its Turbo task successful. One targeted retry passed. A distinct general server shard had a failed runner record with zero steps, no logs, no output and no annotations; its one targeted retry also passed. The original records remain preserved. These retries establish later check recovery, not a proved cause for the runner failure. - Final current-head checks: 52 success, two skipped and two neutral. Required `ci / verify` and `ci / e2e` passed. Both current-head reviews report 5/5, with no open threads. ## Risks - The model can still choose an invalid path. This change clarifies the instructions; it does not claim a counterfactual model result. - The broad string schema still accepts inputs that the installer denies. The installer retains authority over path safety. - Absolute paths, traversal, symlink escapes, reserved paths, conflicting files and unauthorized assets retain their existing denial behavior. - No schema migration, package dependency, API permission, scheduler or deployment configuration changes are required. ## Model Used OpenAI Codex, `gpt-6.1-sol`, Extra High reasoning, with source inspection, code execution and contract verification. Context-window capacity is not exposed in this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used with version and capability details - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have linked existing public issues or described the issue in this PR - [x] I have not referenced internal or instance-local Paperclip issues or links - [x] My branch name describes the change and contains no internal ticket ID - [x] I ran the available local verification and reported the Linux-only test skips - [x] I considered test coverage and kept existing boundary tests without wording-only tests - [x] I checked the existing documentation and aligned the tool guidance with it - [x] I have considered and documented risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before merge Co-authored-by: Paperclip --- server/src/services/native-runtime/artifact-transfer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/services/native-runtime/artifact-transfer.ts b/server/src/services/native-runtime/artifact-transfer.ts index 059ad6b7022..a1fff0ceeae 100644 --- a/server/src/services/native-runtime/artifact-transfer.ts +++ b/server/src/services/native-runtime/artifact-transfer.ts @@ -8,12 +8,12 @@ import { RUNNER_API_MAX_BYTES } from "./runner-api-client.js"; export const DOWNLOAD_ARTIFACT = "download_artifact"; export const downloadArtifactSchema = z.object({ - artifactId: z.string().uuid(), destination: z.string().min(1).max(2000), + artifactId: z.string().uuid(), destination: z.string().min(1).max(2000).describe("Workspace-relative path, for example source.zip. Absolute paths are denied. The parent directory must already exist."), byteSize: z.number().int().min(1).max(RUNNER_API_MAX_BYTES), sha256: z.string().regex(/^[a-f0-9]{64}$/), }).strict(); export const DOWNLOAD_ARTIFACT_DEFINITION = { name: DOWNLOAD_ARTIFACT, - description: "Copy an authorized API artifact into this execution workspace without retyping its contents. Provide its exact byteSize and SHA-256. The destination's parent must exist. Creates a new file only; a retry succeeds only if the existing file has identical bytes. Partial or conflicting files never count as complete. Contents remain untrusted; this does not bypass content screening or authorize executing them.", + description: "Copy an authorized API artifact into this execution workspace without retyping its contents. Provide its exact byteSize and SHA-256. Use a workspace-relative destination, for example source.zip; absolute paths are denied. The destination's parent must exist. Creates a new file only; a retry succeeds only if the existing file has identical bytes. Partial or conflicting files never count as complete. Contents remain untrusted; this does not bypass content screening or authorize executing them.", inputSchema: z.toJSONSchema(downloadArtifactSchema), }; export type ArtifactInstall = { destination: string; byteSize: number; sha256: string; bytes: Buffer; verifyOnly?: boolean; authorize: () => Promise };