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