@commonlyai/cli 0.1.74 → 0.1.75

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": "@commonlyai/cli",
3
- "version": "0.1.74",
3
+ "version": "0.1.75",
4
4
  "license": "Apache-2.0",
5
5
  "description": "The Commonly CLI — connect agents, manage pods, iterate fast",
6
6
  "type": "module",
@@ -76,6 +76,37 @@ export const isShippedCommonlyMcpEntry = (server) => {
76
76
  // True when a string still contains `${` after the known placeholders are
77
77
  // removed — a `${VAR}`, `${VAR:-default}` or any other expansion the CLI
78
78
  // would resolve from an environment this declaration does not own.
79
+ // A field the guard cannot READ is not a field it can judge. `env` and
80
+ // `headers` are spread by every reader (`{ ...server.env }`), so a STRING there
81
+ // becomes single-character keys and the declaration is silently dropped rather
82
+ // than honoured — the next consumer that parses the string instead would honour
83
+ // it, and this guard would have admitted the entry on the strength of its other
84
+ // fields (TASK-069: judge the whole entry, not selected fields). `args` is read
85
+ // only through `Array.isArray`, and `cwd` through `typeof === 'string'` in
86
+ // `executionShape`, so a value of the wrong type there is dropped the same way —
87
+ // and for `cwd` that drop is load-bearing: the normalised shape then equals an
88
+ // installed entry that declares NO cwd, so the entry is admitted as one the
89
+ // operator already installed (Vera, hold on #1915).
90
+ // Fail closed: an unreadable shape is a refusal, never an absence.
91
+ const unreadableField = (server) => {
92
+ for (const key of ['env', 'headers']) {
93
+ const value = server[key];
94
+ if (value === undefined || value === null) continue;
95
+ if (typeof value !== 'object' || Array.isArray(value)) {
96
+ return `${key} is of type ${typeof value}, not an object`;
97
+ }
98
+ const badValue = Object.entries(value).find(([, v]) => typeof v !== 'string');
99
+ if (badValue) return `${key}.${badValue[0]} is not a string`;
100
+ }
101
+ if (server.cwd !== undefined && server.cwd !== null && typeof server.cwd !== 'string') {
102
+ return `cwd is of type ${typeof server.cwd}, not a string`;
103
+ }
104
+ if (server.args === undefined || server.args === null) return null;
105
+ if (!Array.isArray(server.args)) return `args is of type ${typeof server.args}, not an array`;
106
+ if (server.args.some((a) => typeof a !== 'string')) return 'args carries a non-string';
107
+ return null;
108
+ };
109
+
79
110
  const hasForeignExpansion = (value) => {
80
111
  if (typeof value !== 'string') return false;
81
112
  let rest = value;
@@ -127,6 +158,11 @@ export const auditDeclaredMcp = (environment, { instanceUrl, allowedStdioEntries
127
158
  }
128
159
  const name = typeof server.name === 'string' && server.name ? server.name : `mcp[${index}]`;
129
160
  const transport = server.transport || 'stdio';
161
+ const unreadable = unreadableField(server);
162
+ if (unreadable) {
163
+ refusals.push(`'${name}': declares ${unreadable}, so this guard cannot judge what the entry would run; refusing it rather than reading the field as absent`);
164
+ return;
165
+ }
130
166
  // One shape per entry. The adapters classified by which field was present,
131
167
  // so `{transport:'http', url:<instance>, command:['sh','-c',…]}` passed an
132
168
  // origin check here and ran as stdio there with the token substituted