arisa 5.1.8 → 5.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arisa",
3
- "version": "5.1.8",
3
+ "version": "5.1.9",
4
4
  "description": "Telegram + Pi Agent modular assistant",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -65,7 +65,21 @@ export async function selectSlaveServiceAccount({
65
65
  }
66
66
 
67
67
  function quoteSystemd(value) {
68
- return `"${String(value).replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
68
+ return `"${String(value).replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%")}"`;
69
+ }
70
+
71
+ function escapeSystemdPath(value) {
72
+ const text = String(value);
73
+ if (text.includes("\0")) throw new Error("Systemd paths cannot contain NUL bytes");
74
+ return [...text].map((character) => {
75
+ if (character === "\\") return "\\\\";
76
+ if (character === "%") return "%%";
77
+ const codePoint = character.codePointAt(0);
78
+ if (codePoint <= 0x20 || codePoint === 0x7f || character === '"' || character === "'") {
79
+ return `\\x${codePoint.toString(16).padStart(2, "0")}`;
80
+ }
81
+ return character;
82
+ }).join("");
69
83
  }
70
84
 
71
85
  export function buildSlaveSystemdUnit({ account, slaveHome, entryFile, nodePath = process.execPath }) {
@@ -84,10 +98,10 @@ export function buildSlaveSystemdUnit({ account, slaveHome, entryFile, nodePath
84
98
  userDirective.trimEnd(),
85
99
  `Environment=${quoteSystemd(`ARISA_HOME=${paths.home}`)}`,
86
100
  `Environment=${quoteSystemd(`ARISA_SLAVE_HOME=${paths.home}`)}`,
87
- `WorkingDirectory=${quoteSystemd(paths.home)}`,
101
+ `WorkingDirectory=${escapeSystemdPath(paths.home)}`,
88
102
  `ExecStart=${quoteSystemd(nodePath)} ${quoteSystemd(entryFile)} slave --service-runner`,
89
- `StandardOutput=append:${paths.logFile}`,
90
- `StandardError=append:${paths.logFile}`,
103
+ `StandardOutput=append:${escapeSystemdPath(paths.logFile)}`,
104
+ `StandardError=append:${escapeSystemdPath(paths.logFile)}`,
91
105
  "Restart=on-failure",
92
106
  "RestartSec=2",
93
107
  "",
@@ -82,11 +82,23 @@ test("builds a dedicated headless systemd service with isolated state", () => {
82
82
  });
83
83
  assert.match(unit, /User=arisa-slave/);
84
84
  assert.match(unit, /Environment="ARISA_HOME=\/var\/lib\/arisa-slave"/);
85
+ assert.match(unit, /^WorkingDirectory=\/var\/lib\/arisa-slave$/m);
85
86
  assert.match(unit, /ExecStart="\/usr\/bin\/node" "\/opt\/arisa\/src\/index\.js" slave --service-runner/);
86
87
  assert.match(unit, /StandardOutput=append:\/var\/lib\/arisa-slave\/state\/arisa-slave\.log/);
87
88
  assert.doesNotMatch(unit, /Telegram|Pi Agent/);
88
89
  });
89
90
 
91
+ test("escapes systemd WorkingDirectory paths without quoting the entire value", () => {
92
+ const unit = buildSlaveSystemdUnit({
93
+ account: { scope: "user", user: "tester" },
94
+ slaveHome: "/srv/arisa slave",
95
+ entryFile: "/opt/arisa/src/index.js",
96
+ nodePath: "/usr/bin/node"
97
+ });
98
+ assert.match(unit, /^WorkingDirectory=\/srv\/arisa\\x20slave$/m);
99
+ assert.match(unit, /^StandardOutput=append:\/srv\/arisa\\x20slave\/state\/arisa-slave\.log$/m);
100
+ });
101
+
90
102
  test("refuses to replace the PID of an active Slave host", async (t) => {
91
103
  const home = await mkdtemp(path.join(os.tmpdir(), "arisa-slave-pid-"));
92
104
  t.after(() => rm(home, { recursive: true, force: true }));