{"record":{"id":"7b9599699cf3a426","repo":"paperclipai/paperclip","slug":"systemd-service-values-must-not-contain-line-break","errorCode":null,"errorMessage":"Systemd service values must not contain line breaks","messagePattern":"Systemd service values must not contain line breaks","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cli/src/services/service-manager.ts","lineNumber":57,"sourceCode":"export type CommandResult = { stdout: string; stderr: string };\nexport type CommandRunner = (command: string, args: string[], options?: { inherit?: boolean }) => Promise<CommandResult>;\n\nexport const defaultCommandRunner: CommandRunner = async (command, args, options) => {\n  if (options?.inherit) {\n    await new Promise<void>((resolve, reject) => {\n      const child = execFile(command, args, { windowsHide: true }, (error) => error ? reject(error) : resolve());\n      child.stdout?.pipe(process.stdout);\n      child.stderr?.pipe(process.stderr);\n    });\n    return { stdout: \"\", stderr: \"\" };\n  }\n  const result = await execFileAsync(command, args, { encoding: \"utf8\", windowsHide: true });\n  return { stdout: result.stdout, stderr: result.stderr };\n};\n\nfunction escapeSystemd(value: string): string {\n  if (/\\r|\\n/.test(value)) {\n    throw new Error(\"Systemd service values must not contain line breaks\");\n  }\n  return value\n    .replaceAll(\"\\\\\", \"\\\\\\\\\")\n    .replaceAll('\"', '\\\\\"')\n    .replaceAll(\"$\", () => \"$$\")\n    .replaceAll(\"%\", \"%%\");\n}\n\nfunction escapeXml(value: string): string {\n  return value.replaceAll(\"&\", \"&amp;\").replaceAll(\"<\", \"&lt;\").replaceAll(\">\", \"&gt;\").replaceAll('\"', \"&quot;\").replaceAll(\"'\", \"&apos;\");\n}\n\nfunction escapeRegExp(value: string): string {\n  return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nexport function resolveServiceShimPath(homeDir = os.homedir()): string {\n  return process.env.PAPERCLIP_SHIM_PATH?.trim() || path.join(homeDir, \".local\", \"bin\", \"paperclipai\");","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/cli/src/services/service-manager.ts#L39-L75","documentation":"Thrown by escapeSystemd when a value being interpolated into a systemd unit file contains a carriage return (\\r) or newline (\\n). Systemd unit files are line-oriented; inserting a newline into a value field could inject additional directives (e.g., a value containing '\\nExecStart=malicious-command'). This is a fail-closed injection-prevention guard applied before any escaping occurs.","triggerScenarios":"Calling renderSystemdUnit or any code path that calls escapeSystemd with an instanceId, shimPath, or homeDir that contains a newline or carriage return character. This includes the SystemdServiceManager constructor and its renderDefinition/install/start/restart methods.","commonSituations":"PAPERCLIP_INSTANCE_ID is set to a value containing a newline (e.g., from a misconfigured CI variable or a copy-paste with trailing newline). PAPERCLIP_HOME or PAPERCLIP_SHIM_PATH contains a newline. An attacker or broken script passes a crafted instance ID.","solutions":["Check the environment variables: 'echo \"$PAPERCLIP_INSTANCE_ID\" | cat -v' to reveal hidden characters.","Strip newlines from the instance ID before passing it: ensure the --instance flag value has no line breaks.","If PAPERCLIP_HOME or the shim path contains newlines, fix the environment to use clean single-line paths.","Use a simple alphanumeric instance ID: instance IDs should match the expected identifier format."],"exampleFix":"// before: instance ID contains a trailing newline\n// PAPERCLIP_INSTANCE_ID='default\\n'\n\n// after: clean instance ID\n// PAPERCLIP_INSTANCE_ID='default'\n\n// or sanitize before use:\nconst cleanInstanceId = instanceId.replace(/[\\r\\n]/g, '');","handlingStrategy":"validation","validationCode":"function isSafeSystemdValue(value: string): boolean {\n  return !/\\r|\\n/.test(value);\n}\n\n// Call before renderSystemdUnit / SystemdServiceManager construction:\nconst fields = [instanceId, shimPath, homeDir];\nfor (const field of fields) {\n  if (!isSafeSystemdValue(field)) {\n    throw new Error(`Field contains line breaks and cannot be used in a systemd unit: ${JSON.stringify(field)}`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  const unit = renderSystemdUnit({ instanceId, shimPath, homeDir });\n} catch (error) {\n  if (error instanceof Error && error.message === 'Systemd service values must not contain line breaks') {\n    // Sanitize the offending field\n    const cleanInstanceId = instanceId.replace(/[\\r\\n]/g, '');\n    // Retry with cleaned values\n  }\n  throw error;\n}","preventionTips":["Ensure PAPERCLIP_INSTANCE_ID, PAPERCLIP_HOME, and shim paths are single-line values with no trailing newlines.","In CI/CD pipelines, use 'echo -n' or .strip() when setting environment variables to avoid trailing newlines.","Validate user-supplied instance IDs against a safe charset (e.g., /^[A-Za-z0-9._-]+$/) before using them in service definitions."],"tags":["security","systemd","injection-prevention","service-manager","cli"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}