cc-safety-net 1.0.3 → 1.0.5
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/README.md +2 -0
- package/dist/bin/cc-safety-net.js +7 -7
- package/dist/bin/hook/constants.d.ts +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.js +66 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -226,6 +226,8 @@ Install CC Safety Net into your Kimi Code config:
|
|
|
226
226
|
npx -y cc-safety-net hook install --kimi-code
|
|
227
227
|
```
|
|
228
228
|
|
|
229
|
+
Optional: run `npx skill add kenryu42/cc-safety-net` to add the `/cc-safety-net` skill for configuring custom rules.
|
|
230
|
+
|
|
229
231
|
---
|
|
230
232
|
|
|
231
233
|
|
|
@@ -6429,7 +6429,7 @@ var CLAUDE_CODE_TOOL_NAME = "Bash";
|
|
|
6429
6429
|
var GEMINI_CLI_HOOK_EVENT = "BeforeTool";
|
|
6430
6430
|
var GEMINI_CLI_TOOL_NAME = "run_shell_command";
|
|
6431
6431
|
var KIMI_CODE_HOOK_EVENT = "PreToolUse";
|
|
6432
|
-
var KIMI_CODE_TOOL_NAME = "
|
|
6432
|
+
var KIMI_CODE_TOOL_NAME = "Bash";
|
|
6433
6433
|
|
|
6434
6434
|
// src/bin/hook/claude-code.ts
|
|
6435
6435
|
async function runClaudeCodeHook() {
|
|
@@ -7531,7 +7531,7 @@ function detectGeminiCLI(extensionsListOutput) {
|
|
|
7531
7531
|
};
|
|
7532
7532
|
}
|
|
7533
7533
|
function _getKimiConfigPath(homeDir) {
|
|
7534
|
-
return join10(process.env.
|
|
7534
|
+
return join10(process.env.KIMI_CODE_HOME || join10(homeDir, ".kimi-code"), "config.toml");
|
|
7535
7535
|
}
|
|
7536
7536
|
function detectKimiCode(homeDir) {
|
|
7537
7537
|
const configPath = _getKimiConfigPath(homeDir);
|
|
@@ -7927,7 +7927,7 @@ import { existsSync as existsSync14 } from "node:fs";
|
|
|
7927
7927
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
7928
7928
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
7929
7929
|
import { delimiter, extname, join as join11 } from "node:path";
|
|
7930
|
-
var CURRENT_VERSION = "1.0.
|
|
7930
|
+
var CURRENT_VERSION = "1.0.5";
|
|
7931
7931
|
var VERSION_FETCH_TIMEOUT_MS = 2000;
|
|
7932
7932
|
var PI_PROBE_TIMEOUT_MS = 5000;
|
|
7933
7933
|
var PI_SENTINEL_COMMAND = "cc-safety-net";
|
|
@@ -9436,7 +9436,7 @@ function formatTraceJson(result) {
|
|
|
9436
9436
|
return JSON.stringify(result, null, 2);
|
|
9437
9437
|
}
|
|
9438
9438
|
// src/bin/help.ts
|
|
9439
|
-
var version = "1.0.
|
|
9439
|
+
var version = "1.0.5";
|
|
9440
9440
|
var INDENT = " ";
|
|
9441
9441
|
var PROGRAM_NAME = "cc-safety-net";
|
|
9442
9442
|
function formatOptionFlags(option) {
|
|
@@ -9635,11 +9635,11 @@ function removeArrayRangeItem(content, item) {
|
|
|
9635
9635
|
var KIMI_HOOK_COMMAND = "npx -y cc-safety-net hook --kimi-code";
|
|
9636
9636
|
var KIMI_HOOK_BLOCK = `[[hooks]]
|
|
9637
9637
|
event = "PreToolUse"
|
|
9638
|
-
matcher = "
|
|
9638
|
+
matcher = "Bash"
|
|
9639
9639
|
command = "${KIMI_HOOK_COMMAND}"`;
|
|
9640
|
-
var KIMI_INLINE_HOOK = `{ event = "PreToolUse", matcher = "
|
|
9640
|
+
var KIMI_INLINE_HOOK = `{ event = "PreToolUse", matcher = "Bash", command = "${KIMI_HOOK_COMMAND}" }`;
|
|
9641
9641
|
function getKimiConfigPath(homeDir) {
|
|
9642
|
-
return join12(process.env.
|
|
9642
|
+
return join12(process.env.KIMI_CODE_HOME ?? join12(homeDir, ".kimi-code"), "config.toml");
|
|
9643
9643
|
}
|
|
9644
9644
|
function removeTopLevelEmptyHooksArray(content) {
|
|
9645
9645
|
const result = content.split(`
|
|
@@ -3,4 +3,4 @@ export declare const CLAUDE_CODE_TOOL_NAME = "Bash";
|
|
|
3
3
|
export declare const GEMINI_CLI_HOOK_EVENT = "BeforeTool";
|
|
4
4
|
export declare const GEMINI_CLI_TOOL_NAME = "run_shell_command";
|
|
5
5
|
export declare const KIMI_CODE_HOOK_EVENT = "PreToolUse";
|
|
6
|
-
export declare const KIMI_CODE_TOOL_NAME = "
|
|
6
|
+
export declare const KIMI_CODE_TOOL_NAME = "Bash";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,15 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
|
+
type CCSafetyNetPluginInput = PluginInput & {
|
|
3
|
+
homeDir?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const CCSafetyNetPlugin: ({ directory, homeDir }: CCSafetyNetPluginInput) => Promise<{
|
|
6
|
+
config: (opencodeConfig: Record<string, unknown>) => Promise<void>;
|
|
7
|
+
'tool.execute.before': (input: {
|
|
8
|
+
tool: string;
|
|
9
|
+
sessionID: string;
|
|
10
|
+
callID: string;
|
|
11
|
+
}, output: {
|
|
12
|
+
args: any;
|
|
13
|
+
}) => Promise<void>;
|
|
14
|
+
}>;
|
|
15
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -6187,6 +6187,66 @@ function analyzeCommand(command2, options2 = {}) {
|
|
|
6187
6187
|
return analyzeCommandInternal(command2, 0, { ...options2, config });
|
|
6188
6188
|
}
|
|
6189
6189
|
|
|
6190
|
+
// src/core/audit.ts
|
|
6191
|
+
import { appendFileSync, existsSync as existsSync10, mkdirSync as mkdirSync3 } from "node:fs";
|
|
6192
|
+
import { homedir as homedir3 } from "node:os";
|
|
6193
|
+
import { join as join8 } from "node:path";
|
|
6194
|
+
function sanitizeSessionIdForFilename(sessionId) {
|
|
6195
|
+
const raw = sessionId.trim();
|
|
6196
|
+
if (!raw) {
|
|
6197
|
+
return null;
|
|
6198
|
+
}
|
|
6199
|
+
let safe = raw.replace(/[^A-Za-z0-9_.-]+/g, "_");
|
|
6200
|
+
safe = safe.replace(/^[._-]+|[._-]+$/g, "").slice(0, 128);
|
|
6201
|
+
if (!safe || safe === "." || safe === "..") {
|
|
6202
|
+
return null;
|
|
6203
|
+
}
|
|
6204
|
+
return safe;
|
|
6205
|
+
}
|
|
6206
|
+
function writeAuditLog(sessionId, command2, segment, reason, cwd, options2 = {}) {
|
|
6207
|
+
const safeSessionId = sanitizeSessionIdForFilename(sessionId);
|
|
6208
|
+
if (!safeSessionId) {
|
|
6209
|
+
return;
|
|
6210
|
+
}
|
|
6211
|
+
const home = options2.homeDir ?? process.env.HOME ?? homedir3();
|
|
6212
|
+
const logsDir = join8(home, ".cc-safety-net", "logs");
|
|
6213
|
+
try {
|
|
6214
|
+
if (!existsSync10(logsDir)) {
|
|
6215
|
+
mkdirSync3(logsDir, { recursive: true });
|
|
6216
|
+
}
|
|
6217
|
+
const logFile = join8(logsDir, `${safeSessionId}.jsonl`);
|
|
6218
|
+
const entry = {
|
|
6219
|
+
ts: new Date().toISOString(),
|
|
6220
|
+
decision: options2.decision ?? "deny",
|
|
6221
|
+
command: redactSecrets(command2).slice(0, 300),
|
|
6222
|
+
segment: redactSecrets(segment).slice(0, 300),
|
|
6223
|
+
reason,
|
|
6224
|
+
cwd
|
|
6225
|
+
};
|
|
6226
|
+
appendFileSync(logFile, `${JSON.stringify(entry)}
|
|
6227
|
+
`, "utf-8");
|
|
6228
|
+
} catch {}
|
|
6229
|
+
}
|
|
6230
|
+
function redactSecrets(text) {
|
|
6231
|
+
let result = text;
|
|
6232
|
+
result = result.replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, "<redacted>");
|
|
6233
|
+
result = result.replace(/\b((?:DATABASE|POSTGRES|POSTGRESQL|MYSQL|MARIADB|REDIS|MONGO(?:DB)?|DB)_URL)=([^\s]+)/gi, "$1=<redacted>");
|
|
6234
|
+
result = result.replace(/\b([A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASS|KEY|CREDENTIALS)[A-Z0-9_]*)=([^\s]+)/gi, "$1=<redacted>");
|
|
6235
|
+
result = result.replace(/(['"]?\s*(?:authorization|cookie|x-api-key|api-key)\s*:\s*)([^'"\r\n]+)(['"]?)/gi, "$1<redacted>$3");
|
|
6236
|
+
result = result.replace(/(['"]?\s*authorization\s*:\s*)([^'"]+)(['"]?)/gi, "$1<redacted>$3");
|
|
6237
|
+
result = result.replace(/(authorization\s*:\s*)([^\s"']+)(\s+[^\s"']+)?/gi, "$1<redacted>");
|
|
6238
|
+
result = result.replace(/\b([a-z][a-z0-9+.-]*:\/\/)([^\s/:@]+):([^\s@/]+)@/gi, "$1<redacted>:<redacted>@");
|
|
6239
|
+
result = result.replace(/\b([a-z][a-z0-9+.-]*:\/\/)([^\s/@:]+)@/gi, "$1<redacted>@");
|
|
6240
|
+
result = result.replace(/\bgh[pousr]_[A-Za-z0-9]{20,}\b/g, "<redacted>");
|
|
6241
|
+
result = result.replace(/\bxoxb-[A-Za-z0-9-]{20,}\b/g, "<redacted>");
|
|
6242
|
+
result = result.replace(/\bnpm_[A-Za-z0-9_]{20,}\b/g, "<redacted>");
|
|
6243
|
+
result = result.replace(/\b[rs]k_(?:live|test)_[A-Za-z0-9_]{20,}\b/g, "<redacted>");
|
|
6244
|
+
result = result.replace(/\bpypi-[A-Za-z0-9_-]{20,}\b/g, "<redacted>");
|
|
6245
|
+
result = result.replace(/\b[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{6,}\b/g, "<redacted>");
|
|
6246
|
+
result = result.replace(/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g, "<redacted>");
|
|
6247
|
+
return result;
|
|
6248
|
+
}
|
|
6249
|
+
|
|
6190
6250
|
// src/core/format.ts
|
|
6191
6251
|
function formatBlockedMessage(input) {
|
|
6192
6252
|
const { reason, command: command2, segment } = input;
|
|
@@ -6276,7 +6336,7 @@ function loadBuiltinCommands(disabledCommands) {
|
|
|
6276
6336
|
}
|
|
6277
6337
|
// src/index.ts
|
|
6278
6338
|
var REASON_SAFETY_NET_FAILED_CLOSED = "CC Safety Net failed closed because command analysis failed unexpectedly.";
|
|
6279
|
-
var CCSafetyNetPlugin = async ({ directory }) => {
|
|
6339
|
+
var CCSafetyNetPlugin = async ({ directory, homeDir }) => {
|
|
6280
6340
|
const modes = getCCSafetyNetEnvModes();
|
|
6281
6341
|
return {
|
|
6282
6342
|
config: async (opencodeConfig) => {
|
|
@@ -6308,6 +6368,11 @@ var CCSafetyNetPlugin = async ({ directory }) => {
|
|
|
6308
6368
|
}));
|
|
6309
6369
|
}
|
|
6310
6370
|
if (result) {
|
|
6371
|
+
if (input.sessionID) {
|
|
6372
|
+
writeAuditLog(input.sessionID, command2, result.segment, result.reason, directory, {
|
|
6373
|
+
homeDir
|
|
6374
|
+
});
|
|
6375
|
+
}
|
|
6311
6376
|
const message = formatBlockedMessage({
|
|
6312
6377
|
reason: result.reason,
|
|
6313
6378
|
command: command2,
|