codiedev 0.9.56 → 0.10.0-beta.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.
Files changed (46) hide show
  1. package/README.md +5 -4
  2. package/dist/captureLog.d.ts +1 -1
  3. package/dist/cli.js +11 -1
  4. package/dist/codexLimitScan.d.ts +0 -2
  5. package/dist/codexLimitScan.js +2 -15
  6. package/dist/codie.js +146 -1
  7. package/dist/codieLauncher.d.ts +5 -0
  8. package/dist/codieLauncher.js +23 -1
  9. package/dist/codieRuntime.d.ts +11 -1
  10. package/dist/codieRuntime.js +20 -3
  11. package/dist/codieRuntimeInstall.d.ts +12 -11
  12. package/dist/codieRuntimeInstall.js +3 -1
  13. package/dist/codieStartup.d.ts +26 -0
  14. package/dist/codieStartup.js +30 -0
  15. package/dist/commands/doctor.js +8 -0
  16. package/dist/commands/security.d.ts +1 -0
  17. package/dist/commands/security.js +156 -0
  18. package/dist/connect.js +56 -4
  19. package/dist/cursorHooks.d.ts +16 -2
  20. package/dist/cursorHooks.js +31 -6
  21. package/dist/hook.js +232 -22
  22. package/dist/limitEvent.d.ts +5 -1
  23. package/dist/limitEvent.js +6 -27
  24. package/dist/security.d.ts +102 -0
  25. package/dist/security.js +437 -0
  26. package/dist/securityDeepScan.d.ts +41 -0
  27. package/dist/securityDeepScan.js +248 -0
  28. package/dist/securityDoctor.d.ts +32 -0
  29. package/dist/securityDoctor.js +63 -0
  30. package/dist/securityFeature.d.ts +5 -0
  31. package/dist/securityFeature.js +7 -0
  32. package/dist/securityInstall.d.ts +63 -0
  33. package/dist/securityInstall.js +239 -0
  34. package/dist/securityInventory.d.ts +54 -0
  35. package/dist/securityInventory.js +556 -0
  36. package/dist/securityPilot.d.ts +19 -0
  37. package/dist/securityPilot.js +61 -0
  38. package/dist/securityPolicy.d.ts +33 -0
  39. package/dist/securityPolicy.js +188 -0
  40. package/dist/securitySession.d.ts +6 -0
  41. package/dist/securitySession.js +72 -0
  42. package/dist/upgradeNudge.d.ts +1 -0
  43. package/dist/upgradeNudge.js +1 -0
  44. package/dist/utils.d.ts +12 -6
  45. package/dist/utils.js +201 -38
  46. package/package.json +11 -2
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runSecurityCommand = runSecurityCommand;
4
+ const securityInstall_1 = require("../securityInstall");
5
+ const securityPilot_1 = require("../securityPilot");
6
+ const securityPolicy_1 = require("../securityPolicy");
7
+ const securityInventory_1 = require("../securityInventory");
8
+ const securityDeepScan_1 = require("../securityDeepScan");
9
+ const utils_1 = require("../utils");
10
+ const securityDoctor_1 = require("../securityDoctor");
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]";
13
+ function printPilot(json) {
14
+ const report = (0, securityPilot_1.runSyntheticSecurityPilot)();
15
+ if (json) {
16
+ console.log(JSON.stringify(report, null, 2));
17
+ return report.ok;
18
+ }
19
+ console.log("\nCodiedev security pilot — synthetic, side-effect-free validation\n");
20
+ for (const item of report.cases) {
21
+ console.log(`${item.passed ? "✓" : "✗"} ${item.surface}: ${item.name} (${item.actual})`);
22
+ }
23
+ console.log(`\n${report.ok ? "PASS" : "FAIL"}: ${report.cases.filter((item) => item.passed).length}/${report.cases.length} policy checks matched.`);
24
+ console.log("No commands were executed and no real credentials were used.\n");
25
+ return report.ok;
26
+ }
27
+ function printDoctor(json, companyId) {
28
+ const result = (0, securityDoctor_1.collectSecurityDoctorReport)(companyId);
29
+ if (json) {
30
+ console.log(JSON.stringify(result, null, 2));
31
+ return result.ok;
32
+ }
33
+ console.log("\nCodiedev doctor — Agent Security details\n");
34
+ for (const check of (0, securityDoctor_1.securityDoctorChecks)(result)) {
35
+ const mark = check.status === "pass" ? "✓" : check.status === "warn" ? "!" : "✗";
36
+ console.log(`${mark} ${check.name}: ${check.detail}`);
37
+ }
38
+ console.log(`\n${result.ok ? "PASS" : "FAIL"}: ${result.ok ? "endpoint security is healthy" : "run `codiedev connect`, then repeat this check"}.\n`);
39
+ return result.ok;
40
+ }
41
+ async function runSecurityCommand(args) {
42
+ const subcommand = args.find((arg) => !arg.startsWith("-")) ?? "doctor";
43
+ const json = args.includes("--json");
44
+ if (args.includes("--help") || args.includes("-h") || subcommand === "help") {
45
+ console.log(SECURITY_USAGE);
46
+ return;
47
+ }
48
+ const connectedConfig = (0, utils_1.readConfig)();
49
+ const requiresEntitlement = !["pilot", "self-test"].includes(subcommand);
50
+ if (requiresEntitlement && (!connectedConfig || !(0, securityFeature_1.isAgentSecurityEnabled)(connectedConfig))) {
51
+ const detail = connectedConfig
52
+ ? `Agent Security is not enabled for ${connectedConfig.companyName}.`
53
+ : "Not connected. Run `codiedev connect` first.";
54
+ if (json)
55
+ console.log(JSON.stringify({ schemaVersion: 1, enabled: false, detail }, null, 2));
56
+ else
57
+ console.log(`\n${detail}\n`);
58
+ return;
59
+ }
60
+ if (subcommand === "inventory" || subcommand === "scan") {
61
+ const workspaceFlag = args.indexOf("--workspace");
62
+ const workspace = workspaceFlag >= 0 && args[workspaceFlag + 1]
63
+ ? args[workspaceFlag + 1]
64
+ : process.cwd();
65
+ const inventory = (0, securityInventory_1.buildSecurityInventory)({ workspace });
66
+ const deep = subcommand === "scan" && args.includes("--deep")
67
+ ? (0, securityDeepScan_1.runDeepSecurityScan)(inventory, {
68
+ workspace,
69
+ connectMcp: args.includes("--connect-mcp"),
70
+ supplyChain: args.includes("--supply-chain"),
71
+ })
72
+ : undefined;
73
+ const uploadableInventory = deep ? (0, securityDeepScan_1.mergeDeepScanFindings)(inventory, deep) : inventory;
74
+ let upload;
75
+ const config = (0, utils_1.readConfig)();
76
+ if (config && !args.includes("--no-upload")) {
77
+ try {
78
+ upload = await (0, securityInventory_1.uploadSecurityInventory)(uploadableInventory, config);
79
+ }
80
+ catch (error) {
81
+ if (!json)
82
+ console.warn(`Inventory upload failed; local scan is still valid: ${error.message}`);
83
+ }
84
+ }
85
+ const output = { inventory: uploadableInventory, scanners: (0, securityInventory_1.scannerAvailability)(), ...(deep ? { deep } : {}), ...(upload ? { upload } : {}) };
86
+ if (json)
87
+ console.log(JSON.stringify(output, null, 2));
88
+ else {
89
+ const risky = inventory.components.filter((component) => component.findings.length > 0).length;
90
+ console.log(`\nCodiedev security inventory — ${inventory.components.length} components, ${risky} with local findings, ${inventory.drift.length} changed\n`);
91
+ const counts = new Map();
92
+ for (const component of inventory.components)
93
+ counts.set(component.kind, (counts.get(component.kind) ?? 0) + 1);
94
+ for (const [kind, count] of [...counts].sort())
95
+ console.log(` ${kind}: ${count}`);
96
+ if (deep)
97
+ for (const run of deep.runs)
98
+ console.log(` ${run.scanner}: ${run.status} — ${run.detail}`);
99
+ if (upload)
100
+ console.log(`\nUploaded ${upload.accepted} component records to the security workspace.`);
101
+ console.log("\nUse --json for machine-readable hashes and findings. Use scan --deep for installed OSS scanners; --connect-mcp and --supply-chain are explicit because they start or download third-party code.\n");
102
+ }
103
+ return;
104
+ }
105
+ if (subcommand === "sync") {
106
+ const config = (0, utils_1.readConfig)();
107
+ if (!config) {
108
+ console.error("Not connected. Run `codiedev connect` first.");
109
+ process.exitCode = 1;
110
+ return;
111
+ }
112
+ try {
113
+ const policy = await (0, securityPolicy_1.syncEndpointSecurityPolicy)(config);
114
+ if (json)
115
+ console.log(JSON.stringify({ ok: true, policy }, null, 2));
116
+ else {
117
+ const verification = (0, securityPolicy_1.verifyEndpointSecurityPolicySignature)(policy);
118
+ console.log(`Security policy v${policy.policyVersion} synced (${policy.enforcementMode} mode; ${verification.detail}).`);
119
+ }
120
+ }
121
+ catch (error) {
122
+ console.error(`Security policy sync failed: ${error.message}`);
123
+ process.exitCode = 1;
124
+ }
125
+ return;
126
+ }
127
+ if (subcommand === "rollback") {
128
+ if (!args.includes("--yes")) {
129
+ console.error("Rollback overwrites the installed agent hook configurations. Re-run with `codiedev security rollback --yes` after confirming the snapshot shown by `codiedev doctor`.");
130
+ process.exitCode = 1;
131
+ return;
132
+ }
133
+ try {
134
+ const result = (0, securityInstall_1.restoreSecurityInstallSnapshot)((0, securityInstall_1.readSecurityInstallManifest)());
135
+ if (json)
136
+ console.log(JSON.stringify({ schemaVersion: 1, ok: true, ...result }, null, 2));
137
+ else
138
+ console.log(`Restored ${result.restored} hook configuration(s) from snapshot ${result.snapshotId}.`);
139
+ }
140
+ catch (error) {
141
+ console.error(`Rollback failed: ${error.message}`);
142
+ process.exitCode = 1;
143
+ }
144
+ return;
145
+ }
146
+ const ok = subcommand === "pilot" || subcommand === "self-test"
147
+ ? printPilot(json)
148
+ : subcommand === "doctor" || subcommand === "status"
149
+ ? printDoctor(json, connectedConfig.companyId)
150
+ : false;
151
+ if (!["pilot", "self-test", "doctor", "status"].includes(subcommand)) {
152
+ console.error(SECURITY_USAGE);
153
+ }
154
+ if (!ok)
155
+ process.exitCode = 1;
156
+ }
package/dist/connect.js CHANGED
@@ -39,11 +39,16 @@ const https = __importStar(require("https"));
39
39
  const http = __importStar(require("http"));
