{"record":{"id":"9f142321f871c115","repo":"paperclipai/paperclip","slug":"acpx-input-profile-agent-runtime-executable-esc","errorCode":null,"errorMessage":"ACPX ${input.profile.agent} runtime executable escapes its package","messagePattern":"ACPX (.+?) runtime executable escapes its package","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts","lineNumber":896,"sourceCode":"    ),\n  );\n  const executablePackage = await readPackageJson(\n    executablePackageJsonPath,\n    qualification.packageName,\n  );\n  if (executablePackage.version !== qualification.packageVersion) {\n    throw new Error(\n      `ACPX ${input.profile.agent} runtime executable package version mismatch: expected ${qualification.packageVersion}, received ${executablePackage.version ?? \"unknown\"}`,\n    );\n  }\n\n  const packageDirectory = dirname(executablePackageJsonPath);\n  const unresolvedExecutablePath = resolve(\n    packageDirectory,\n    qualification.relativeExecutable,\n  );\n  if (!isInside(packageDirectory, unresolvedExecutablePath)) {\n    throw new Error(\n      `ACPX ${input.profile.agent} runtime executable escapes its package`,\n    );\n  }\n  const executableDirectory = await realpath(dirname(unresolvedExecutablePath));\n  if (!isInsideOrEqual(packageDirectory, executableDirectory)) {\n    throw new Error(\n      `ACPX ${input.profile.agent} runtime executable escapes its package`,\n    );\n  }\n  const executablePath = resolve(\n    executableDirectory,\n    basename(unresolvedExecutablePath),\n  );\n  const verified = await openVerifiedRuntimeExecutable(\n    executablePath,\n    qualification.executableDigest,\n    input.profile.agent,\n  );","sourceCodeStart":878,"sourceCodeEnd":914,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts#L878-L914","documentation":"Before touching the filesystem for real, the verifier resolves qualification.relativeExecutable against the platform package directory and asserts the unresolved path stays inside that package (path-traversal guard on the literal path). If the qualified relativeExecutable value resolves outside the package directory, the qualification data or metadata is inconsistent/unsafe and verification stops.","triggerScenarios":"The qualification record's relativeExecutable contains traversal segments (../) or an absolute path such that resolve(packageDirectory, relativeExecutable) escapes packageDirectory; in practice this fires only with tampered qualification tables, a monkey-patched profile, or an incorrect custom QUALIFIED_* runtime entry.","commonSituations":"A developer adding a new qualified runtime entry who mistypes relativeExecutable (leading '/' or '../bin/...'); an attacker-modified build where pinned path constants were altered; symlink-free literal traversal introduced by a bad code patch.","solutions":["Inspect the QUALIFIED_*_RUNTIME entry for the agent and fix relativeExecutable to a package-relative path like './bin/<name>' with no '../' or absolute prefix.","Ensure the executable actually exists at that relative location inside the platform package's published layout.","If a custom qualification was injected at runtime, remove the override and use the built-in qualified constants.","Reinstall the affected package to rule out a modified local copy, then re-run verification.","If intentionally relocating binaries, update both the qualification record and this containment check in the upstream repo together, keeping the path package-relative."],"exampleFix":"// before (qualification entry)\nrelativeExecutable: \"../shared/bin/acpx\"\n// after\nrelativeExecutable: \"./bin/acpx\"","handlingStrategy":"validation","validationCode":"import { isInside } from \"@paperclip/...\"; // same helper family as the verifier\nconst rel = qualification.relativeExecutable;\nconst resolved = path.resolve(packageDir, rel);\nif (!isInside(packageDir, resolved)) {\n  throw new Error(`relativeExecutable '${rel}' escapes the package; use a package-relative path like './bin/acpx'`);\n}","typeGuard":"function isPackageRelativeExecutable(packageDir: string, rel: string): boolean {\n  const resolved = path.resolve(packageDir, rel);\n  return resolved.startsWith(packageDir + path.sep);\n}","tryCatchPattern":null,"preventionTips":["Author qualified runtime entries with plain package-relative paths ('./bin/<name>'); never use '../' or absolute paths.","Code-review any change to QUALIFIED_*_RUNTIME constants with the containment check in mind.","Do not patch or monkey-patch qualification tables at runtime."],"tags":["path-traversal","security","package-integrity","acpx"],"backgroundTag":"path-traversal-blocked","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}