{"record":{"id":"2db70d9ca24bb840","repo":"paperclipai/paperclip","slug":"materialized-opencode-executable-has-unsafe-permis","errorCode":null,"errorMessage":"Materialized OpenCode executable has unsafe permissions","messagePattern":"Materialized OpenCode executable has unsafe permissions","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/scripts/materialize-opencode-binary.mjs","lineNumber":87,"sourceCode":"    linkSync(source, target);\n  } catch (error) {\n    const code = error?.code;\n    if (!new Set([\"EACCES\", \"EMLINK\", \"EPERM\", \"EXDEV\"]).has(code)) {\n      throw error;\n    }\n    copyFileSync(source, target);\n  }\n  chmodSync(target, 0o755);\n\n  const sourceDigest = sha256(source);\n  const targetDigest = sha256(target);\n  if (sourceDigest !== targetDigest) {\n    throw new Error(\"Materialized OpenCode executable digest mismatch\");\n  }\n  const targetStat = lstatSync(target);\n  const mode = targetStat.mode & 0o777;\n  if (!targetStat.isFile() || (mode & 0o111) === 0 || mode & 0o022) {\n    throw new Error(\"Materialized OpenCode executable has unsafe permissions\");\n  }\n\n  const version = spawnSync(target, [\"--version\"], {\n    encoding: \"utf8\",\n    timeout: 30_000,\n    windowsHide: true,\n  });\n  if (version.status !== 0 || version.stdout.trim() !== OPENCODE_VERSION) {\n    throw new Error(\n      `Materialized OpenCode executable did not report ${OPENCODE_VERSION}`,\n    );\n  }\n  return { sourceDigest, target, version: OPENCODE_VERSION };\n}\n\nconst invokedPath = process.argv[1]\n  ? pathToFileURL(realpathSync(process.argv[1])).href\n  : null;","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/scripts/materialize-opencode-binary.mjs#L69-L105","documentation":"After verifying the digest, the script checks the target's permission bits: it must be a regular file, executable (any of 0o111 set), and not group/world writable (no 0o022 bits). Anything else is a tampering/correctness risk and throws.","triggerScenarios":"The target after chmodSync(0o755) still has an unsafe mode — e.g. the filesystem ignored the chmod (some mounts), a umask/ACL forced group-write bits, or the target was replaced by a non-file between link and stat (TOCTOU).","commonSituations":"Building on a mounted volume that does not honor chmod; an over-permissive umask; ACLs or sync tools adding write bits to node_modules; another process swapping the binary.","solutions":["Confirm the filesystem honors chmod ('chmod 755 bin/opencode.exe && stat -c %a bin/opencode.exe'); build on a local ext4/overlayfs volume if not.","Remove the target and re-run the materialization to re-apply 0o755 cleanly.","Check for tools modifying permissions on node_modules (ACLs, sync tools) and exclude the directory.","If the file is no longer a regular file, treat it as tampering: delete it, reinstall dependencies, re-run."],"exampleFix":"# before\n$ stat -c %a bin/opencode.exe  # e.g. 775 (group-writable)\n# after\n$ chmod 755 bin/opencode.exe\n$ node scripts/materialize-opencode-binary.mjs","handlingStrategy":"validation","validationCode":"import { lstatSync, chmodSync } from 'node:fs';\nconst st = lstatSync(target);\nconst mode = st.mode & 0o777;\nif (!st.isFile() || (mode & 0o111) === 0 || (mode & 0o022) !== 0) {\n  chmodSync(target, 0o755);\n}","typeGuard":null,"tryCatchPattern":"try {\n  materializePinnedOpenCodeBinary();\n} catch (err) {\n  if (err.message === 'Materialized OpenCode executable has unsafe permissions') {\n    chmodSync(target, 0o755);\n    materializePinnedOpenCodeBinary();\n  } else throw err;\n}","preventionTips":["Use a sane umask (e.g. 022) in build scripts and CI.","Build on filesystems that honor chmod (local disk, standard overlayfs).","Check ACLs/sync tools that may add group-write bits to node_modules.","Re-apply 0o755 after any post-link tooling touches the binary."],"tags":["security","permissions","filesystem"],"backgroundTag":"permission-denied","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"}