codiedev 0.9.48 → 0.9.50

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.
@@ -9,6 +9,7 @@ const securityDeepScan_1 = require("../securityDeepScan");
9
9
  const utils_1 = require("../utils");
10
10
  const securityDoctor_1 = require("../securityDoctor");
11
11
  const securityFeature_1 = require("../securityFeature");
12
+ const SECURITY_USAGE = "Usage: codiedev security <doctor|pilot|inventory|scan|sync|rollback> [--json] [--deep] [--connect-mcp] [--supply-chain] [--no-upload] [--yes]";
12
13
  function printPilot(json) {
13
14
  const report = (0, securityPilot_1.runSyntheticSecurityPilot)();
14
15
  if (json) {
@@ -40,6 +41,10 @@ function printDoctor(json, companyId) {
40
41
  async function runSecurityCommand(args) {
41
42
  const subcommand = args.find((arg) => !arg.startsWith("-")) ?? "doctor";
42
43
  const json = args.includes("--json");
44
+ if (args.includes("--help") || args.includes("-h") || subcommand === "help") {
45
+ console.log(SECURITY_USAGE);
46
+ return;
47
+ }
43
48
  const connectedConfig = (0, utils_1.readConfig)();
44
49
  const requiresEntitlement = !["pilot", "self-test"].includes(subcommand);
45
50
  if (requiresEntitlement && (!connectedConfig || !(0, securityFeature_1.isAgentSecurityEnabled)(connectedConfig))) {
@@ -144,7 +149,7 @@ async function runSecurityCommand(args) {
144
149
  ? printDoctor(json, connectedConfig.companyId)
145
150
  : false;
146
151
  if (!["pilot", "self-test", "doctor", "status"].includes(subcommand)) {
147
- console.error("Usage: codiedev security <doctor|pilot|inventory|scan|sync|rollback> [--json] [--deep] [--connect-mcp] [--supply-chain] [--no-upload] [--yes]");
152
+ console.error(SECURITY_USAGE);
148
153
  }
149
154
  if (!ok)
150
155
  process.exitCode = 1;
package/dist/hook.js CHANGED
@@ -366,7 +366,11 @@ async function runSecurityEventUpload() {
366
366
  endpointName: config.deviceName,
367
367
  cliVersion: version_1.CLI_VERSION,
368
368
  });
369
- const result = await postJsonForResponse(`${config.backendUrl}/api/cli/securityEvent`, { ...eventWithEndpoint }, config.token, 1500);
369
+ const result = await postJsonForResponse(`${config.backendUrl}/api/cli/securityEvent`, { ...eventWithEndpoint }, config.token,
370
+ // Leave room for a cold backend and durable scheduling. This uploader is
371
+ // detached, so the network budget never delays the developer's prompt or
372
+ // tool call. Policy refreshes use the separately signed sync endpoint.
373
+ 5_000);
370
374
  if (result?.policy?.schemaVersion === 1) {
371
375
  (0, securityPolicy_1.writeEndpointSecurityPolicy)(result.policy, undefined, undefined, config.companyId);
372
376
  }
package/dist/security.js CHANGED
@@ -81,6 +81,13 @@ function effectivePolicyAction(policy, category, fallback, source, input) {
81
81
  const configured = policy.rules[category] ?? fallback;
82
82
  return policy.enforcementMode === "alert" && configured === "block" ? "alert" : configured;
83
83
  }
84
+ function policyActionPrefix(action) {
85
+ if (action === "block")
86
+ return "Codie blocked";
87
+ if (action === "alert")
88
+ return "Codie detected";
89
+ return "Codie detected but allowed";
90
+ }
84
91
  const SECRET_KEY = /(?:api[_-]?key|access[_-]?token|auth[_-]?token|password|passwd|secret|private[_-]?key|client[_-]?secret)/i;
85
92
  const SECRET_VALUE_PATTERNS = [
86
93
  /\b(?:sk|rk|pk)-(?:live|test|proj)?[-_A-Za-z0-9]{16,}\b/g,
@@ -131,7 +138,7 @@ function evaluatePromptSecurity(prompt, policy, source) {
131
138
  return {
132
139
  action,
133
140
  category: "secret_in_prompt",
134
- reason: "Codie blocked this prompt because it appears to contain an API key, password, or credential. Remove the secret value and reference the environment variable by name instead (for example, $OPENAI_API_KEY).",
141
+ reason: policyActionPrefix(action) + " this prompt because it appears to contain an API key, password, or credential. Remove the secret value and reference the environment variable by name instead (for example, $OPENAI_API_KEY).",
135
142
  };
136
143
  }
137
144
  function serializedToolInput(input) {
@@ -196,18 +203,20 @@ function evaluateToolSecurity(input, phase, policy, source, sessionContext) {
196
203
  if (!serialized)
197
204
  return { action: "allow" };
198
205
  if (destructiveTargetOutsideWorkspace(serialized, input.cwd)) {
206
+ const action = effectivePolicyAction(policy, "destructive_outside_workspace", "block", source, input);
199
207
  return {
200
- action: effectivePolicyAction(policy, "destructive_outside_workspace", "block", source, input),
208
+ action,
201
209
  category: "destructive_outside_workspace",
202
- reason: "Codie blocked a destructive command targeting files outside the approved workspace.",
210
+ reason: policyActionPrefix(action) + " a destructive command targeting files outside the approved workspace.",
203
211
  };
204
212
  }
205
213
  const hasNetworkEgress = /\b(?:curl|wget)\b|https?:\/\//i.test(serialized);
206
214
  if (hasNetworkEgress && containsSecretValue(serialized)) {
215
+ const action = effectivePolicyAction(policy, "secret_exfiltration", "block", source, input);
207
216
  return {
208
- action: effectivePolicyAction(policy, "secret_exfiltration", "block", source, input),
217
+ action,
209
218
  category: "secret_exfiltration",
210
- reason: "Codie blocked a command that would send credential material to an external destination.",
219
+ reason: policyActionPrefix(action) + " a command that would send credential material to an external destination.",
211
220
  };
212
221
  }
213
222
  if (hasNetworkEgress &&
@@ -221,17 +230,19 @@ function evaluateToolSecurity(input, phase, policy, source, sessionContext) {
221
230
  }
222
231
  const writeOperation = isWriteOperation(input, serialized);
223
232
  if (writeOperation && SENSITIVE_PATH.test(serialized)) {
233
+ const action = effectivePolicyAction(policy, "sensitive_file_write", "block", source, input);
224
234
  return {
225
- action: effectivePolicyAction(policy, "sensitive_file_write", "block", source, input),
235
+ action,
226
236
  category: "sensitive_file_write",
227
- reason: "Codie blocked a write to a credential-bearing file.",
237
+ reason: policyActionPrefix(action) + " a write to a credential-bearing file.",
228
238
  };
229
239
  }
230
240
  if (writeOperation && PROTECTED_AGENT_CONFIG.test(serialized)) {
241
+ const action = effectivePolicyAction(policy, "protected_agent_config_write", "block", source, input);
231
242
  return {
232
- action: effectivePolicyAction(policy, "protected_agent_config_write", "block", source, input),
243
+ action,
233
244
  category: "protected_agent_config_write",
234
- reason: "Codie blocked an agent from modifying its own hooks, policy, MCP, or instruction configuration.",
245
+ reason: policyActionPrefix(action) + " an agent modifying its own hooks, policy, MCP, or instruction configuration.",
235
246
  };
236
247
  }
237
248
  const server = mcpServerName(input);
@@ -313,10 +313,18 @@ function collectCodexToml(filePath, homeDir, workspace, components) {
313
313
  if (!raw)
314
314
  return;
315
315
  const text = raw.toString("utf8");
316
- const sections = [...text.matchAll(/^\[mcp_servers\.(["']?)([^\]"']+)\1\]\s*$([\s\S]*?)(?=^\[|\s*$)/gm)];
317
- for (const section of sections) {
318
- const name = section[2].trim();
319
- const body = section[3].trim();
316
+ const lines = text.split(/\r?\n/);
317
+ for (let index = 0; index < lines.length; index += 1) {
318
+ const section = lines[index].match(/^\s*\[mcp_servers\.(?:"([^"]+)"|'([^']+)'|([^\]]+))\]\s*(?:#.*)?$/);
319
+ if (!section)
320
+ continue;
321
+ const name = (section[1] ?? section[2] ?? section[3] ?? "").trim();
322
+ const bodyLines = [];
323
+ for (index += 1; index < lines.length && !/^\s*\[/.test(lines[index]); index += 1) {
324
+ bodyLines.push(lines[index]);
325
+ }
326
+ index -= 1;
327
+ const body = bodyLines.join("\n").trim();
320
328
  components.push(makeComponent({
321
329
  kind: "mcp-server", name, sourcePath: filePath, homeDir, workspace,
322
330
  content: body, findings: commandFindings(body),
@@ -443,11 +451,13 @@ function buildSecurityInventory(options = {}) {
443
451
  path.join(homeDir, ".config", "opencode", "opencode.jsonc"),
444
452
  path.join(homeDir, ".config", "Code", "User", "mcp.json"),
445
453
  path.join(homeDir, ".config", "Cursor", "User", "mcp.json"),
454
+ path.join(homeDir, ".codex", "hooks.json"),
446
455
  path.join(homeDir, "Library", "Application Support", "Code", "User", "mcp.json"),
447
456
  path.join(homeDir, "Library", "Application Support", "Cursor", "User", "mcp.json"),
448
457
  path.join(homeDir, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
449
458
  path.join(workspace, ".mcp.json"), path.join(workspace, ".cursor", "mcp.json"),
450
459
  path.join(workspace, ".vscode", "mcp.json"),
460
+ path.join(workspace, ".codex", "hooks.json"),
451
461
  path.join(workspace, ".claude", "settings.json"), path.join(workspace, ".claude", "settings.local.json"),
452
462
  path.join(workspace, "opencode.json"), path.join(workspace, "opencode.jsonc"),
453
463
  ];
@@ -1,5 +1,13 @@
1
1
  import type { AgentSurface, EndpointSecurityPolicy } from "./security";
2
2
  export type TrustedPolicyKeys = Record<string, string>;
3
+ /**
4
+ * Vendor policy roots are compiled into the endpoint binary. This prevents a
5
+ * compromised control-plane response from introducing the key that verifies
6
+ * that same response. Additional customer-managed roots may still be supplied
7
+ * through CODIEDEV_SECURITY_POLICY_PUBLIC_KEYS, but they cannot replace the
8
+ * Codiedev production root with the same key id.
9
+ */
10
+ export declare const BUILT_IN_POLICY_KEYS: TrustedPolicyKeys;
3
11
  export declare function trustedPolicyKeysFromEnvironment(env?: NodeJS.ProcessEnv): TrustedPolicyKeys;
4
12
  export declare function verifyEndpointSecurityPolicySignature(policy: EndpointSecurityPolicy, trustedKeys?: TrustedPolicyKeys): {
5
13
  ok: boolean;
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.BUILT_IN_POLICY_KEYS = void 0;
36
37
  exports.trustedPolicyKeysFromEnvironment = trustedPolicyKeysFromEnvironment;
37
38
  exports.verifyEndpointSecurityPolicySignature = verifyEndpointSecurityPolicySignature;
38
39
  exports.readEndpointSecurityPolicy = readEndpointSecurityPolicy;
@@ -52,6 +53,16 @@ function policyFile(companyId) {
52
53
  const scope = companyId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128);
53
54
  return `security-policy.${scope}.json`;
54
55
  }
56
+ /**
57
+ * Vendor policy roots are compiled into the endpoint binary. This prevents a
58
+ * compromised control-plane response from introducing the key that verifies
59
+ * that same response. Additional customer-managed roots may still be supplied
60
+ * through CODIEDEV_SECURITY_POLICY_PUBLIC_KEYS, but they cannot replace the
61
+ * Codiedev production root with the same key id.
62
+ */
63
+ exports.BUILT_IN_POLICY_KEYS = {
64
+ "codiedev-prod-2026-01": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA0H+UISjTn4zhd6Pk9YmrMBqnJEJmv9fXoavXorlV2cU=\n-----END PUBLIC KEY-----\n",
65
+ };
55
66
  function stableJson(value) {
56
67
  if (Array.isArray(value))
57
68
  return `[${value.map(stableJson).join(",")}]`;
@@ -66,16 +77,17 @@ function stableJson(value) {
66
77
  function trustedPolicyKeysFromEnvironment(env = process.env) {
67
78
  const raw = env.CODIEDEV_SECURITY_POLICY_PUBLIC_KEYS;
68
79
  if (!raw)
69
- return {};
80
+ return { ...exports.BUILT_IN_POLICY_KEYS };
70
81
  try {
71
82
  const parsed = JSON.parse(raw);
72
83
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
73
- return {};
74
- return Object.fromEntries(Object.entries(parsed)
84
+ return { ...exports.BUILT_IN_POLICY_KEYS };
85
+ const configured = Object.fromEntries(Object.entries(parsed)
75
86
  .filter((entry) => typeof entry[1] === "string"));
87
+ return { ...configured, ...exports.BUILT_IN_POLICY_KEYS };
76
88
  }
77
89
  catch {
78
- return {};
90
+ return { ...exports.BUILT_IN_POLICY_KEYS };
79
91
  }
80
92
  }
81
93
  function verifyEndpointSecurityPolicySignature(policy, trustedKeys = trustedPolicyKeysFromEnvironment()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.9.48",
3
+ "version": "0.9.50",
4
4
  "description": "Connect Claude Code, Codex, Cursor, OpenCode, or VS Code Copilot to Codiedev for team session capture and artifact collaboration — and run the governed Codie harness",
5
5
  "bin": {
6
6
  "codiedev": "dist/cli.js",
@@ -12,6 +12,7 @@
12
12
  "dev": "tsc --watch",
13
13
  "prepublishOnly": "tsc",
14
14
  "test": "vitest run",
15
+ "test:security-beta": "vitest run src/__tests__/security.test.ts src/__tests__/securityCommandEntitlement.test.ts src/__tests__/securityDeepScan.test.ts src/__tests__/securityDoctor.test.ts src/__tests__/securityEntitlementInstall.test.ts src/__tests__/securityFeature.test.ts src/__tests__/securityInstall.test.ts src/__tests__/securityInventory.test.ts src/__tests__/securityPilot.test.ts src/__tests__/securityPolicy.test.ts src/__tests__/securitySession.test.ts src/__tests__/codexHookTrust.test.ts src/__tests__/codieSecurityAdapterWiring.test.ts && tsc && node dist/cli.js security pilot --json",
15
16
  "test:security-pilot": "vitest run src/__tests__/security.test.ts src/__tests__/securityInstall.test.ts src/__tests__/securityPilot.test.ts src/__tests__/codieSecurityAdapterWiring.test.ts && tsc && node dist/cli.js security pilot --json",
16
17
  "test:watch": "vitest"
17
18
  },