codiedev 0.9.36 → 0.9.38

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.
@@ -46,6 +46,7 @@ const upgradeNudge_1 = require("../upgradeNudge");
46
46
  const captureReadiness_1 = require("../captureReadiness");
47
47
  const captureLog_1 = require("../captureLog");
48
48
  const securityDoctor_1 = require("../securityDoctor");
49
+ const securityFeature_1 = require("../securityFeature");
49
50
  const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json");
50
51
  const CLAUDE_INSTRUCTIONS_PATH = path.join(os.homedir(), ".claude", "CLAUDE.md");
51
52
  const CODEX_HOOKS_PATH = path.join(os.homedir(), ".codex", "hooks.json");
@@ -486,7 +487,9 @@ async function runDoctor(_args) {
486
487
  // Agent Security is part of installation health, not a separate setup
487
488
  // concept. Keep `codiedev security doctor` as an alias for focused output,
488
489
  // while the main doctor remains the one command users need to remember.
489
- checks.push(...(0, securityDoctor_1.securityDoctorChecks)((0, securityDoctor_1.collectSecurityDoctorReport)()));
490
+ if ((0, securityFeature_1.isAgentSecurityEnabled)(config)) {
491
+ checks.push(...(0, securityDoctor_1.securityDoctorChecks)((0, securityDoctor_1.collectSecurityDoctorReport)(config.companyId)));
492
+ }
490
493
  // 8. gh / glab CLIs (optional — only needed for reverse-ticket from a
491
494
  // specific PR/MR URL; branch mode works without either).
492
495
  checks.push({
@@ -8,6 +8,7 @@ const securityInventory_1 = require("../securityInventory");
8
8
  const securityDeepScan_1 = require("../securityDeepScan");
9
9
  const utils_1 = require("../utils");
10
10
  const securityDoctor_1 = require("../securityDoctor");
11
+ const securityFeature_1 = require("../securityFeature");
11
12
  function printPilot(json) {
12
13
  const report = (0, securityPilot_1.runSyntheticSecurityPilot)();
13
14
  if (json) {
@@ -22,8 +23,8 @@ function printPilot(json) {
22
23
  console.log("No commands were executed and no real credentials were used.\n");
23
24
  return report.ok;
24
25
  }
25
- function printDoctor(json) {
26
- const result = (0, securityDoctor_1.collectSecurityDoctorReport)();
26
+ function printDoctor(json, companyId) {
27
+ const result = (0, securityDoctor_1.collectSecurityDoctorReport)(companyId);
27
28
  if (json) {
28
29
  console.log(JSON.stringify(result, null, 2));
29
30
  return result.ok;
@@ -39,6 +40,18 @@ function printDoctor(json) {
39
40
  async function runSecurityCommand(args) {
40
41
  const subcommand = args.find((arg) => !arg.startsWith("-")) ?? "doctor";
41
42
  const json = args.includes("--json");
43
+ const connectedConfig = (0, utils_1.readConfig)();
44
+ const requiresEntitlement = !["pilot", "self-test"].includes(subcommand);
45
+ if (requiresEntitlement && (!connectedConfig || !(0, securityFeature_1.isAgentSecurityEnabled)(connectedConfig))) {
46
+ const detail = connectedConfig
47
+ ? `Agent Security is not enabled for ${connectedConfig.companyName}.`
48
+ : "Not connected. Run `codiedev connect` first.";
49
+ if (json)
50
+ console.log(JSON.stringify({ schemaVersion: 1, enabled: false, detail }, null, 2));
51
+ else
52
+ console.log(`\n${detail}\n`);
53
+ return;
54
+ }
42
55
  if (subcommand === "inventory" || subcommand === "scan") {
43
56
  const workspaceFlag = args.indexOf("--workspace");
44
57
  const workspace = workspaceFlag >= 0 && args[workspaceFlag + 1]
@@ -128,7 +141,7 @@ async function runSecurityCommand(args) {
128
141
  const ok = subcommand === "pilot" || subcommand === "self-test"
129
142
  ? printPilot(json)
130
143
  : subcommand === "doctor" || subcommand === "status"
131
- ? printDoctor(json)
144
+ ? printDoctor(json, connectedConfig.companyId)
132
145
  : false;
133
146
  if (!["pilot", "self-test", "doctor", "status"].includes(subcommand)) {
134
147
  console.error("Usage: codiedev security <doctor|pilot|inventory|scan|sync|rollback> [--json] [--deep] [--connect-mcp] [--supply-chain] [--no-upload] [--yes]");
package/dist/connect.js CHANGED
@@ -48,6 +48,7 @@ const upgradeNudge_1 = require("./upgradeNudge");
48
48
  const shared_1 = require("./commands/shared");
49
49
  const securityInstall_1 = require("./securityInstall");
50
50
  const securityPolicy_1 = require("./securityPolicy");
51
+ const securityFeature_1 = require("./securityFeature");
51
52
  // cli.codiedev.com is a Convex custom domain on the prod deployment; the
52
53
  // underlying judicious-falcon-861.convex.site URL keeps serving, so configs
53
54
  // written by older CLI versions stay valid without a re-connect.
@@ -204,6 +205,7 @@ async function runConnect(args = []) {
204
205
  // Extract fields from response
205
206
  const companyId = responseData.companyId;
206
207
  const companyName = responseData.companyName;
208
+ const agentSecurityEnabled = responseData.agentSecurityEnabled === true;
207
209
  const repos = responseData.repos ?? [];
208
210
  if (!companyId || !companyName) {
209
211
  console.error("\nError: Invalid response from server. Token may be invalid.");
@@ -216,6 +218,7 @@ async function runConnect(args = []) {
216
218
  repos,
217
219
  lastSynced: new Date().toISOString(),
218
220
  backendUrl: BACKEND_URL,
221
+ agentSecurityEnabled,
219
222
  deviceId: saved?.deviceId || crypto.randomUUID(),
220
223
  deviceName: saved?.deviceName || os.hostname(),
221
224
  captureIgnoredPaths: saved?.captureIgnoredPaths ?? [],
@@ -315,11 +318,11 @@ async function runConnect(args = []) {
315
318
  }
316
319
  };
317
320
  if (hasClaude) {
318
- runInstall("claude-code-hook", utils_1.installHook, "Claude Code SessionEnd (capture) + UserPromptSubmit (skill discovery) + Stop (auto-backup) hooks (~/.claude/settings.json)", "claudeCode-hook");
321
+ 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");
319
322
  runInstall("claude-code-instructions", utils_1.installClaudeCodeInstructions, "Claude Code agent instructions (~/.claude/CLAUDE.md)", "claudeCode-instructions");
320
323
  }
321
324
  if (hasCodex) {
322
- runInstall("codex-hook", utils_1.installCodexHook, "Codex Stop (capture + auto-backup) hooks (~/.codex/hooks.json)", "codex-hook");
325
+ runInstall("codex-hook", () => (0, utils_1.installCodexHook)(agentSecurityEnabled), "Codex Stop (capture + auto-backup) hooks (~/.codex/hooks.json)", "codex-hook");
323
326
  runInstall("codex-instructions", utils_1.installCodexInstructions, "Codex agent instructions (~/.codex/AGENTS.md)", "codex-instructions");
324
327
  }
325
328
  if (hasCursor) {
@@ -329,13 +332,13 @@ async function runConnect(args = []) {
329
332
  // Always stage the adapter. It is dormant until OpenCode starts, which lets
330
333
  // a user connect Claude Code today and install the Codie harness tomorrow
331
334
  // without needing a second setup ritual.
332
- runInstall("opencode-capture-adapter", utils_1.installOpenCodeCapturePlugin, "OpenCode session capture adapter (~/.config/opencode/plugins/codiedev.js)", "opencode-capture");
335
+ runInstall("opencode-capture-adapter", () => (0, utils_1.installOpenCodeCapturePlugin)(agentSecurityEnabled), "OpenCode session capture adapter (~/.config/opencode/plugins/codiedev.js)", "opencode-capture");
333
336
  // Record the exact installed hook bytes and the pre-update rollback files.
334
337
  // This supplies health/integrity checks without requiring a package publish
335
338
  // or trusting whichever `node` happens to be first on PATH later.
336
339
  try {
337
340
  const hookScriptPath = path.join(__dirname, "hook.js");
338
- if (fs.existsSync(hookScriptPath)) {
341
+ if (agentSecurityEnabled && fs.existsSync(hookScriptPath)) {
339
342
  const targets = [];
340
343
  if (installedTargets.includes("claudeCode-hook"))
341
344
  targets.push({ surface: "claude-code", configPath: securityConfigPaths[0] });
@@ -351,11 +354,13 @@ async function runConnect(args = []) {
351
354
  }
352
355
  // Prime the local cache after installation. Hooks read this file only, so
353
356
  // an endpoint decision never depends on live network latency.
354
- try {
355
- await (0, securityPolicy_1.syncEndpointSecurityPolicy)(config);
356
- }
357
- catch (err) {
358
- console.warn(`Warning: Agent-security policy will use safe defaults until sync succeeds — ${err.message}`);
357
+ if (agentSecurityEnabled) {
358
+ try {
359
+ await (0, securityPolicy_1.syncEndpointSecurityPolicy)(config);
360
+ }
361
+ catch (err) {
362
+ console.warn(`Warning: Agent-security policy will use safe defaults until sync succeeds — ${err.message}`);
363
+ }
359
364
  }
360
365
  // Tell the backend which targets we wired up so the portal's onboarding
361
366
  // card flips to "Connected" only when something actually installed —
@@ -395,6 +400,8 @@ async function runConnect(args = []) {
395
400
  }
396
401
  console.log();
397
402
  console.log("Run `codiedev doctor` to verify everything's wired up.");
398
- console.log("Run `codiedev security pilot` to validate blocking safely.");
403
+ if ((0, securityFeature_1.isAgentSecurityEnabled)(config)) {
404
+ console.log("Run `codiedev security pilot` to validate blocking safely.");
405
+ }
399
406
  console.log();
400
407
  }
package/dist/hook.js CHANGED
@@ -48,6 +48,7 @@ const captureLog_1 = require("./captureLog");
48
48
  const security_1 = require("./security");
49
49
  const securitySession_1 = require("./securitySession");
50
50
  const securityPolicy_1 = require("./securityPolicy");
51
+ const securityFeature_1 = require("./securityFeature");
51
52
  const securityInventory_1 = require("./securityInventory");
52
53
  const captureExclusions_1 = require("./captureExclusions");
53
54
  // Pure builder for the discovery additionalContext. Returns null when there are
@@ -269,9 +270,13 @@ function postJsonForResponse(url, body, bearerToken, timeoutMs) {
269
270
  // is dispatched separately and fail-open, so a detector outage or timeout can
270
271
  // never delay or override the deterministic endpoint decision.
271
272
  async function runSecurityHook(hookInput, phase, source) {
273
+ const config = (0, utils_1.readConfig)();
274
+ if (!config || !(0, securityFeature_1.isAgentSecurityEnabled)(config))
275
+ return;
276
+ const policy = (0, securityPolicy_1.readEndpointSecurityPolicy)(undefined, undefined, config.companyId);
272
277
  const sessionId = hookInput.session_id ?? hookInput.sessionId;
273
278
  const sessionSignal = phase === "pre" ? (0, securitySession_1.readSessionSecuritySignal)(sessionId) : undefined;
274
- const localDecision = (0, security_1.evaluateToolSecurity)(hookInput, phase, (0, securityPolicy_1.readEndpointSecurityPolicy)(), source, sessionSignal);
279
+ const localDecision = (0, security_1.evaluateToolSecurity)(hookInput, phase, policy, source, sessionSignal);
275
280
  if (phase === "post" && (0, security_1.securityInputHasSensitivePath)(hookInput)) {
276
281
  (0, securitySession_1.recordSensitiveReadSignal)(sessionId);
277
282
  }
@@ -302,7 +307,10 @@ async function runSecurityHook(hookInput, phase, source) {
302
307
  dispatchSecurityEventUpload(event);
303
308
  }
304
309
  function runPromptSecurityHook(hookInput, source) {
305
- const decision = (0, security_1.evaluatePromptSecurity)(hookInput.prompt, (0, securityPolicy_1.readEndpointSecurityPolicy)(), source);
310
+ const config = (0, utils_1.readConfig)();
311
+ if (!config || !(0, securityFeature_1.isAgentSecurityEnabled)(config))
312
+ return false;
313
+ const decision = (0, security_1.evaluatePromptSecurity)(hookInput.prompt, (0, securityPolicy_1.readEndpointSecurityPolicy)(undefined, undefined, config.companyId), source);
306
314
  if (decision.action !== "block")
307
315
  return false;
308
316
  dispatchSecurityEventUpload((0, security_1.buildBlockedSecurityPromptEvent)(hookInput, source, decision.category ?? "secret_in_prompt"));
@@ -313,12 +321,15 @@ function runPromptSecurityHook(hookInput, source) {
313
321
  return true;
314
322
  }
315
323
  function dispatchAllowedPromptSecurityEvent(hookInput, source) {
316
- const event = (0, security_1.buildSecurityPromptEvent)(hookInput, source, Date.now, (0, securityPolicy_1.readEndpointSecurityPolicy)());
324
+ const config = (0, utils_1.readConfig)();
325
+ if (!config || !(0, securityFeature_1.isAgentSecurityEnabled)(config))
326
+ return;
327
+ const event = (0, security_1.buildSecurityPromptEvent)(hookInput, source, Date.now, (0, securityPolicy_1.readEndpointSecurityPolicy)(undefined, undefined, config.companyId));
317
328
  if (event)
318
329
  dispatchSecurityEventUpload(event);
319
330
  }
320
331
  function dispatchSecurityEventUpload(event) {
321
- if (!event || !(0, utils_1.readConfig)())
332
+ if (!event || !(0, securityFeature_1.isAgentSecurityEnabled)((0, utils_1.readConfig)()))
322
333
  return;
323
334
  try {
324
335
  const child = (0, child_process_1.spawn)(process.execPath, [process.argv[1], "security-upload"], {
@@ -334,7 +345,7 @@ function dispatchSecurityEventUpload(event) {
334
345
  }
335
346
  async function runSecurityEventUpload() {
336
347
  const config = (0, utils_1.readConfig)();
337
- if (!config)
348
+ if (!config || !(0, securityFeature_1.isAgentSecurityEnabled)(config))
338
349
  return;
339
350
  const startedAt = Date.now();
340
351
  try {
@@ -346,8 +357,9 @@ async function runSecurityEventUpload() {
346
357
  cliVersion: version_1.CLI_VERSION,
347
358
  });
348
359
  const result = await postJsonForResponse(`${config.backendUrl}/api/cli/securityEvent`, { ...eventWithEndpoint }, config.token, 1500);
349
- if (result?.policy?.schemaVersion === 1)
350
- (0, securityPolicy_1.writeEndpointSecurityPolicy)(result.policy);
360
+ if (result?.policy?.schemaVersion === 1) {
361
+ (0, securityPolicy_1.writeEndpointSecurityPolicy)(result.policy, undefined, undefined, config.companyId);
362
+ }
351
363
  (0, captureLog_1.appendCaptureLog)({
352
364
  stage: "security",
353
365
  sessionId: event.sessionId,
@@ -492,7 +504,7 @@ async function main() {
492
504
  // waits for filesystem traversal or an inventory upload.
493
505
  if (process.argv[2] === "security-inventory-upload") {
494
506
  const config = (0, utils_1.readConfig)();
495
- if (config) {
507
+ if (config && (0, securityFeature_1.isAgentSecurityEnabled)(config)) {
496
508
  const inventory = (0, securityInventory_1.buildSecurityInventory)({ workspace: process.argv[3] || process.cwd() });
497
509
  try {
498
510
  await (0, securityInventory_1.uploadSecurityInventory)(inventory, config);
@@ -597,7 +609,7 @@ async function main() {
597
609
  }
598
610
  // SessionStart: post the capture-coverage denominator ping. Tiny + fast.
599
611
  if (process.argv[2] === "session-start" || process.argv[2] === "session-start-opencode") {
600
- if ((0, securityInventory_1.securityInventoryDue)()) {
612
+ if ((0, securityFeature_1.isAgentSecurityEnabled)((0, utils_1.readConfig)()) && (0, securityInventory_1.securityInventoryDue)()) {
601
613
  try {
602
614
  const child = (0, child_process_1.spawn)(process.execPath, [process.argv[1], "security-inventory-upload", hookInput.cwd || process.cwd()], {
603
615
  detached: true,
@@ -27,6 +27,6 @@ export interface SecurityDoctorReport {
27
27
  };
28
28
  }
29
29
  /** One security-health model shared by `doctor` and its legacy alias. */
30
- export declare function collectSecurityDoctorReport(): SecurityDoctorReport;
30
+ export declare function collectSecurityDoctorReport(companyId?: string): SecurityDoctorReport;
31
31
  /** Convert security health into the same compact rows used by core doctor. */
32
32
  export declare function securityDoctorChecks(report: SecurityDoctorReport): SecurityDoctorCheck[];
@@ -6,12 +6,12 @@ const securityInstall_1 = require("./securityInstall");
6
6
  const securityPilot_1 = require("./securityPilot");
7
7
  const securityPolicy_1 = require("./securityPolicy");
8
8
  /** One security-health model shared by `doctor` and its legacy alias. */
9
- function collectSecurityDoctorReport() {
9
+ function collectSecurityDoctorReport(companyId) {
10
10
  const manifest = (0, securityInstall_1.readSecurityInstallManifest)();
11
11
  const integrity = (0, securityInstall_1.verifySecurityInstallManifest)(manifest);
12
12
  const installations = (0, securityInstall_1.inspectSecurityInstallation)();
13
13
  const pilot = (0, securityPilot_1.runSyntheticSecurityPilot)();
14
- const policy = (0, securityPolicy_1.inspectEndpointSecurityPolicy)();
14
+ const policy = (0, securityPolicy_1.inspectEndpointSecurityPolicy)(undefined, undefined, companyId);
15
15
  const ok = integrity.ok &&
16
16
  policy.verification.ok &&
17
17
  pilot.ok &&
@@ -0,0 +1,5 @@
1
+ export interface AgentSecurityEntitlement {
2
+ agentSecurityEnabled?: boolean;
3
+ }
4
+ /** Security is opt-in per company. Missing/stale entitlement data is dormant. */
5
+ export declare function isAgentSecurityEnabled(config: AgentSecurityEntitlement | null | undefined): boolean;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isAgentSecurityEnabled = isAgentSecurityEnabled;
4
+ /** Security is opt-in per company. Missing/stale entitlement data is dormant. */
5
+ function isAgentSecurityEnabled(config) {
6
+ return config?.agentSecurityEnabled === true;
7
+ }
@@ -177,7 +177,7 @@ function inspectJsonSurface(surface, filePath) {
177
177
  const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
178
178
  const suffix = surface === "codex" ? "-codex" : "";
179
179
  const expected = [
180
- ["UserPromptSubmit", "discover"],
180
+ ["UserPromptSubmit", `discover${suffix}`],
181
181
  ["PreToolUse", `security-pre${suffix}`],
182
182
  ["PostToolUse", `security-post${suffix}`],
183
183
  ["PostToolUseFailure", `security-failure${suffix}`],
@@ -6,8 +6,8 @@ export declare function verifyEndpointSecurityPolicySignature(policy: EndpointSe
6
6
  trusted: boolean;
7
7
  detail: string;
8
8
  };
9
- export declare function readEndpointSecurityPolicy(stateDir?: string, trustedKeys?: TrustedPolicyKeys): EndpointSecurityPolicy | undefined;
10
- export declare function inspectEndpointSecurityPolicy(stateDir?: string, trustedKeys?: TrustedPolicyKeys): {
9
+ export declare function readEndpointSecurityPolicy(stateDir?: string, trustedKeys?: TrustedPolicyKeys, companyId?: string): EndpointSecurityPolicy | undefined;
10
+ export declare function inspectEndpointSecurityPolicy(stateDir?: string, trustedKeys?: TrustedPolicyKeys, companyId?: string): {
11
11
  exists: boolean;
12
12
  policy?: EndpointSecurityPolicy;
13
13
  verification: {
@@ -16,8 +16,9 @@ export declare function inspectEndpointSecurityPolicy(stateDir?: string, trusted
16
16
  detail: string;
17
17
  };
18
18
  };
19
- export declare function writeEndpointSecurityPolicy(policy: EndpointSecurityPolicy, stateDir?: string, trustedKeys?: TrustedPolicyKeys): void;
19
+ export declare function writeEndpointSecurityPolicy(policy: EndpointSecurityPolicy, stateDir?: string, trustedKeys?: TrustedPolicyKeys, companyId?: string): void;
20
20
  export declare function syncEndpointSecurityPolicy(config: {
21
21
  backendUrl: string;
22
22
  token: string;
23
+ companyId: string;
23
24
  }, source?: AgentSurface, timeoutMs?: number): Promise<EndpointSecurityPolicy>;
@@ -46,6 +46,12 @@ const https = __importStar(require("https"));
46
46
  const path = __importStar(require("path"));
47
47
  const configDir_1 = require("./configDir");
48
48
  const POLICY_FILE = "security-policy.json";
49
+ function policyFile(companyId) {
50
+ if (!companyId)
51
+ return POLICY_FILE;
52
+ const scope = companyId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128);
53
+ return `security-policy.${scope}.json`;
54
+ }
49
55
  function stableJson(value) {
50
56
  if (Array.isArray(value))
51
57
  return `[${value.map(stableJson).join(",")}]`;
@@ -96,12 +102,12 @@ function verifyEndpointSecurityPolicySignature(policy, trustedKeys = trustedPoli
96
102
  return { ok: false, trusted: false, detail: `policy signature could not be verified: ${error.message}` };
97
103
  }
98
104
  }
99
- function readEndpointSecurityPolicy(stateDir = (0, configDir_1.codiedevStateDir)(), trustedKeys = trustedPolicyKeysFromEnvironment()) {
100
- const inspection = inspectEndpointSecurityPolicy(stateDir, trustedKeys);
105
+ function readEndpointSecurityPolicy(stateDir = (0, configDir_1.codiedevStateDir)(), trustedKeys = trustedPolicyKeysFromEnvironment(), companyId) {
106
+ const inspection = inspectEndpointSecurityPolicy(stateDir, trustedKeys, companyId);
101
107
  return inspection.verification.ok ? inspection.policy : undefined;
102
108
  }
103
- function inspectEndpointSecurityPolicy(stateDir = (0, configDir_1.codiedevStateDir)(), trustedKeys = trustedPolicyKeysFromEnvironment()) {
104
- const target = path.join(stateDir, POLICY_FILE);
109
+ function inspectEndpointSecurityPolicy(stateDir = (0, configDir_1.codiedevStateDir)(), trustedKeys = trustedPolicyKeysFromEnvironment(), companyId) {
110
+ const target = path.join(stateDir, policyFile(companyId));
105
111
  if (!fs.existsSync(target)) {
106
112
  return { exists: false, verification: { ok: true, trusted: false, detail: "no cached endpoint policy" } };
107
113
  }
@@ -117,12 +123,12 @@ function inspectEndpointSecurityPolicy(stateDir = (0, configDir_1.codiedevStateD
117
123
  return { exists: true, verification: { ok: false, trusted: false, detail: `cached endpoint policy could not be read: ${error.message}` } };
118
124
  }
119
125
  }
120
- function writeEndpointSecurityPolicy(policy, stateDir = (0, configDir_1.codiedevStateDir)(), trustedKeys = trustedPolicyKeysFromEnvironment()) {
126
+ function writeEndpointSecurityPolicy(policy, stateDir = (0, configDir_1.codiedevStateDir)(), trustedKeys = trustedPolicyKeysFromEnvironment(), companyId) {
121
127
  const verification = verifyEndpointSecurityPolicySignature(policy, trustedKeys);
122
128
  if (!verification.ok)
123
129
  throw new Error(`Refusing untrusted endpoint policy: ${verification.detail}`);
124
130
  fs.mkdirSync(stateDir, { recursive: true, mode: 0o700 });
125
- const target = path.join(stateDir, POLICY_FILE);
131
+ const target = path.join(stateDir, policyFile(companyId));
126
132
  const temporary = `${target}.${process.pid}.tmp`;
127
133
  fs.writeFileSync(temporary, `${JSON.stringify(policy, null, 2)}\n`, { mode: 0o600 });
128
134
  fs.chmodSync(temporary, 0o600);
@@ -156,6 +162,6 @@ async function syncEndpointSecurityPolicy(config, source, timeoutMs = 3_000) {
156
162
  request.on("error", reject);
157
163
  request.end();
158
164
  });
159
- writeEndpointSecurityPolicy(policy);
165
+ writeEndpointSecurityPolicy(policy, undefined, undefined, config.companyId);
160
166
  return policy;
161
167
  }
package/dist/utils.d.ts CHANGED
@@ -14,6 +14,7 @@ export interface CodiedevConfig {
14
14
  }>;
15
15
  lastSynced: string;
16
16
  backendUrl: string;
17
+ agentSecurityEnabled?: boolean;
17
18
  deviceId?: string;
18
19
  deviceName?: string;
19
20
  captureIgnoredPaths?: string[];
@@ -96,7 +97,7 @@ export declare function openCodeInstalled(): boolean;
96
97
  /** Install a dormant global OpenCode adapter. Installing it unconditionally
97
98
  * keeps `codiedev connect` as the only setup step even when Codie/OpenCode is
98
99
  * installed later. OpenCode loads this file from its documented plugin dir. */
99
- export declare function installOpenCodeCapturePlugin(): void;
100
+ export declare function installOpenCodeCapturePlugin(agentSecurityEnabled?: boolean): void;
100
101
  /**
101
102
  * VS Code with the GitHub Copilot Chat extension installed. The extension
102
103
  * directory pattern is `~/.vscode/extensions/github.copilot-chat-*`. We treat
@@ -104,11 +105,13 @@ export declare function installOpenCodeCapturePlugin(): void;
104
105
  */
105
106
  export declare function vscodeCopilotInstalled(): boolean;
106
107
  export declare function upsertCodiedevHook(hooks: Record<string, unknown>, eventName: string, subcommand: string, matcher?: string): void;
107
- export declare function installHook(): void;
108
+ /** Remove Codiedev's entries for one event while preserving foreign hooks. */
109
+ export declare function removeCodiedevHook(hooks: Record<string, unknown>, eventName: string): boolean;
110
+ export declare function installHook(agentSecurityEnabled?: boolean): void;
108
111
  export declare function ensureDiscoveryHookInstalled(): void;
109
112
  export declare function ensureBackupHookInstalled(): void;
110
113
  export declare function ensureLimitHookInstalled(): void;
111
- export declare function ensureAllCodiedevHooksInstalled(): void;
114
+ export declare function ensureAllCodiedevHooksInstalled(agentSecurityEnabled?: boolean): void;
112
115
  export declare function ensureSessionStartHookInstalled(): void;
113
116
  export declare function installClaudeCodeInstructions(): void;
114
117
  export declare function installCodexInstructions(): void;
@@ -166,7 +169,7 @@ export declare function cleanupCodexMcp(): void;
166
169
  * so we append a well-marked block if one isn't already present. Idempotent.
167
170
  */
168
171
  export declare function installCodexMcp(): boolean;
169
- export declare function installCodexHook(): void;
172
+ export declare function installCodexHook(agentSecurityEnabled?: boolean): void;
170
173
  export declare function installCursorHook(): void;
171
174
  export interface ParsedStats {
172
175
  messageCount: number;
package/dist/utils.js CHANGED
@@ -64,6 +64,7 @@ exports.openCodeInstalled = openCodeInstalled;
64
64
  exports.installOpenCodeCapturePlugin = installOpenCodeCapturePlugin;
65
65
  exports.vscodeCopilotInstalled = vscodeCopilotInstalled;
66
66
  exports.upsertCodiedevHook = upsertCodiedevHook;
67
+ exports.removeCodiedevHook = removeCodiedevHook;
67
68
  exports.installHook = installHook;
68
69
  exports.ensureDiscoveryHookInstalled = ensureDiscoveryHookInstalled;
69
70
  exports.ensureBackupHookInstalled = ensureBackupHookInstalled;
@@ -1050,7 +1051,7 @@ function openCodeInstalled() {
1050
1051
  /** Install a dormant global OpenCode adapter. Installing it unconditionally
1051
1052
  * keeps `codiedev connect` as the only setup step even when Codie/OpenCode is
1052
1053
  * installed later. OpenCode loads this file from its documented plugin dir. */
1053
- function installOpenCodeCapturePlugin() {
1054
+ function installOpenCodeCapturePlugin(agentSecurityEnabled = true) {
1054
1055
  const start = resolveHookInvocation("session-start-opencode");
1055
1056
  const capture = resolveHookInvocation("capture-opencode");
1056
1057
  const finish = resolveHookInvocation("capture-opencode-final");
@@ -1072,6 +1073,7 @@ function installOpenCodeCapturePlugin() {
1072
1073
  `const SECURITY_PRE = ${JSON.stringify(securityPre)};\n` +
1073
1074
  `const SECURITY_POST = ${JSON.stringify(securityPost)};\n` +
1074
1075
  `const SECURITY_FAILURE = ${JSON.stringify(securityFailure)};\n` +
1076
+ `const AGENT_SECURITY_ENABLED = ${JSON.stringify(agentSecurityEnabled)};\n` +
1075
1077
  `const fingerprints = new Map();\n\n` +
1076
1078
  `const transcriptPaths = new Map();\n\n` +
1077
1079
  `function sessionId(event) {\n` +
@@ -1115,10 +1117,12 @@ function installOpenCodeCapturePlugin() {
1115
1117
  ` enforceSync(PROMPT, { sessionId: id, cwd, prompt });\n` +
1116
1118
  ` },\n` +
1117
1119
  ` "tool.execute.before": async (input, output) => {\n` +
1120
+ ` if (!AGENT_SECURITY_ENABLED) return;\n` +
1118
1121
  ` const cwd = worktree || directory || process.cwd();\n` +
1119
1122
  ` enforceSync(SECURITY_PRE, { sessionId: input?.sessionID ?? input?.sessionId ?? "unknown", cwd, toolName: input?.tool ?? "unknown", toolUseId: input?.callID, toolInput: output?.args ?? output });\n` +
1120
1123
  ` },\n` +
1121
1124
  ` "tool.execute.after": async (input, output) => {\n` +
1125
+ ` if (!AGENT_SECURITY_ENABLED) return;\n` +
1122
1126
  ` const cwd = worktree || directory || process.cwd();\n` +
1123
1127
  ` const payload = { sessionId: input?.sessionID ?? input?.sessionId ?? "unknown", cwd, toolName: input?.tool ?? "unknown", toolUseId: input?.callID, toolInput: input?.args, toolResponse: output };\n` +
1124
1128
  ` dispatch(output?.error ? SECURITY_FAILURE : SECURITY_POST, payload);\n` +
@@ -1217,7 +1221,36 @@ function upsertCodiedevHook(hooks, eventName, subcommand, matcher = ".*") {
1217
1221
  });
1218
1222
  hooks[eventName] = filtered;
1219
1223
  }
1220
- function installHook() {
1224
+ /** Remove Codiedev's entries for one event while preserving foreign hooks. */
1225
+ function removeCodiedevHook(hooks, eventName) {
1226
+ const entries = hooks[eventName];
1227
+ if (!Array.isArray(entries))
1228
+ return false;
1229
+ let changed = false;
1230
+ const filtered = entries.flatMap((entry) => {
1231
+ const record = entry;
1232
+ const inner = record.hooks;
1233
+ if (!Array.isArray(inner))
1234
+ return [record];
1235
+ const remaining = inner.filter((hook) => {
1236
+ const remove = (0, detection_1.isCodiedevHookCommand)(hook.command);
1237
+ if (remove)
1238
+ changed = true;
1239
+ return !remove;
1240
+ });
1241
+ if (remaining.length === 0)
1242
+ return [];
1243
+ return [{ ...record, hooks: remaining }];
1244
+ });
1245
+ if (!changed)
1246
+ return false;
1247
+ if (filtered.length === 0)
1248
+ delete hooks[eventName];
1249
+ else
1250
+ hooks[eventName] = filtered;
1251
+ return true;
1252
+ }
1253
+ function installHook(agentSecurityEnabled = true) {
1221
1254
  let settings = {};
1222
1255
  try {
1223
1256
  if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
@@ -1236,9 +1269,16 @@ function installHook() {
1236
1269
  upsertCodiedevHook(hooks, "SessionEnd", "capture");
1237
1270
  // Tool boundaries → ADR shadow detection. PreToolUse is the eventual
1238
1271
  // enforcement point; alert mode always returns allow.
1239
- upsertCodiedevHook(hooks, "PreToolUse", "security-pre");
1240
- upsertCodiedevHook(hooks, "PostToolUse", "security-post");
1241
- upsertCodiedevHook(hooks, "PostToolUseFailure", "security-failure");
1272
+ if (agentSecurityEnabled) {
1273
+ upsertCodiedevHook(hooks, "PreToolUse", "security-pre");
1274
+ upsertCodiedevHook(hooks, "PostToolUse", "security-post");
1275
+ upsertCodiedevHook(hooks, "PostToolUseFailure", "security-failure");
1276
+ }
1277
+ else {
1278
+ removeCodiedevHook(hooks, "PreToolUse");
1279
+ removeCodiedevHook(hooks, "PostToolUse");
1280
+ removeCodiedevHook(hooks, "PostToolUseFailure");
1281
+ }
1242
1282
  // UserPromptSubmit → discover (surface a matching team skill for the task).
1243
1283
  upsertCodiedevHook(hooks, "UserPromptSubmit", "discover");
1244
1284
  // Stop → backup (auto-back-up changed artifact files to your private library).
@@ -1340,7 +1380,7 @@ function ensureLimitHookInstalled() {
1340
1380
  // shrinks that window and removes the intra-process version entirely.
1341
1381
  // The per-hook ensure* functions below are kept for tests/back-compat but are
1342
1382
  // no longer called from the hook entrypoint.
1343
- function ensureAllCodiedevHooksInstalled() {
1383
+ function ensureAllCodiedevHooksInstalled(agentSecurityEnabled = readConfig()?.agentSecurityEnabled === true) {
1344
1384
  try {
1345
1385
  if (!fs.existsSync(CLAUDE_SETTINGS_PATH))
1346
1386
  return; // not a Claude Code user
@@ -1348,16 +1388,22 @@ function ensureAllCodiedevHooksInstalled() {
1348
1388
  const hooks = (settings.hooks ?? {});
1349
1389
  const wanted = [
1350
1390
  ["SessionEnd", "capture"],
1351
- ["PreToolUse", "security-pre"],
1352
- ["PostToolUse", "security-post"],
1353
- ["PostToolUseFailure", "security-failure"],
1354
1391
  ["UserPromptSubmit", "discover"],
1355
1392
  ["Stop", "backup"],
1356
1393
  ["StopFailure", "limit", "rate_limit"],
1357
1394
  ["SessionStart", "session-start"],
1358
1395
  ];
1396
+ if (agentSecurityEnabled) {
1397
+ wanted.splice(1, 0, ["PreToolUse", "security-pre"], ["PostToolUse", "security-post"], ["PostToolUseFailure", "security-failure"]);
1398
+ }
1399
+ let changed = false;
1400
+ if (!agentSecurityEnabled) {
1401
+ for (const eventName of ["PreToolUse", "PostToolUse", "PostToolUseFailure"]) {
1402
+ changed = removeCodiedevHook(hooks, eventName) || changed;
1403
+ }
1404
+ }
1359
1405
  const missing = wanted.filter(([eventName]) => !hasCodiedevHook(hooks, eventName));
1360
- if (missing.length === 0)
1406
+ if (missing.length === 0 && !changed)
1361
1407
  return; // steady state: no write, no churn
1362
1408
  settings.hooks = hooks;
1363
1409
  for (const [eventName, subcommand, matcher] of missing) {
@@ -1798,7 +1844,7 @@ function installCodexMcp() {
1798
1844
  // Codex doesn't have a SessionEnd event yet — Stop fires after every turn.
1799
1845
  // The backend dedupes on (companyId, sessionId) and replaces the stored
1800
1846
  // transcript on re-upload, so the latest turn's snapshot wins.
1801
- function installCodexHook() {
1847
+ function installCodexHook(agentSecurityEnabled = true) {
1802
1848
  let hooksFile = {};
1803
1849
  try {
1804
1850
  if (fs.existsSync(CODEX_HOOKS_PATH)) {
@@ -1845,9 +1891,16 @@ function installCodexHook() {
1845
1891
  // Codex uses the same Claude-style matcher-group shape. Keep these as
1846
1892
  // separate entries because Codex hook coverage is still evolving and a
1847
1893
  // malformed/unavailable post hook must not interfere with PreToolUse.
1848
- upsertCodiedevHook(hooks, "PreToolUse", "security-pre-codex");
1849
- upsertCodiedevHook(hooks, "PostToolUse", "security-post-codex");
1850
- upsertCodiedevHook(hooks, "PostToolUseFailure", "security-failure-codex");
1894
+ if (agentSecurityEnabled) {
1895
+ upsertCodiedevHook(hooks, "PreToolUse", "security-pre-codex");
1896
+ upsertCodiedevHook(hooks, "PostToolUse", "security-post-codex");
1897
+ upsertCodiedevHook(hooks, "PostToolUseFailure", "security-failure-codex");
1898
+ }
1899
+ else {
1900
+ removeCodiedevHook(hooks, "PreToolUse");
1901
+ removeCodiedevHook(hooks, "PostToolUse");
1902
+ removeCodiedevHook(hooks, "PostToolUseFailure");
1903
+ }
1851
1904
  // Prompt guard + skill discovery. The guard runs locally before any prompt
1852
1905
  // content can reach either the model or Codiedev's discovery endpoint.
1853
1906
  upsertCodiedevHook(hooks, "UserPromptSubmit", "discover-codex");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.9.36",
3
+ "version": "0.9.38",
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",