@mmmbuto/nexuscrew 0.9.23 → 0.9.25

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/lib/mcp/server.js CHANGED
@@ -32,6 +32,7 @@ const { TOOLS, IDENTITY_CODE, IDENTITY_REMEDIATION } = require('./tools.js');
32
32
  const cells = require('./cells.js');
33
33
  const {
34
34
  normalizeIdentityContext, IDENTITY_CONTEXT_AUTHORITY_UNAVAILABLE,
35
+ IDENTITY_CONTEXT_VERIFIED_ENV_INVALID,
35
36
  } = require('./identity-schema.js');
36
37
  const { persistIdentityChannel } = require('./identity-provider.js');
37
38
 
@@ -99,18 +100,55 @@ function companionInstructions() {
99
100
  // senza cambiare la precedenza e senza esporre valori/segreti. `resolveSession`
100
101
  // resta il wrapper pubblico Promise<string|null> invariato (compatibilita').
101
102
  const IDENTITY_REQUIRED_ENV_VARS = Object.freeze(['TMUX', 'TMUX_PANE', 'NEXUSCREW_MCP_SESSION']);
103
+ // i nomi dei metadati verified consegnati dal launcher VL (solo su
104
+ // sessione bound). Presence only: mai i valori.
105
+ const IDENTITY_VERIFIED_ENV_VARS = Object.freeze([
106
+ 'NEXUSCREW_VERIFIED_ENV_VERSION',
107
+ 'NEXUSCREW_VERIFIED_OWNER_INSTANCE_ID',
108
+ 'NEXUSCREW_VERIFIED_CELL_ID',
109
+ 'NEXUSCREW_VERIFIED_INCARNATION_ID',
110
+ 'NEXUSCREW_VERIFIED_BINDING_ID',
111
+ 'NEXUSCREW_VERIFIED_ORIGIN',
112
+ 'NEXUSCREW_VERIFIED_THREAD_ID',
113
+ ]);
114
+ const IDENTITY_ALL_REQUIRED_ENV_VARS = Object.freeze([
115
+ ...IDENTITY_REQUIRED_ENV_VARS, ...IDENTITY_VERIFIED_ENV_VARS,
116
+ ]);
102
117
 
103
118
  function envPresenceOf(env) {
104
- return {
119
+ const presence = {
105
120
  TMUX: !!env.TMUX,
106
121
  TMUX_PANE: !!env.TMUX_PANE,
107
122
  NEXUSCREW_MCP_SESSION: !!(typeof env.NEXUSCREW_MCP_SESSION === 'string' && env.NEXUSCREW_MCP_SESSION.trim()),
108
123
  };
124
+ for (const name of IDENTITY_VERIFIED_ENV_VARS) {
125
+ presence[name] = !!(typeof env[name] === 'string' && env[name].trim());
126
+ }
127
+ return presence;
128
+ }
129
+
130
+ // la fonte verified esiste se ANY dei nomi verified e' presente.
131
+ // Presence parziale = fonte presente ma invalida (fail-closed), non assenza.
132
+ function verifiedEnvPresent(envPresence) {
133
+ return IDENTITY_VERIFIED_ENV_VARS.some((name) => envPresence[name] === true);
109
134
  }
110
135
 
111
136
  function resolveIdentity({ env, tmuxBin, execFileImpl }) {
112
137
  const e = env || {};
113
138
  const envPresence = envPresenceOf(e);
139
+ // se una qualunque variabile verified e' presente, il percorso
140
+ // legacy e' VETATO (non e' un fallback, e' un percorso alternativo usato
141
+ // solo in assenza totale della verified). La verifica completa (introspezione
142
+ // live/coerente) vive nel provider online; qui il chiamante senza provider
143
+ // riceve comunque un fail-closed nominato invece di un'identita' legacy
144
+ // fabbricata accanto a metadati verified di cui non si puo' garantire la
145
+ // coerenza.
146
+ if (verifiedEnvPresent(envPresence)) {
147
+ return {
148
+ session: null, source: 'verified-env', code: IDENTITY_CODE.VERIFIED_ENV_INVALID,
149
+ envPresence, requiredEnvVars: IDENTITY_ALL_REQUIRED_ENV_VARS, remediation: IDENTITY_REMEDIATION,
150
+ };
151
+ }
114
152
  const fallbackRaw = e.NEXUSCREW_MCP_SESSION;
115
153
  const fallbackPresent = typeof fallbackRaw === 'string' && fallbackRaw.trim().length > 0;
116
154
  const tmuxPresent = !!e.TMUX;
@@ -130,24 +168,24 @@ function resolveIdentity({ env, tmuxBin, execFileImpl }) {
130
168
 
131
169
  const ok = (session, source) => ({
132
170
  session, source, code: IDENTITY_CODE.OK,
133
- envPresence, requiredEnvVars: IDENTITY_REQUIRED_ENV_VARS, remediation: IDENTITY_REMEDIATION,
171
+ envPresence, requiredEnvVars: IDENTITY_ALL_REQUIRED_ENV_VARS, remediation: IDENTITY_REMEDIATION,
134
172
  });
135
173
  const missing = () => ({
136
174
  session: null, source: 'missing', code: codeWhenMissing(),
137
- envPresence, requiredEnvVars: IDENTITY_REQUIRED_ENV_VARS, remediation: IDENTITY_REMEDIATION,
175
+ envPresence, requiredEnvVars: IDENTITY_ALL_REQUIRED_ENV_VARS, remediation: IDENTITY_REMEDIATION,
138
176
  });
139
177
  // P0: pane stantio o non verificabile -> NON attribuire. source 'stale-pane'
140
178
  // nomina il problema; code STALE_PANE (tools.js).
141
179
  const stalePane = () => ({
142
180
  session: null, source: 'stale-pane', code: IDENTITY_CODE.STALE_PANE,
143
- envPresence, requiredEnvVars: IDENTITY_REQUIRED_ENV_VARS, remediation: IDENTITY_REMEDIATION,
181
+ envPresence, requiredEnvVars: IDENTITY_ALL_REQUIRED_ENV_VARS, remediation: IDENTITY_REMEDIATION,
144
182
  });
145
183
  // P0/R2: tmux e fallback env dicono sessioni DIVERSE entrambe valide:
146
184
  // identità ambigua -> NON attribuire (nemmeno il fallback: è parte del
147
185
  // conflitto). Il code nomina il mismatch, che INVALID non direbbe.
148
186
  const sessionMismatch = () => ({
149
187
  session: null, source: 'session-mismatch', code: IDENTITY_CODE.SESSION_MISMATCH,
150
- envPresence, requiredEnvVars: IDENTITY_REQUIRED_ENV_VARS, remediation: IDENTITY_REMEDIATION,
188
+ envPresence, requiredEnvVars: IDENTITY_ALL_REQUIRED_ENV_VARS, remediation: IDENTITY_REMEDIATION,
151
189
  });
152
190
 
153
191
  return new Promise((resolve) => {
@@ -273,7 +311,8 @@ function createMcpServer(opts = {}) {
273
311
  try {
274
312
  raw = await identityContextProvider({ tool });
275
313
  } catch (e) {
276
- if (e && e.code === IDENTITY_CONTEXT_AUTHORITY_UNAVAILABLE) throw e;
314
+ if (e && (e.code === IDENTITY_CONTEXT_AUTHORITY_UNAVAILABLE
315
+ || e.code === IDENTITY_CONTEXT_VERIFIED_ENV_INVALID)) throw e;
277
316
  throw identityContextError(IDENTITY_CONTEXT_MISSING,
278
317
  'contesto identita online non disponibile: binding condiviso rifiutato');
279
318
  }
@@ -284,7 +323,7 @@ function createMcpServer(opts = {}) {
284
323
  if (!identityContextProvider) return localIdentity();
285
324
  return identityContext({ tool, sharedRequired: true }).then((context) => ({
286
325
  session: context.tmuxSession,
287
- source: 'online',
326
+ source: identityContextProvider.identitySource === 'verified-env' ? 'verified-env' : 'online',
288
327
  code: IDENTITY_CODE.OK,
289
328
  envPresence: envPresenceOf(env),
290
329
  requiredEnvVars: IDENTITY_REQUIRED_ENV_VARS,
@@ -442,9 +481,11 @@ function createMcpServer(opts = {}) {
442
481
  ? identityContextProvider.currentProof() : null;
443
482
  const sharedIdentity = sharedContext ? Object.freeze({
444
483
  session: sharedContext.tmuxSession,
445
- source: 'online', code: IDENTITY_CODE.OK,
484
+ source: identityContextProvider.identitySource === 'verified-env'
485
+ ? 'verified-env' : 'online',
486
+ code: IDENTITY_CODE.OK,
446
487
  envPresence: envPresenceOf(env),
447
- requiredEnvVars: IDENTITY_REQUIRED_ENV_VARS,
488
+ requiredEnvVars: IDENTITY_ALL_REQUIRED_ENV_VARS,
448
489
  remediation: IDENTITY_REMEDIATION,
449
490
  context: sharedContext,
450
491
  ...(sharedProof ? { proof: sharedProof } : {}),
package/lib/mcp/tools.js CHANGED
@@ -44,15 +44,26 @@ const IDENTITY_CODE = Object.freeze({
44
44
  // P0/R2: tmux e NEXUSCREW_MCP_SESSION sono entrambi validi ma dicono sessioni
45
45
  // diverse: identità ambigua, non si attribuisce nessuna delle due.
46
46
  SESSION_MISMATCH: 'NEXUSCREW_MCP_IDENTITY_SESSION_MISMATCH',
47
+ // una fonte verified (NEXUSCREW_VERIFIED_*) presente ma non
48
+ // coerente (versione, owner, cella, canale, introspezione) e' fail-closed:
49
+ // MAI fallback alle variabili legacy quando la fonte verified esiste.
50
+ VERIFIED_ENV_INVALID: 'NEXUSCREW_MCP_IDENTITY_VERIFIED_ENV_INVALID',
47
51
  });
48
52
 
49
53
  // Remediation senza segreti/valori: nomi soltanto, compatibile con
50
54
  // `codex-vl mcp add --env-var` (allowlist di nomi, nessun valore persistito).
55
+ // la remediation NON consiglia piu' di allowlistare TMUX/TMUX_PANE/
56
+ // NEXUSCREW_MCP_SESSION in env_vars: sul percorso shared verified quel permesso
57
+ // fa fallire lo spawn (rmcp-client utils: nomi riservati rifiutati). Le strade
58
+ // sono due: sessione verified (codex-vl >= 0.154.0-vl.3 + NexusCrew >= 0.9.25
59
+ // con authority mode provisionato) oppure sessione legacy con le tre variabili
60
+ // ereditate dal client MCP.
51
61
  const IDENTITY_REMEDIATION =
52
- 'Nei client che ripuliscono l\'ambiente, allowlista i nomi delle variabili di identita nel server MCP stdio '
53
- + '(codex-vl mcp add ... --env-var NEXUSCREW_MCP_SESSION --env-var TMUX --env-var TMUX_PANE) '
54
- + 'oppure assicurati che il client MCP inoltri il contesto tmux al processo child: nessun valore '
55
- + 'viene copiato nella CLI o nel file di configurazione.';
62
+ 'Due percorsi validi: (1) sessione verified — serve codex-vl >= 0.154.0-vl.3 e NexusCrew >= 0.9.25 '
63
+ + 'con authority mode provisionato (nexuscrew identity provision): il launcher consegna i metadati '
64
+ + 'NEXUSCREW_VERIFIED_* al figlio MCP, non serve allowlistare nulla; (2) sessione legacy — il client '
65
+ + 'MCP deve ereditare TMUX/TMUX_PANE/NEXUSCREW_MCP_SESSION (niente allowlist in env_vars del server '
66
+ + 'MCP: con codex-vl >= 0.154.0-vl.3 i nomi sono riservati e lo spawn fallisce).';
56
67
 
57
68
  // Perche' una ricerca fallita non e' una sola cosa.
58
69
  //
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmmbuto/nexuscrew",
3
- "version": "0.9.23",
3
+ "version": "0.9.25",
4
4
  "description": "Faithful browser tmux client \u2014 attach to live sessions over a real PTY, localhost-only, mobile-easy",
5
5
  "main": "lib/server.js",
6
6
  "bin": {