{"record":{"id":"d5ad9eace5a6427a","repo":"paperclipai/paperclip","slug":"acpx-verified-runtime-descriptor-is-misplaced","errorCode":null,"errorMessage":"ACPX verified runtime descriptor is misplaced","messagePattern":"ACPX verified runtime descriptor is misplaced","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts","lineNumber":152,"sourceCode":"export const PROVIDER_LIFETIME_GUARDIAN_SOURCE = `\nconst fs = require(\"node:fs\");\nconst { spawn } = require(\"node:child_process\");\nconst WATCHDOG_SOURCE = ${JSON.stringify(PROVIDER_LIFETIME_WATCHDOG_SOURCE)};\nconst runtimeExecutable = process.env.${VERIFIED_RUNTIME_EXECUTABLE_ENV} || process.execPath;\nconst dependencyAncestorCount = Number.parseInt(process.argv[4], 10);\nconst providerRuntimeExecutableCount = Number.parseInt(process.argv[8], 10);\nif (!Number.isSafeInteger(dependencyAncestorCount) || dependencyAncestorCount < 0 || dependencyAncestorCount > ${MAX_DEPENDENCY_ANCESTORS}) throw new Error(\"ACPX provider dependency ancestry is invalid\");\nif (providerRuntimeExecutableCount !== 0 && providerRuntimeExecutableCount !== 1) throw new Error(\"ACPX provider runtime executable count is invalid\");\nconst PROVIDER_RUNTIME_EXECUTABLE_FD = ${DEPENDENCY_ANCESTOR_FD_START} + dependencyAncestorCount;\nconst OWNER_FD = PROVIDER_RUNTIME_EXECUTABLE_FD + providerRuntimeExecutableCount;\nconst OWNERSHIP_FD = OWNER_FD + 1;\nconst PROVIDER_EXIT_FD = OWNERSHIP_FD + 1;\nconst CREDENTIAL_FENCE_FD_START = PROVIDER_EXIT_FD + 1;\nconst VERIFIED_RUNTIME_FD = CREDENTIAL_FENCE_FD_START + 2;\nconst dependencyAncestorFds = Array.from({ length: dependencyAncestorCount }, (_, index) => ${DEPENDENCY_ANCESTOR_FD_START} + index);\nconst runtimeDescriptorMatch = /^\\\\/proc\\\\/self\\\\/fd\\\\/([0-9]+)$/.exec(runtimeExecutable);\nconst runtimeDescriptorFd = runtimeDescriptorMatch === null ? null : Number.parseInt(runtimeDescriptorMatch[1], 10);\nif (runtimeDescriptorFd !== null && runtimeDescriptorFd !== VERIFIED_RUNTIME_FD) throw new Error(\"ACPX verified runtime descriptor is misplaced\");\nif (runtimeDescriptorFd !== null) fs.fstatSync(runtimeDescriptorFd);\nlet provider;\nlet watchdog;\nlet reaped = false;\nlet shutdownStarted = false;\nconst reap = () => {\n  if (reaped) return;\n  reaped = true;\n  // This sentinel is the provider group's leader. It remains alive until this\n  // one atomic signal, pinning the numeric group identity against PID reuse.\n  process.kill(-process.pid, \"SIGKILL\");\n};\nconst owner = fs.createReadStream(\"\", { fd: OWNER_FD, autoClose: false });\nowner.once(\"end\", reap);\nowner.once(\"error\", reap);\nowner.resume();\n// Fail before provider code exists unless both inherited quorum fences are live.\nfs.fstatSync(CREDENTIAL_FENCE_FD_START);","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts#L134-L170","documentation":"When spawning the ACPX verified runtime, the code expects the runtime executable to arrive as the /proc/self/fd path pointing exactly at the reserved VERIFIED_RUNTIME_FD descriptor (CREDENTIAL_FENCE_FD_START + 2). If the parsed fd number differs from the reserved one, the fd wiring between caller and spawner is broken, so the integrity check fails closed rather than spawning an unverified binary. This protects against the runtime being executed from an unexpected or unvetted descriptor.","triggerScenarios":"Calling the spawn path with a runtimeExecutable that matches /proc/self/fd/<n> where n is not VERIFIED_RUNTIME_FD — i.e. the fd was passed on a different descriptor number than the reserved slot.","commonSituations":"fd reservation constants (OWNERSHIP_FD, PROVIDER_EXIT_FD, CREDENTIAL_FENCE_FD_START) changed in one place but the spawning code or dependency-ancestor fd count was not updated, so the verified runtime lands on a shifted fd; double-spawning two children that both consume fence fds; an intermediate wrapper re-executing and renumbering fds.","solutions":["Compare the reserved fd constants (OWNERSHIP_FD, PROVIDER_EXIT_FD, CREDENTIAL_FENCE_FD_START, VERIFIED_RUNTIME_FD) against the fd number actually passed as runtimeExecutable; re-align the caller so the verified runtime is dup'd onto VERIFIED_RUNTIME_FD","Check dependencyAncestorCount: any change to the number of ancestor fds shifts the fence layout; update CREDENTIAL_FENCE_FD_START derivation accordingly","Log /proc/self/fd listings at spawn time to see which fd the runtime actually arrived on and fix the dup2/fd inheritance in the caller","Remove duplicate or stale fd holders (e.g. leftover watchdogs) that occupy the reserved fd before the runtime is passed"],"exampleFix":"// before\nconst fd = parseFd(runtimeExecutable); // e.g. 5, but VERIFIED_RUNTIME_FD is 6\nspawn(fd, opts);\n// after\nimport { VERIFIED_RUNTIME_FD } from './installation-integrity';\ndup2(runtimeFd, VERIFIED_RUNTIME_FD);\nspawn(`/proc/self/fd/${VERIFIED_RUNTIME_FD}`, opts);","handlingStrategy":"validation","validationCode":"import { VERIFIED_RUNTIME_FD } from './installation-integrity';\nimport * as fs from 'node:fs';\nconst m = /^\\/proc\\/self\\/fd\\/([0-9]+)$/.exec(runtimeExecutable);\nif (m && Number(m[1]) !== VERIFIED_RUNTIME_FD) {\n  throw new Error(`runtime fd ${m[1]} != reserved VERIFIED_RUNTIME_FD ${VERIFIED_RUNTIME_FD}`);\n}","typeGuard":"function isVerifiedRuntimeFd(executable: string): boolean {\n  const m = /^\\/proc\\/self\\/fd\\/([0-9]+)$/.exec(executable);\n  return m === null || Number(m[1]) === VERIFIED_RUNTIME_FD;\n}","tryCatchPattern":"try {\n  spawnAcpxRuntime(runtimeExecutable, opts);\n} catch (err) {\n  if (err instanceof Error && err.message === 'ACPX verified runtime descriptor is misplaced') {\n    logger.error({ fds: fs.readdirSync('/proc/self/fd') }, 'fd fence misaligned');\n    throw new Error('ACPX fd wiring broken: re-align VERIFIED_RUNTIME_FD in the caller');\n  }\n  throw err;\n}","preventionTips":["Centralize the fd constants (OWNERSHIP_FD, CREDENTIAL_FENCE_FD_START, VERIFIED_RUNTIME_FD) in one exported module and derive all users from it","Add a unit test that spawns with a deliberately wrong fd and asserts the guard fires","Log the full /proc/self/fd listing before spawn in debug mode to catch fence shifts early","Never dup2 or open new files into the reserved fd range in unrelated code paths"],"tags":["acpx","fd-management","process-spawn","invariant"],"backgroundTag":"internal-invariant-violation","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"}