@xshieldai/n8n-nodes-kavachos 1.5.1 → 1.5.2

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/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # @xshieldai/n8n-nodes-kavachos
2
2
 
3
- > **🔍 Verification status (2026-10-12 IST — v1.5.1)**
4
- > - **Tests:** ⚠️ none inside the package. In the repository, [red-team/n8n-kavach-run.battery.sh](https://github.com/rocketlang/aegis/blob/master/red-team/n8n-kavach-run.battery.sh) drives the Run and Gate nodes outside n8n against an installed copy (32 cases, with a stand-in `kavachos` and a stand-in gate). **Not yet run inside an n8n instance.**
3
+ > **🔍 Verification status (2026-10-12 IST — v1.5.2)**
4
+ > - **Tests:** ⚠️ none inside the package. In the repository: [red-team/n8n-kavach-run.battery.sh](https://github.com/rocketlang/aegis/blob/master/red-team/n8n-kavach-run.battery.sh) drives the Run and Gate nodes outside n8n against an installed copy (35 cases), and [red-team/n8n-inside-n8n.battery.sh](https://github.com/rocketlang/aegis/blob/master/red-team/n8n-inside-n8n.battery.sh) installs the package through n8n's own installer and runs all four nodes in workflows inside n8n 2.42.7 (23 cases; first run on 1.5.1, 12 October 2026). Gate, Budget and Audit run against a real Aegis there. **Kavach Run is tested with a stand-in `kavachos` in both: neither battery launches anything under kernel enforcement.**
5
5
  > - **Examples:** ✅ 1 importable workflow, in the repository (not in the npm package): [examples/n8n-governed-agent.json](https://github.com/rocketlang/aegis/blob/master/packages/n8n-nodes-kavachos/examples/n8n-governed-agent.json). Drag into n8n, wire the Aegis API credential pointing at `http://localhost:4850`, run.
6
6
  > - **Live demo:** ⚠️ planned (Tier 3)
7
7
  > - **Phase-1 limits:** KavachRun is Linux-only; where enforcement is unavailable (non-Linux, or `kavachos` not installed) it **fails closed by default** (`On Non-Linux` = `throw`; `warn`/`skip` are opt-in). KavachGate works on any OS — it's a thin HTTP relay to AEGIS.
8
8
  >
9
- > The KavachGate node's behaviour is end-to-end testable against a live AEGIS instance — start AEGIS, import the example workflow, fire a `rm -rf /` command, watch it block. Fixtures that run inside n8n are still to come.
9
+ > The KavachGate node's behaviour is end-to-end testable against a live AEGIS instance — start AEGIS, import the example workflow, fire a `rm -rf /` command, watch it block.
10
10
 
11
11
  > **Renamed at 1.5.0.** This package was `@xshieldai/n8n-nodes` through 1.4.1. n8n's community-nodes screen
12
12
  > only installs a package whose name starts with `n8n-nodes-` after the scope, and refused the old name. The
@@ -41,6 +41,9 @@ Wraps a subprocess in kavachos **kernel enforcement** (seccomp-bpf + cgroup BPF
41
41
  `kavachos run <options> -- <binary> <arguments>`, so the binary's arguments are passed on word for word and
42
42
  cannot be read as kavachos options. With an older `kavachos` the node refuses to run. See the changelog.
43
43
 
44
+ Since 1.5.2 an argument in quotes is one word: `--name 'two words'`. Nothing else in the **Arguments** field is
45
+ read specially (it is not a shell), and a quote that is never closed stops the node before anything runs.
46
+
44
47
  Since 1.4.0 the node waits for the command without stopping n8n, stops it at **Time Limit (seconds)**, and
45
48
  reports what `kavachos` itself said: a refused launch is an error, and `enforced` is true only when no layer
46
49
  was reported missing (`egress_constrained`, `path_jail_loaded`). It reads kavachos's own lines for this, so
@@ -67,7 +67,7 @@ class KavachRun {
67
67
  type: "string",
68
68
  default: "",
69
69
  placeholder: "src/my-agent.ts --port 4900",
70
- description: "Space-separated arguments to pass to the binary.",
70
+ description: "Arguments for the binary, separated by spaces. Put quotes around an argument that holds a space: --name 'two words'. Nothing else is read specially (this is not a shell).",
71
71
  },
72
72
  {
73
73
  displayName: "Trust Mask (hex)",
@@ -128,6 +128,10 @@ class KavachRun {
128
128
  for (let i = 0; i < items.length; i++) {
129
129
  const binary = this.getNodeParameter("binary", i);
130
130
  const args = this.getNodeParameter("args", i).trim();
131
+ const split = (0, shared_1.splitWords)(args);
132
+ if (split.error)
133
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `${split.error}. Nothing was run.`, { itemIndex: i });
134
+ const argWords = split.words;
131
135
  const trustMask = this.getNodeParameter("trustMask", i);
132
136
  const domain = this.getNodeParameter("domain", i);
133
137
  const falco = this.getNodeParameter("falco", i);
@@ -156,8 +160,7 @@ class KavachRun {
156
160
  }
157
161
  // warn: run unwrapped with a flag in output
158
162
  process.stderr.write(`[KavachRun] WARNING: ${reason} — running ${binary} unwrapped\n`);
159
- const argList = args ? args.split(/\s+/) : [];
160
- const proc = await (0, shared_1.runProcess)(binary, argList, timeoutMs);
163
+ const proc = await (0, shared_1.runProcess)(binary, argWords, timeoutMs);
161
164
  if (proc.error) {
162
165
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `${binary} could not be started: ${proc.error}`, { itemIndex: i });
163
166
  }
@@ -198,7 +201,7 @@ class KavachRun {
198
201
  ...(falco ? ["--falco"] : []),
199
202
  "--",
200
203
  binary,
201
- ...(args ? args.split(/\s+/) : []),
204
+ ...argWords,
202
205
  ];
203
206
  const proc = await (0, shared_1.runProcess)("kavachos", kavachArgs, timeoutMs);
204
207
  if (proc.error) {
@@ -26,3 +26,7 @@ export interface JsonReply {
26
26
  * is shorter than the gate's own wait for a level-1 approval.)
27
27
  */
28
28
  export declare function requestJson(method: "GET" | "POST", url: string, headers: Record<string, string>, body: string | undefined, timeoutMs: number): Promise<JsonReply>;
29
+ export declare function splitWords(text: string): {
30
+ words: string[];
31
+ error?: string;
32
+ };
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.runProcess = runProcess;
37
37
  exports.requestJson = requestJson;
38
+ exports.splitWords = splitWords;
38
39
  // SPDX-License-Identifier: AGPL-3.0-only
39
40
  // Shared by the Kavach nodes: running a command without stopping n8n, and asking Aegis with a time limit.
40
41
  const child_process_1 = require("child_process");
@@ -137,3 +138,51 @@ function requestJson(method, url, headers, body, timeoutMs) {
137
138
  req.end();
138
139
  });
139
140
  }
141
+ // The Arguments field as a list of words. Through 1.5.1 the field was cut at every space, so an argument that
142
+ // holds a space could not be passed. Now quotes group: '...' is taken as it stands, and in "..." a backslash
143
+ // before a double quote or another backslash gives that character. Everywhere else every character is itself:
144
+ // this is not a shell, so $, *, ~ and a backslash outside double quotes are not read as anything.
145
+ // A quote that is never closed is an error and not a guess.
146
+ function splitWords(text) {
147
+ const words = [];
148
+ let word = "", inWord = false, quote = null;
149
+ for (let i = 0; i < text.length; i++) {
150
+ const c = text[i];
151
+ if (quote === "'") {
152
+ if (c === "'")
153
+ quote = null;
154
+ else
155
+ word += c;
156
+ continue;
157
+ }
158
+ if (quote === '"') {
159
+ if (c === '"')
160
+ quote = null;
161
+ else if (c === "\\" && (text[i + 1] === '"' || text[i + 1] === "\\"))
162
+ word += text[++i];
163
+ else
164
+ word += c;
165
+ continue;
166
+ }
167
+ if (c === "'" || c === '"') {
168
+ quote = c;
169
+ inWord = true;
170
+ continue;
171
+ }
172
+ if (/\s/.test(c)) {
173
+ if (inWord) {
174
+ words.push(word);
175
+ word = "";
176
+ inWord = false;
177
+ }
178
+ continue;
179
+ }
180
+ word += c;
181
+ inWord = true;
182
+ }
183
+ if (quote)
184
+ return { words: [], error: `the arguments have a ${quote === "'" ? "single" : "double"} quote that is never closed` };
185
+ if (inWord)
186
+ words.push(word);
187
+ return { words };
188
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xshieldai/n8n-nodes-kavachos",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "xShieldAI n8n community nodes — DAN gate, kernel enforcement, budget check, and audit log for AI agents.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "author": "Capt. Anil Sharma <capt.anil.sharma@powerpbox.org>",