cc-safety-net 1.0.3 → 1.0.4

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.
@@ -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.3";
7930
+ var CURRENT_VERSION = "1.0.4";
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.3";
9439
+ var version = "1.0.4";
9440
9440
  var INDENT = " ";
9441
9441
  var PROGRAM_NAME = "cc-safety-net";
9442
9442
  function formatOptionFlags(option) {
package/dist/index.d.ts CHANGED
@@ -1,2 +1,15 @@
1
- import type { Plugin } from '@opencode-ai/plugin';
2
- export declare const CCSafetyNetPlugin: Plugin;
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safety-net",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "A coding agent CLI hook - block destructive git and filesystem commands before execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",