codiedev 0.9.50 → 0.9.52
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/dist/captureLog.d.ts +1 -1
- package/dist/cli.js +1 -11
- package/dist/codie.js +0 -121
- package/dist/codieLauncher.d.ts +0 -5
- package/dist/codieLauncher.js +1 -22
- package/dist/commands/doctor.js +0 -8
- package/dist/connect.js +3 -52
- package/dist/hook.js +44 -215
- package/dist/limitEvent.d.ts +4 -1
- package/dist/limitEvent.js +54 -0
- package/dist/limitEventReporter.d.ts +21 -0
- package/dist/limitEventReporter.js +91 -0
- package/dist/utils.d.ts +5 -7
- package/dist/utils.js +48 -145
- package/package.json +3 -10
- package/dist/commands/security.d.ts +0 -1
- package/dist/commands/security.js +0 -156
- package/dist/security.d.ts +0 -97
- package/dist/security.js +0 -422
- package/dist/securityDeepScan.d.ts +0 -41
- package/dist/securityDeepScan.js +0 -248
- package/dist/securityDoctor.d.ts +0 -32
- package/dist/securityDoctor.js +0 -63
- package/dist/securityFeature.d.ts +0 -5
- package/dist/securityFeature.js +0 -7
- package/dist/securityInstall.d.ts +0 -62
- package/dist/securityInstall.js +0 -227
- package/dist/securityInventory.d.ts +0 -54
- package/dist/securityInventory.js +0 -556
- package/dist/securityPilot.d.ts +0 -19
- package/dist/securityPilot.js +0 -61
- package/dist/securityPolicy.d.ts +0 -32
- package/dist/securityPolicy.js +0 -179
- package/dist/securitySession.d.ts +0 -6
- package/dist/securitySession.js +0 -72
package/dist/utils.js
CHANGED
|
@@ -64,7 +64,6 @@ exports.openCodeInstalled = openCodeInstalled;
|
|
|
64
64
|
exports.installOpenCodeCapturePlugin = installOpenCodeCapturePlugin;
|
|
65
65
|
exports.vscodeCopilotInstalled = vscodeCopilotInstalled;
|
|
66
66
|
exports.upsertCodiedevHook = upsertCodiedevHook;
|
|
67
|
-
exports.removeCodiedevHook = removeCodiedevHook;
|
|
68
67
|
exports.installHook = installHook;
|
|
69
68
|
exports.ensureDiscoveryHookInstalled = ensureDiscoveryHookInstalled;
|
|
70
69
|
exports.ensureBackupHookInstalled = ensureBackupHookInstalled;
|
|
@@ -83,6 +82,7 @@ exports.cleanupVSCodeMcp = cleanupVSCodeMcp;
|
|
|
83
82
|
exports.cleanupCodexMcp = cleanupCodexMcp;
|
|
84
83
|
exports.installCodexMcp = installCodexMcp;
|
|
85
84
|
exports.installCodexHook = installCodexHook;
|
|
85
|
+
exports.ensureCodexLimitHookInstalled = ensureCodexLimitHookInstalled;
|
|
86
86
|
exports.installCursorHook = installCursorHook;
|
|
87
87
|
exports.parseClaudeSteering = parseClaudeSteering;
|
|
88
88
|
exports.parseCodexSteering = parseCodexSteering;
|
|
@@ -1052,15 +1052,10 @@ function openCodeInstalled() {
|
|
|
1052
1052
|
/** Install a dormant global OpenCode adapter. Installing it unconditionally
|
|
1053
1053
|
* keeps `codiedev connect` as the only setup step even when Codie/OpenCode is
|
|
1054
1054
|
* installed later. OpenCode loads this file from its documented plugin dir. */
|
|
1055
|
-
function installOpenCodeCapturePlugin(
|
|
1055
|
+
function installOpenCodeCapturePlugin() {
|
|
1056
1056
|
const start = resolveHookInvocation("session-start-opencode");
|
|
1057
1057
|
const capture = resolveHookInvocation("capture-opencode");
|
|
1058
1058
|
const finish = resolveHookInvocation("capture-opencode-final");
|
|
1059
|
-
const backup = resolveHookInvocation("backup");
|
|
1060
|
-
const prompt = resolveHookInvocation("discover-codie");
|
|
1061
|
-
const securityPre = resolveHookInvocation("security-pre-codie");
|
|
1062
|
-
const securityPost = resolveHookInvocation("security-post-codie");
|
|
1063
|
-
const securityFailure = resolveHookInvocation("security-failure-codie");
|
|
1064
1059
|
const source = `// Managed by codiedev connect. Safe to remove and reinstall.\n` +
|
|
1065
1060
|
`import { spawn, spawnSync } from "node:child_process";\n` +
|
|
1066
1061
|
`import { mkdir, writeFile } from "node:fs/promises";\n` +
|
|
@@ -1069,15 +1064,8 @@ function installOpenCodeCapturePlugin(agentSecurityEnabled = true) {
|
|
|
1069
1064
|
`const START = ${JSON.stringify(start)};\n` +
|
|
1070
1065
|
`const CAPTURE = ${JSON.stringify(capture)};\n` +
|
|
1071
1066
|
`const FINISH = ${JSON.stringify(finish)};\n` +
|
|
1072
|
-
`const BACKUP = ${JSON.stringify(backup)};\n` +
|
|
1073
|
-
`const PROMPT = ${JSON.stringify(prompt)};\n` +
|
|
1074
|
-
`const SECURITY_PRE = ${JSON.stringify(securityPre)};\n` +
|
|
1075
|
-
`const SECURITY_POST = ${JSON.stringify(securityPost)};\n` +
|
|
1076
|
-
`const SECURITY_FAILURE = ${JSON.stringify(securityFailure)};\n` +
|
|
1077
|
-
`const AGENT_SECURITY_ENABLED = ${JSON.stringify(agentSecurityEnabled)};\n` +
|
|
1078
1067
|
`const fingerprints = new Map();\n\n` +
|
|
1079
1068
|
`const transcriptPaths = new Map();\n\n` +
|
|
1080
|
-
`const sessionDirectories = new Map();\n\n` +
|
|
1081
1069
|
`function sessionId(event) {\n` +
|
|
1082
1070
|
` return event?.properties?.sessionID ?? event?.properties?.sessionId ?? event?.properties?.info?.id ?? null;\n` +
|
|
1083
1071
|
`}\n\n` +
|
|
@@ -1091,75 +1079,19 @@ function installOpenCodeCapturePlugin(agentSecurityEnabled = true) {
|
|
|
1091
1079
|
`function dispatchSync(invocation, payload) {\n` +
|
|
1092
1080
|
` try { spawnSync(invocation.command, invocation.args, { input: JSON.stringify(payload), stdio: ["pipe", "ignore", "ignore"], timeout: 1500 }); } catch {}\n` +
|
|
1093
1081
|
`}\n\n` +
|
|
1094
|
-
`function enforceSync(invocation, payload) {\n` +
|
|
1095
|
-
` const result = spawnSync(invocation.command, invocation.args, { input: JSON.stringify(payload), encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], timeout: 1500 });\n` +
|
|
1096
|
-
` if (result.error) return null;\n` +
|
|
1097
|
-
` try {\n` +
|
|
1098
|
-
` const decision = JSON.parse(result.stdout || "{}");\n` +
|
|
1099
|
-
` const denied = decision?.decision === "block" || decision?.hookSpecificOutput?.permissionDecision === "deny";\n` +
|
|
1100
|
-
` return denied ? (decision?.reason || decision?.hookSpecificOutput?.permissionDecisionReason || "Codie security policy blocked this action") : null;\n` +
|
|
1101
|
-
` } catch (error) {\n` +
|
|
1102
|
-
` if (error instanceof SyntaxError) return null;\n` +
|
|
1103
|
-
` return null;\n` +
|
|
1104
|
-
` }\n` +
|
|
1105
|
-
`}\n\n` +
|
|
1106
1082
|
`export const CodiedevCapture = async ({ client, directory, worktree }) => {\n` +
|
|
1107
|
-
` const enforce = async (invocation, payload) => {\n` +
|
|
1108
|
-
` const reason = enforceSync(invocation, payload);\n` +
|
|
1109
|
-
` if (!reason) return;\n` +
|
|
1110
|
-
` try { await client.tui.showToast({ title: "Codie Security", message: reason, variant: "error", duration: 10000 }); } catch {}\n` +
|
|
1111
|
-
` if (process.env.CODIEDEV_SECURITY_BLOCK_OUTPUT === "marker") {\n` +
|
|
1112
|
-
` process.stderr.write("__CODIEDEV_SECURITY_BLOCK__" + JSON.stringify({ reason }) + "\\n");\n` +
|
|
1113
|
-
` process.exit(1);\n` +
|
|
1114
|
-
` } else {\n` +
|
|
1115
|
-
` process.stderr.write("\\nCodie Security blocked this action:\\n" + reason + "\\n");\n` +
|
|
1116
|
-
` }\n` +
|
|
1117
|
-
` throw new Error(reason);\n` +
|
|
1118
|
-
` };\n` +
|
|
1119
|
-
` const resolveCwd = async (id, hint) => {\n` +
|
|
1120
|
-
` if (typeof hint === "string" && hint && hint !== "/") { sessionDirectories.set(id, hint); return hint; }\n` +
|
|
1121
|
-
` const remembered = sessionDirectories.get(id);\n` +
|
|
1122
|
-
` if (remembered) return remembered;\n` +
|
|
1123
|
-
` try {\n` +
|
|
1124
|
-
` const response = await client.session.get({ path: { id } });\n` +
|
|
1125
|
-
` const session = response?.data ?? response;\n` +
|
|
1126
|
-
` if (typeof session?.directory === "string" && session.directory) { sessionDirectories.set(id, session.directory); return session.directory; }\n` +
|
|
1127
|
-
` } catch {}\n` +
|
|
1128
|
-
` const fallback = worktree || directory || process.cwd();\n` +
|
|
1129
|
-
` sessionDirectories.set(id, fallback);\n` +
|
|
1130
|
-
` return fallback;\n` +
|
|
1131
|
-
` };\n` +
|
|
1132
1083
|
` const finalizeAll = () => {\n` +
|
|
1133
1084
|
` for (const [id, transcriptPath] of transcriptPaths) {\n` +
|
|
1134
|
-
` const cwd =
|
|
1085
|
+
` const cwd = worktree || directory || process.cwd();\n` +
|
|
1135
1086
|
` dispatchSync(FINISH, { session_id: id, transcript_path: transcriptPath, cwd, workspace_roots: [cwd] });\n` +
|
|
1136
1087
|
` }\n` +
|
|
1137
1088
|
` };\n` +
|
|
1138
1089
|
` process.once("exit", finalizeAll);\n` +
|
|
1139
1090
|
` return ({\n` +
|
|
1140
|
-
` "chat.message": async (input, output) => {\n` +
|
|
1141
|
-
` const id = input?.sessionID ?? input?.sessionId ?? "unknown";\n` +
|
|
1142
|
-
` const cwd = await resolveCwd(id);\n` +
|
|
1143
|
-
` const prompt = Array.isArray(output?.parts) ? output.parts.filter((part) => part?.type === "text").map((part) => part.text || "").join("\\n") : "";\n` +
|
|
1144
|
-
` await enforce(PROMPT, { sessionId: id, cwd, prompt });\n` +
|
|
1145
|
-
` },\n` +
|
|
1146
|
-
` "tool.execute.before": async (input, output) => {\n` +
|
|
1147
|
-
` if (!AGENT_SECURITY_ENABLED) return;\n` +
|
|
1148
|
-
` const id = input?.sessionID ?? input?.sessionId ?? "unknown";\n` +
|
|
1149
|
-
` const cwd = await resolveCwd(id);\n` +
|
|
1150
|
-
` await enforce(SECURITY_PRE, { sessionId: id, cwd, toolName: input?.tool ?? "unknown", toolUseId: input?.callID, toolInput: output?.args ?? output });\n` +
|
|
1151
|
-
` },\n` +
|
|
1152
|
-
` "tool.execute.after": async (input, output) => {\n` +
|
|
1153
|
-
` if (!AGENT_SECURITY_ENABLED) return;\n` +
|
|
1154
|
-
` const id = input?.sessionID ?? input?.sessionId ?? "unknown";\n` +
|
|
1155
|
-
` const cwd = await resolveCwd(id);\n` +
|
|
1156
|
-
` const payload = { sessionId: id, cwd, toolName: input?.tool ?? "unknown", toolUseId: input?.callID, toolInput: input?.args, toolResponse: output };\n` +
|
|
1157
|
-
` dispatch(output?.error ? SECURITY_FAILURE : SECURITY_POST, payload);\n` +
|
|
1158
|
-
` },\n` +
|
|
1159
1091
|
` event: async ({ event }) => {\n` +
|
|
1160
1092
|
` const id = sessionId(event);\n` +
|
|
1161
1093
|
` if (!id) return;\n` +
|
|
1162
|
-
` const cwd =
|
|
1094
|
+
` const cwd = worktree || directory || process.cwd();\n` +
|
|
1163
1095
|
` if (event.type === "session.created") {\n` +
|
|
1164
1096
|
` dispatch(START, { session_id: id, cwd, workspace_roots: [cwd] });\n` +
|
|
1165
1097
|
` return;\n` +
|
|
@@ -1168,7 +1100,6 @@ function installOpenCodeCapturePlugin(agentSecurityEnabled = true) {
|
|
|
1168
1100
|
` const transcriptPath = transcriptPaths.get(id);\n` +
|
|
1169
1101
|
` if (transcriptPath) dispatch(FINISH, { session_id: id, transcript_path: transcriptPath, cwd, workspace_roots: [cwd] });\n` +
|
|
1170
1102
|
` transcriptPaths.delete(id);\n` +
|
|
1171
|
-
` sessionDirectories.delete(id);\n` +
|
|
1172
1103
|
` fingerprints.delete(id);\n` +
|
|
1173
1104
|
` return;\n` +
|
|
1174
1105
|
` }\n` +
|
|
@@ -1188,7 +1119,6 @@ function installOpenCodeCapturePlugin(agentSecurityEnabled = true) {
|
|
|
1188
1119
|
` await writeFile(transcriptPath, JSON.stringify(list), { mode: 0o600 });\n` +
|
|
1189
1120
|
` transcriptPaths.set(id, transcriptPath);\n` +
|
|
1190
1121
|
` dispatch(CAPTURE, { session_id: id, transcript_path: transcriptPath, cwd, workspace_roots: [cwd] });\n` +
|
|
1191
|
-
` dispatch(BACKUP, { session_id: id, cwd, workspace_roots: [cwd] });\n` +
|
|
1192
1122
|
` } catch {}\n` +
|
|
1193
1123
|
` },\n` +
|
|
1194
1124
|
` });\n` +
|
|
@@ -1251,36 +1181,7 @@ function upsertCodiedevHook(hooks, eventName, subcommand, matcher = ".*") {
|
|
|
1251
1181
|
});
|
|
1252
1182
|
hooks[eventName] = filtered;
|
|
1253
1183
|
}
|
|
1254
|
-
|
|
1255
|
-
function removeCodiedevHook(hooks, eventName) {
|
|
1256
|
-
const entries = hooks[eventName];
|
|
1257
|
-
if (!Array.isArray(entries))
|
|
1258
|
-
return false;
|
|
1259
|
-
let changed = false;
|
|
1260
|
-
const filtered = entries.flatMap((entry) => {
|
|
1261
|
-
const record = entry;
|
|
1262
|
-
const inner = record.hooks;
|
|
1263
|
-
if (!Array.isArray(inner))
|
|
1264
|
-
return [record];
|
|
1265
|
-
const remaining = inner.filter((hook) => {
|
|
1266
|
-
const remove = (0, detection_1.isCodiedevHookCommand)(hook.command);
|
|
1267
|
-
if (remove)
|
|
1268
|
-
changed = true;
|
|
1269
|
-
return !remove;
|
|
1270
|
-
});
|
|
1271
|
-
if (remaining.length === 0)
|
|
1272
|
-
return [];
|
|
1273
|
-
return [{ ...record, hooks: remaining }];
|
|
1274
|
-
});
|
|
1275
|
-
if (!changed)
|
|
1276
|
-
return false;
|
|
1277
|
-
if (filtered.length === 0)
|
|
1278
|
-
delete hooks[eventName];
|
|
1279
|
-
else
|
|
1280
|
-
hooks[eventName] = filtered;
|
|
1281
|
-
return true;
|
|
1282
|
-
}
|
|
1283
|
-
function installHook(agentSecurityEnabled = true) {
|
|
1184
|
+
function installHook() {
|
|
1284
1185
|
let settings = {};
|
|
1285
1186
|
try {
|
|
1286
1187
|
if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
@@ -1297,18 +1198,6 @@ function installHook(agentSecurityEnabled = true) {
|
|
|
1297
1198
|
const hooks = settings.hooks;
|
|
1298
1199
|
// SessionEnd → capture (upload the finished session for the team library).
|
|
1299
1200
|
upsertCodiedevHook(hooks, "SessionEnd", "capture");
|
|
1300
|
-
// Tool boundaries → ADR shadow detection. PreToolUse is the eventual
|
|
1301
|
-
// enforcement point; alert mode always returns allow.
|
|
1302
|
-
if (agentSecurityEnabled) {
|
|
1303
|
-
upsertCodiedevHook(hooks, "PreToolUse", "security-pre");
|
|
1304
|
-
upsertCodiedevHook(hooks, "PostToolUse", "security-post");
|
|
1305
|
-
upsertCodiedevHook(hooks, "PostToolUseFailure", "security-failure");
|
|
1306
|
-
}
|
|
1307
|
-
else {
|
|
1308
|
-
removeCodiedevHook(hooks, "PreToolUse");
|
|
1309
|
-
removeCodiedevHook(hooks, "PostToolUse");
|
|
1310
|
-
removeCodiedevHook(hooks, "PostToolUseFailure");
|
|
1311
|
-
}
|
|
1312
1201
|
// UserPromptSubmit → discover (surface a matching team skill for the task).
|
|
1313
1202
|
upsertCodiedevHook(hooks, "UserPromptSubmit", "discover");
|
|
1314
1203
|
// Stop → backup (auto-back-up changed artifact files to your private library).
|
|
@@ -1410,7 +1299,7 @@ function ensureLimitHookInstalled() {
|
|
|
1410
1299
|
// shrinks that window and removes the intra-process version entirely.
|
|
1411
1300
|
// The per-hook ensure* functions below are kept for tests/back-compat but are
|
|
1412
1301
|
// no longer called from the hook entrypoint.
|
|
1413
|
-
function ensureAllCodiedevHooksInstalled(
|
|
1302
|
+
function ensureAllCodiedevHooksInstalled() {
|
|
1414
1303
|
try {
|
|
1415
1304
|
if (!fs.existsSync(CLAUDE_SETTINGS_PATH))
|
|
1416
1305
|
return; // not a Claude Code user
|
|
@@ -1423,17 +1312,8 @@ function ensureAllCodiedevHooksInstalled(agentSecurityEnabled = readConfig()?.ag
|
|
|
1423
1312
|
["StopFailure", "limit", "rate_limit"],
|
|
1424
1313
|
["SessionStart", "session-start"],
|
|
1425
1314
|
];
|
|
1426
|
-
if (agentSecurityEnabled) {
|
|
1427
|
-
wanted.splice(1, 0, ["PreToolUse", "security-pre"], ["PostToolUse", "security-post"], ["PostToolUseFailure", "security-failure"]);
|
|
1428
|
-
}
|
|
1429
|
-
let changed = false;
|
|
1430
|
-
if (!agentSecurityEnabled) {
|
|
1431
|
-
for (const eventName of ["PreToolUse", "PostToolUse", "PostToolUseFailure"]) {
|
|
1432
|
-
changed = removeCodiedevHook(hooks, eventName) || changed;
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
1315
|
const missing = wanted.filter(([eventName]) => !hasCodiedevHook(hooks, eventName));
|
|
1436
|
-
if (missing.length === 0
|
|
1316
|
+
if (missing.length === 0)
|
|
1437
1317
|
return; // steady state: no write, no churn
|
|
1438
1318
|
settings.hooks = hooks;
|
|
1439
1319
|
for (const [eventName, subcommand, matcher] of missing) {
|
|
@@ -1874,7 +1754,7 @@ function installCodexMcp() {
|
|
|
1874
1754
|
// Codex doesn't have a SessionEnd event yet — Stop fires after every turn.
|
|
1875
1755
|
// The backend dedupes on (companyId, sessionId) and replaces the stored
|
|
1876
1756
|
// transcript on re-upload, so the latest turn's snapshot wins.
|
|
1877
|
-
function installCodexHook(
|
|
1757
|
+
function installCodexHook() {
|
|
1878
1758
|
let hooksFile = {};
|
|
1879
1759
|
try {
|
|
1880
1760
|
if (fs.existsSync(CODEX_HOOKS_PATH)) {
|
|
@@ -1889,8 +1769,9 @@ function installCodexHook(agentSecurityEnabled = true) {
|
|
|
1889
1769
|
hooksFile.hooks = {};
|
|
1890
1770
|
}
|
|
1891
1771
|
const hooks = hooksFile.hooks;
|
|
1892
|
-
if (!hooks.Stop)
|
|
1772
|
+
if (!hooks.Stop) {
|
|
1893
1773
|
hooks.Stop = [];
|
|
1774
|
+
}
|
|
1894
1775
|
const existing = hooks.Stop;
|
|
1895
1776
|
const filtered = existing.filter((hook) => {
|
|
1896
1777
|
const inner = hook.hooks;
|
|
@@ -1898,6 +1779,15 @@ function installCodexHook(agentSecurityEnabled = true) {
|
|
|
1898
1779
|
return true;
|
|
1899
1780
|
return !inner.some((h) => (0, detection_1.isCodiedevHookCommand)(h.command));
|
|
1900
1781
|
});
|
|
1782
|
+
filtered.push({
|
|
1783
|
+
hooks: [
|
|
1784
|
+
{
|
|
1785
|
+
type: "command",
|
|
1786
|
+
command: resolveHookCommand("limit-codex"),
|
|
1787
|
+
timeout: 10,
|
|
1788
|
+
},
|
|
1789
|
+
],
|
|
1790
|
+
});
|
|
1901
1791
|
filtered.push({
|
|
1902
1792
|
hooks: [
|
|
1903
1793
|
{
|
|
@@ -1918,28 +1808,41 @@ function installCodexHook(agentSecurityEnabled = true) {
|
|
|
1918
1808
|
],
|
|
1919
1809
|
});
|
|
1920
1810
|
hooks.Stop = filtered;
|
|
1921
|
-
// Codex uses the same Claude-style matcher-group shape. Keep these as
|
|
1922
|
-
// separate entries because Codex hook coverage is still evolving and a
|
|
1923
|
-
// malformed/unavailable post hook must not interfere with PreToolUse.
|
|
1924
|
-
if (agentSecurityEnabled) {
|
|
1925
|
-
upsertCodiedevHook(hooks, "PreToolUse", "security-pre-codex");
|
|
1926
|
-
upsertCodiedevHook(hooks, "PostToolUse", "security-post-codex");
|
|
1927
|
-
upsertCodiedevHook(hooks, "PostToolUseFailure", "security-failure-codex");
|
|
1928
|
-
}
|
|
1929
|
-
else {
|
|
1930
|
-
removeCodiedevHook(hooks, "PreToolUse");
|
|
1931
|
-
removeCodiedevHook(hooks, "PostToolUse");
|
|
1932
|
-
removeCodiedevHook(hooks, "PostToolUseFailure");
|
|
1933
|
-
}
|
|
1934
|
-
// Prompt guard + skill discovery. The guard runs locally before any prompt
|
|
1935
|
-
// content can reach either the model or Codiedev's discovery endpoint.
|
|
1936
|
-
upsertCodiedevHook(hooks, "UserPromptSubmit", "discover-codex");
|
|
1937
1811
|
if (!fs.existsSync(CODEX_DIR)) {
|
|
1938
1812
|
fs.mkdirSync(CODEX_DIR, { recursive: true });
|
|
1939
1813
|
}
|
|
1940
1814
|
fs.writeFileSync(CODEX_HOOKS_PATH, JSON.stringify(hooksFile, null, 2), "utf8");
|
|
1941
1815
|
(0, codexHookTrust_1.writeCodexHookTrust)(CODEX_CONFIG_PATH, CODEX_HOOKS_PATH, hooksFile);
|
|
1942
1816
|
}
|
|
1817
|
+
// Existing Codex users already have capture-codex on Stop. After a CLI
|
|
1818
|
+
// upgrade, that hook repairs the new limit reporter without requiring connect.
|
|
1819
|
+
function ensureCodexLimitHookInstalled() {
|
|
1820
|
+
try {
|
|
1821
|
+
if (!fs.existsSync(CODEX_HOOKS_PATH))
|
|
1822
|
+
return;
|
|
1823
|
+
const hooksFile = JSON.parse(fs.readFileSync(CODEX_HOOKS_PATH, "utf8"));
|
|
1824
|
+
const hooks = (hooksFile.hooks ?? {});
|
|
1825
|
+
const stop = Array.isArray(hooks.Stop) ? hooks.Stop : [];
|
|
1826
|
+
const present = stop.some((group) => {
|
|
1827
|
+
const inner = group.hooks;
|
|
1828
|
+
return Array.isArray(inner) && inner.some((hook) => typeof hook.command === "string" && /(?:^|\s)limit-codex(?:\s|$)/.test(hook.command));
|
|
1829
|
+
});
|
|
1830
|
+
if (present)
|
|
1831
|
+
return;
|
|
1832
|
+
stop.unshift({
|
|
1833
|
+
hooks: [{ type: "command", command: resolveHookCommand("limit-codex"), timeout: 10 }],
|
|
1834
|
+
});
|
|
1835
|
+
hooks.Stop = stop;
|
|
1836
|
+
hooksFile.hooks = hooks;
|
|
1837
|
+
fs.writeFileSync(CODEX_HOOKS_PATH, JSON.stringify(hooksFile, null, 2), "utf8");
|
|
1838
|
+
// Trust keys include each hook's positional index. Adding the new hook at
|
|
1839
|
+
// the front shifts capture/backup, so refresh every Codiedev hash together.
|
|
1840
|
+
(0, codexHookTrust_1.writeCodexHookTrust)(CODEX_CONFIG_PATH, CODEX_HOOKS_PATH, hooksFile);
|
|
1841
|
+
}
|
|
1842
|
+
catch {
|
|
1843
|
+
// Best-effort: capture must never fail because hook repair did.
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1943
1846
|
// Cursor's hooks.json uses { version: 1, hooks: { sessionEnd: [{ command, type, timeout }] } }.
|
|
1944
1847
|
// `sessionEnd` fires when a conversation closes (completed, aborted, error,
|
|
1945
1848
|
// window_close, user_close); the base stdin payload includes transcript_path
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codiedev",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.52",
|
|
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,8 +12,7 @@
|
|
|
12
12
|
"dev": "tsc --watch",
|
|
13
13
|
"prepublishOnly": "tsc",
|
|
14
14
|
"test": "vitest run",
|
|
15
|
-
"test:
|
|
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",
|
|
15
|
+
"test:functional": "vitest run src/__tests__/limitEvent.functional.test.ts",
|
|
17
16
|
"test:watch": "vitest"
|
|
18
17
|
},
|
|
19
18
|
"files": [
|
|
@@ -37,18 +36,12 @@
|
|
|
37
36
|
"session-capture"
|
|
38
37
|
],
|
|
39
38
|
"homepage": "https://codiedev.com",
|
|
40
|
-
"repository": {
|
|
41
|
-
"type": "git",
|
|
42
|
-
"url": "git+https://github.com/Signal-and-Code/codiedev.git",
|
|
43
|
-
"directory": "packages/codiedev-cli"
|
|
44
|
-
},
|
|
45
39
|
"license": "MIT",
|
|
46
40
|
"engines": {
|
|
47
41
|
"node": ">=18"
|
|
48
42
|
},
|
|
49
43
|
"publishConfig": {
|
|
50
|
-
"access": "public"
|
|
51
|
-
"provenance": true
|
|
44
|
+
"access": "public"
|
|
52
45
|
},
|
|
53
46
|
"devDependencies": {
|
|
54
47
|
"@types/node": "^20.0.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function runSecurityCommand(args: string[]): Promise<void>;
|
|
@@ -1,156 +0,0 @@
|
|
|
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/security.d.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
export type SecurityHookPhase = "pre" | "post" | "failure";
|
|
2
|
-
export type AgentSurface = "claude-code" | "codex" | "codie-harness";
|
|
3
|
-
export interface RawSecurityHookInput {
|
|
4
|
-
session_id?: string;
|
|
5
|
-
sessionId?: string;
|
|
6
|
-
cwd?: string;
|
|
7
|
-
hook_event_name?: string;
|
|
8
|
-
hookEventName?: string;
|
|
9
|
-
tool_name?: string;
|
|
10
|
-
toolName?: string;
|
|
11
|
-
tool_input?: unknown;
|
|
12
|
-
toolInput?: unknown;
|
|
13
|
-
tool_response?: unknown;
|
|
14
|
-
toolResponse?: unknown;
|
|
15
|
-
tool_use_id?: string;
|
|
16
|
-
toolUseId?: string;
|
|
17
|
-
duration_ms?: number;
|
|
18
|
-
durationMs?: number;
|
|
19
|
-
error?: unknown;
|
|
20
|
-
prompt?: string;
|
|
21
|
-
}
|
|
22
|
-
export interface SecurityWorkflowEvent {
|
|
23
|
-
schemaVersion: 1;
|
|
24
|
-
source: AgentSurface;
|
|
25
|
-
phase: SecurityHookPhase;
|
|
26
|
-
sessionId: string;
|
|
27
|
-
toolName: string;
|
|
28
|
-
toolUseId?: string;
|
|
29
|
-
cwd?: string;
|
|
30
|
-
observedAt: number;
|
|
31
|
-
toolDurationMs?: number;
|
|
32
|
-
payload: unknown;
|
|
33
|
-
redactionCount: number;
|
|
34
|
-
localDecision?: LocalSecurityAction;
|
|
35
|
-
localCategory?: LocalSecurityDecision["category"];
|
|
36
|
-
endpointId?: string;
|
|
37
|
-
endpointName?: string;
|
|
38
|
-
cliVersion?: string;
|
|
39
|
-
}
|
|
40
|
-
export type LocalSecurityAction = "allow" | "alert" | "block";
|
|
41
|
-
export type SecurityCategory = "secret_in_prompt" | "secret_exfiltration" | "destructive_outside_workspace" | "sensitive_file_read" | "sensitive_file_write" | "protected_agent_config_write" | "unapproved_network_egress" | "untrusted_mcp_tool" | "unapproved_command" | "obfuscated_shell_command" | "package_install" | "sensitive_read_then_egress";
|
|
42
|
-
export interface EndpointSecurityPolicy {
|
|
43
|
-
schemaVersion: 1;
|
|
44
|
-
policyVersion: number;
|
|
45
|
-
enforcementMode: "alert" | "block";
|
|
46
|
-
rules: Partial<Record<SecurityCategory, LocalSecurityAction>>;
|
|
47
|
-
constraints?: {
|
|
48
|
-
allowedMcpServers?: string[];
|
|
49
|
-
allowedHosts?: string[];
|
|
50
|
-
allowedCommands?: string[];
|
|
51
|
-
};
|
|
52
|
-
exceptions: Array<{
|
|
53
|
-
scope: "company" | "user" | "source" | "repository" | "tool" | "category";
|
|
54
|
-
scopeValue: string;
|
|
55
|
-
}>;
|
|
56
|
-
fetchedAt: number;
|
|
57
|
-
signature?: {
|
|
58
|
-
algorithm: "Ed25519";
|
|
59
|
-
keyId: string;
|
|
60
|
-
signedAt: number;
|
|
61
|
-
value: string;
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
export interface LocalSecurityDecision {
|
|
65
|
-
action: LocalSecurityAction;
|
|
66
|
-
category?: SecurityCategory;
|
|
67
|
-
reason?: string;
|
|
68
|
-
}
|
|
69
|
-
export declare function withEndpointIdentity(event: SecurityWorkflowEvent, identity: {
|
|
70
|
-
endpointId?: string;
|
|
71
|
-
endpointName?: string;
|
|
72
|
-
cliVersion?: string;
|
|
73
|
-
}): SecurityWorkflowEvent;
|
|
74
|
-
/**
|
|
75
|
-
* Runs before a user prompt reaches the model. This is intentionally narrow:
|
|
76
|
-
* block credential material, but allow safe references such as
|
|
77
|
-
* `$OPENAI_API_KEY` so developers can ask agents to use environment variables
|
|
78
|
-
* without pasting their values.
|
|
79
|
-
*/
|
|
80
|
-
export declare function evaluatePromptSecurity(prompt: string | undefined, policy?: EndpointSecurityPolicy, source?: AgentSurface): LocalSecurityDecision;
|
|
81
|
-
/** Runs synchronously before a tool executes. No network or model is involved. */
|
|
82
|
-
export declare function evaluateToolSecurity(input: RawSecurityHookInput, phase: SecurityHookPhase, policy?: EndpointSecurityPolicy, source?: AgentSurface, sessionContext?: {
|
|
83
|
-
sensitiveReadAt?: number;
|
|
84
|
-
now?: number;
|
|
85
|
-
}): LocalSecurityDecision;
|
|
86
|
-
export declare function securityInputHasSensitivePath(input: RawSecurityHookInput): boolean;
|
|
87
|
-
export declare function buildSecurityWorkflowEvent(input: RawSecurityHookInput, phase: SecurityHookPhase, source: AgentSurface, now?: () => number): SecurityWorkflowEvent | null;
|
|
88
|
-
/**
|
|
89
|
-
* Captures the intent side of the causal chain without weakening the local
|
|
90
|
-
* privacy boundary. A prompt that the deterministic policy blocks never
|
|
91
|
-
* becomes an uploadable event; allowed prompts are redacted and bounded using
|
|
92
|
-
* the same sanitizer as tool payloads.
|
|
93
|
-
*/
|
|
94
|
-
export declare function buildSecurityPromptEvent(input: Pick<RawSecurityHookInput, "session_id" | "sessionId" | "cwd" | "prompt">, source: AgentSurface, now?: () => number, policy?: EndpointSecurityPolicy): SecurityWorkflowEvent | null;
|
|
95
|
-
/** Audit-safe evidence for a blocked prompt. The secret-bearing prompt is
|
|
96
|
-
* intentionally omitted; administrators still see that prevention occurred. */
|
|
97
|
-
export declare function buildBlockedSecurityPromptEvent(input: Pick<RawSecurityHookInput, "session_id" | "sessionId" | "cwd">, source: AgentSurface, category: NonNullable<LocalSecurityDecision["category"]>, now?: () => number): SecurityWorkflowEvent | null;
|