40
40
  const crypto = __importStar(require("crypto"));
41
41
  const os = __importStar(require("os"));
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
42
44
  const utils_1 = require("./utils");
43
45
  const connectFlow_1 = require("./connectFlow");
44
46
  const version_1 = require("./version");
45
47
  const upgradeNudge_1 = require("./upgradeNudge");
46
48
  const shared_1 = require("./commands/shared");
49
+ const securityInstall_1 = require("./securityInstall");
50
+ const securityPolicy_1 = require("./securityPolicy");
51
+ const securityFeature_1 = require("./securityFeature");
47
52
  // cli.codiedev.com is a Convex custom domain on the prod deployment; the
48
53
  // underlying judicious-falcon-861.convex.site URL keeps serving, so configs
49
54
  // written by older CLI versions stay valid without a re-connect.
@@ -200,6 +205,7 @@ async function runConnect(args = []) {
200
205
  // Extract fields from response
201
206
  const companyId = responseData.companyId;
202
207
  const companyName = responseData.companyName;
208
+ const agentSecurityEnabled = responseData.agentSecurityEnabled === true;
203
209
  const repos = responseData.repos ?? [];
204
210
  if (!companyId || !companyName) {
205
211
  console.error("\nError: Invalid response from server. Token may be invalid.");
@@ -212,6 +218,7 @@ async function runConnect(args = []) {
212
218
  repos,
213
219
  lastSynced: new Date().toISOString(),
214
220
  backendUrl: BACKEND_URL,
221
+ agentSecurityEnabled,
215
222
  deviceId: saved?.deviceId || crypto.randomUUID(),
216
223
  deviceName: saved?.deviceName || os.hostname(),
217
224
  captureIgnoredPaths: saved?.captureIgnoredPaths ?? [],
@@ -280,6 +287,17 @@ async function runConnect(args = []) {
280
287
  // /api/cli/markInstalled so the portal's "Connected" state reflects
281
288
  // what's actually wired up on this machine, not just "token validated."
282
289
  const installedTargets = [];
290
+ const home = os.homedir();
291
+ const securityConfigPaths = [
292
+ path.join(home, ".claude", "settings.json"),
293
+ path.join(home, ".codex", "hooks.json"),
294
+ path.join(home, ".cursor", "hooks.json"),
295
+ path.join(home, ".config", "opencode", "plugins", "codiedev.js"),
296
+ path.join(home, ".codex", "config.toml"),
297
+ ];
298
+ // Snapshot before any hook update. Rollback is intentionally not automatic:
299
+ // an administrator can inspect these 0600 copies before restoring them.
300
+ const securitySnapshot = (0, securityInstall_1.createSecurityInstallSnapshot)(securityConfigPaths);
283
301
  // Wrap each per-target write so we emit one install_target row each. The
284
302
  // existing user-visible behavior (console.warn on failure, continue) is
285
303
  // unchanged — telemetry just observes the outcome.
@@ -302,21 +320,52 @@ async function runConnect(args = []) {
302
320
  }
303
321
  };
304
322
  if (hasClaude) {
305
- runInstall("claude-code-hook", utils_1.installHook, "Claude Code SessionEnd (capture) + UserPromptSubmit (skill discovery) + Stop (auto-backup) hooks (~/.claude/settings.json)", "claudeCode-hook");
323
+ runInstall("claude-code-hook", () => (0, utils_1.installHook)(agentSecurityEnabled), "Claude Code SessionEnd (capture) + UserPromptSubmit (skill discovery) + Stop (auto-backup) hooks (~/.claude/settings.json)", "claudeCode-hook");
306
324
  runInstall("claude-code-instructions", utils_1.installClaudeCodeInstructions, "Claude Code agent instructions (~/.claude/CLAUDE.md)", "claudeCode-instructions");
307
325
  }
308
326
  if (hasCodex) {
309
- runInstall("codex-hook", utils_1.installCodexHook, "Codex Stop (capture + auto-backup) hooks (~/.codex/hooks.json)", "codex-hook");
327
+ runInstall("codex-hook", () => (0, utils_1.installCodexHook)(agentSecurityEnabled), "Codex Stop (capture + auto-backup) hooks (~/.codex/hooks.json)", "codex-hook");
310
328
  runInstall("codex-instructions", utils_1.installCodexInstructions, "Codex agent instructions (~/.codex/AGENTS.md)", "codex-instructions");
311
329
  }
312
330
  if (hasCursor) {
313
- runInstall("cursor-hook", utils_1.installCursorHook, "Cursor sessionStart + stop backup + sessionEnd capture hooks (~/.cursor/hooks.json)", "cursor-hook");
331
+ runInstall("cursor-hook", () => (0, utils_1.installCursorHook)(agentSecurityEnabled), "Cursor sessionStart + prompt security + stop backup + sessionEnd capture hooks (~/.cursor/hooks.json)", "cursor-hook");
314
332
  runInstall("cursor-instructions", utils_1.installCursorInstructions, "Cursor agent instructions (~/.cursor/rules/codiedev.mdc)", "cursor-instructions");
315
333
  }
316
334
  // Always stage the adapter. It is dormant until OpenCode starts, which lets
317
335
  // a user connect Claude Code today and install the Codie harness tomorrow
318
336
  // without needing a second setup ritual.
319
- runInstall("opencode-capture-adapter", utils_1.installOpenCodeCapturePlugin, "OpenCode session capture adapter (~/.config/opencode/plugins/codiedev.js)", "opencode-capture");
337
+ runInstall("opencode-capture-adapter", () => (0, utils_1.installOpenCodeCapturePlugin)(agentSecurityEnabled), "OpenCode session capture adapter (~/.config/opencode/plugins/codiedev.js)", "opencode-capture");
338
+ // Record the exact installed hook bytes and the pre-update rollback files.
339
+ // This supplies health/integrity checks without requiring a package publish
340
+ // or trusting whichever `node` happens to be first on PATH later.
341
+ try {
342
+ const hookScriptPath = path.join(__dirname, "hook.js");
343
+ if (agentSecurityEnabled && fs.existsSync(hookScriptPath)) {
344
+ const targets = [];
345
+ if (installedTargets.includes("claudeCode-hook"))
346
+ targets.push({ surface: "claude-code", configPath: securityConfigPaths[0] });
347
+ if (installedTargets.includes("codex-hook"))
348
+ targets.push({ surface: "codex", configPath: securityConfigPaths[1] });
349
+ if (installedTargets.includes("cursor-hook"))
350
+ targets.push({ surface: "cursor", configPath: securityConfigPaths[2] });
351
+ if (installedTargets.includes("opencode-capture"))
352
+ targets.push({ surface: "codie-harness", configPath: securityConfigPaths[3] });
353
+ (0, securityInstall_1.writeSecurityInstallManifest)({ hookScriptPath, snapshot: securitySnapshot, targets });
354
+ }
355
+ }
356
+ catch (err) {
357
+ console.warn(`Warning: Security hooks installed, but integrity manifest could not be written — ${err.message}`);
358
+ }
359
+ // Prime the local cache after installation. Hooks read this file only, so
360
+ // an endpoint decision never depends on live network latency.
361
+ if (agentSecurityEnabled) {
362
+ try {
363
+ await (0, securityPolicy_1.syncEndpointSecurityPolicy)(config);
364
+ }
365
+ catch (err) {
366
+ console.warn(`Warning: Agent-security policy will use safe defaults until sync succeeds — ${err.message}`);
367
+ }
368
+ }
320
369
  // Tell the backend which targets we wired up so the portal's onboarding
321
370
  // card flips to "Connected" only when something actually installed —
322
371
  // not on bare token validation. Best-effort: a network failure here
@@ -355,5 +404,8 @@ async function runConnect(args = []) {
355
404
  }
356
405
  console.log();
357
406
  console.log("Run `codiedev doctor` to verify everything's wired up.");
407
+ if ((0, securityFeature_1.isAgentSecurityEnabled)(config)) {
408
+ console.log("Run `codiedev security pilot` to validate blocking safely.");
409
+ }
358
410
  console.log();
359
411
  }
@@ -1,9 +1,23 @@
1
- /** Normalize Cursor's native hook payload into the shared capture shape. */
1
+ /** Cursor's native hook payload is close to Claude Code's, but its stable
2
+ * conversation identifier and several tool fields use different names. Keep
3
+ * the translation at the process boundary so the shared capture/security
4
+ * pipeline never has to guess which surface produced an event. */
2
5
  export declare function normalizeCursorHookInput<T extends Record<string, unknown>>(input: T): T & Record<string, unknown>;
3
- /** Add Claude-compatible message discriminators while preserving Cursor JSONL. */
6
+ /** Cursor stores JSONL message roles in `role`; the shared capture pipeline
7
+ * uses Claude's top-level `type` discriminator. Preserve all original fields
8
+ * and add only that discriminator so the existing parsers see real messages. */
4
9
  export declare function normalizeCursorTranscript(content: string): string;
5
10
  /** Cursor may change its private JSONL record shape independently of the hook
6
11
  * contract. Only a byte-empty snapshot is safe to discard; a non-empty file
7
12
  * that the current normalizer cannot parse must still be uploaded for backend
8
13
  * recovery and diagnosis. */
9
14
  export declare function isCursorTranscriptEmpty(content: string): boolean;
15
+ export declare function cursorPreToolBlockResponse(reason: string): {
16
+ permission: "deny";
17
+ user_message: string;
18
+ agent_message: string;
19
+ };
20
+ export declare function cursorPromptBlockResponse(reason: string): {
21
+ continue: false;
22
+ user_message: string;
23
+ };
@@ -3,7 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.normalizeCursorHookInput = normalizeCursorHookInput;
4
4
  exports.normalizeCursorTranscript = normalizeCursorTranscript;
5
5
  exports.isCursorTranscriptEmpty = isCursorTranscriptEmpty;
6
- /** Normalize Cursor's native hook payload into the shared capture shape. */
6
+ exports.cursorPreToolBlockResponse = cursorPreToolBlockResponse;
7
+ exports.cursorPromptBlockResponse = cursorPromptBlockResponse;
8
+ /** Cursor's native hook payload is close to Claude Code's, but its stable
9
+ * conversation identifier and several tool fields use different names. Keep
10
+ * the translation at the process boundary so the shared capture/security
11
+ * pipeline never has to guess which surface produced an event. */
7
12
  function normalizeCursorHookInput(input) {
8
13
  const sessionId = firstString(input.session_id, input.sessionId, input.conversation_id);
9
14
  const roots = Array.isArray(input.workspace_roots)
@@ -24,22 +29,29 @@ function normalizeCursorHookInput(input) {
24
29
  ...(duration !== undefined ? { duration_ms: duration } : {}),
25
30
  };
26
31
  }
27
- /** Add Claude-compatible message discriminators while preserving Cursor JSONL. */
32
+ /** Cursor stores JSONL message roles in `role`; the shared capture pipeline
33
+ * uses Claude's top-level `type` discriminator. Preserve all original fields
34
+ * and add only that discriminator so the existing parsers see real messages. */
28
35
  function normalizeCursorTranscript(content) {
29
- return content.split("\n").map((line) => {
36
+ return content
37
+ .split("\n")
38
+ .map((line) => {
30
39
  if (!line.trim())
31
40
  return line;
32
41
  try {
33
42
  const entry = JSON.parse(line);
34
- if ((entry.role === "user" || entry.role === "assistant") && entry.type === undefined) {
43
+ if ((entry.role === "user" || entry.role === "assistant") &&
44
+ entry.type === undefined) {
35
45
  return JSON.stringify({ ...entry, type: entry.role });
36
46
  }
37
47
  }
38
48
  catch {
39
- // Tolerant downstream parsers skip malformed/non-JSON lines.
49
+ // Preserve malformed/non-JSON lines; tolerant downstream parsers skip
50
+ // anything they cannot consume.
40
51
  }
41
52
  return line;
42
- }).join("\n");
53
+ })
54
+ .join("\n");
43
55
  }
44
56
  /** Cursor may change its private JSONL record shape independently of the hook
45
57
  * contract. Only a byte-empty snapshot is safe to discard; a non-empty file
@@ -54,3 +66,16 @@ function firstString(...values) {
54
66
  function firstNumber(...values) {
55
67
  return values.find((value) => typeof value === "number" && Number.isFinite(value));
56
68
  }
69
+ function cursorPreToolBlockResponse(reason) {
70
+ return {
71
+ permission: "deny",
72
+ user_message: reason,
73
+ agent_message: reason,
74
+ };
75
+ }
76
+ function cursorPromptBlockResponse(reason) {
77
+ return {
78
+ continue: false,
79
+ user_message: reason,
80
+ };
81
+ }