cc-safety-net 1.0.1 → 1.0.2

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.
@@ -338,6 +338,10 @@ function dangerousInText(text) {
338
338
  return null;
339
339
  }
340
340
 
341
+ // src/core/analyze/segment.ts
342
+ import { realpathSync as realpathSync6 } from "node:fs";
343
+ import { normalize as normalize3 } from "node:path";
344
+
341
345
  // src/core/analyze/awk.ts
342
346
  var AWK_INTERPRETERS = new Set(["awk", "gawk", "nawk", "mawk"]);
343
347
  var REASON_AWK_SYSTEM_DYNAMIC = "Detected awk system() call with dynamic command that cannot be safely analyzed.";
@@ -4143,8 +4147,7 @@ function analyzeParallelCommand(context) {
4143
4147
  }
4144
4148
  var CWD_CHANGE_REGEX = /^\s*(?:\$\(\s*)?[({]*\s*(?:command\s+|builtin\s+)?(?:cd|pushd|popd)(?:\s|$)/;
4145
4149
  function segmentChangesCwd(segment) {
4146
- const stripped = stripLeadingGrouping(segment);
4147
- const unwrapped = stripWrappers([...stripped]);
4150
+ const unwrapped = getCwdChangeTokens(segment);
4148
4151
  if (unwrapped.length === 0) {
4149
4152
  return false;
4150
4153
  }
@@ -4163,6 +4166,32 @@ function segmentChangesCwd(segment) {
4163
4166
  const joined = segment.join(" ");
4164
4167
  return CWD_CHANGE_REGEX.test(joined);
4165
4168
  }
4169
+ function resolveCwdAfterSegment(segment, cwd) {
4170
+ if (!segmentChangesCwd(segment)) {
4171
+ return;
4172
+ }
4173
+ if (!cwd) {
4174
+ return null;
4175
+ }
4176
+ const unwrapped = getCwdChangeTokens(segment, cwd);
4177
+ const cdIndex = getCdCommandIndex(unwrapped);
4178
+ if (cdIndex === -1 || unwrapped[cdIndex] !== "cd") {
4179
+ return null;
4180
+ }
4181
+ const target = unwrapped[cdIndex + 1];
4182
+ if (!target || target === "-" || target.includes("$") || target.includes("`")) {
4183
+ return null;
4184
+ }
4185
+ try {
4186
+ const resolved = resolveChdirTarget(cwd, target);
4187
+ if (samePath(resolved, cwd)) {
4188
+ return cwd;
4189
+ }
4190
+ } catch {
4191
+ return null;
4192
+ }
4193
+ return null;
4194
+ }
4166
4195
  function getHeadAfterTimePrefix(tokens, startIndex) {
4167
4196
  let i = startIndex;
4168
4197
  while (tokens[i]?.startsWith("-")) {
@@ -4170,6 +4199,31 @@ function getHeadAfterTimePrefix(tokens, startIndex) {
4170
4199
  }
4171
4200
  return tokens[i] ?? "";
4172
4201
  }
4202
+ function getCdCommandIndex(tokens) {
4203
+ let headIndex = 0;
4204
+ if (tokens[0] === "builtin" && tokens.length > 1) {
4205
+ headIndex = 1;
4206
+ }
4207
+ if (tokens[headIndex] !== "time") {
4208
+ return headIndex;
4209
+ }
4210
+ let i = headIndex + 1;
4211
+ while (tokens[i]?.startsWith("-")) {
4212
+ i++;
4213
+ }
4214
+ return i;
4215
+ }
4216
+ function getCwdChangeTokens(segment, cwd) {
4217
+ const stripped = stripLeadingGrouping(segment);
4218
+ return stripWrappers([...stripped], cwd);
4219
+ }
4220
+ function samePath(a, b) {
4221
+ try {
4222
+ return normalize3(realpathSync6(a)) === normalize3(realpathSync6(b));
4223
+ } catch {
4224
+ return normalize3(a) === normalize3(b);
4225
+ }
4226
+ }
4173
4227
  function stripLeadingGrouping(tokens) {
4174
4228
  let i = 0;
4175
4229
  while (i < tokens.length) {
@@ -4520,8 +4574,9 @@ function analyzeCommandInternal(command2, depth, options2) {
4520
4574
  if (textReason) {
4521
4575
  return { reason: textReason, segment: segmentStr };
4522
4576
  }
4523
- if (segmentChangesCwd(segment)) {
4524
- effectiveCwd = null;
4577
+ const nextCwd2 = resolveCwdAfterSegment(segment, effectiveCwd);
4578
+ if (nextCwd2 !== undefined) {
4579
+ effectiveCwd = nextCwd2;
4525
4580
  }
4526
4581
  continue;
4527
4582
  }
@@ -4543,8 +4598,9 @@ function analyzeCommandInternal(command2, depth, options2) {
4543
4598
  if (reason) {
4544
4599
  return { reason, segment: segmentStr };
4545
4600
  }
4546
- if (segmentChangesCwd(segment)) {
4547
- effectiveCwd = null;
4601
+ const nextCwd = resolveCwdAfterSegment(segment, effectiveCwd);
4602
+ if (nextCwd !== undefined) {
4603
+ effectiveCwd = nextCwd;
4548
4604
  }
4549
4605
  applyShellGitContextEnvSegment(segment, shellGitContextState);
4550
4606
  }
@@ -7830,7 +7886,7 @@ import { existsSync as existsSync14 } from "node:fs";
7830
7886
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
7831
7887
  import { tmpdir as tmpdir4 } from "node:os";
7832
7888
  import { delimiter, extname, join as join11 } from "node:path";
7833
- var CURRENT_VERSION = "1.0.1";
7889
+ var CURRENT_VERSION = "1.0.2";
7834
7890
  var VERSION_FETCH_TIMEOUT_MS = 2000;
7835
7891
  var PI_PROBE_TIMEOUT_MS = 5000;
7836
7892
  var PI_SENTINEL_COMMAND = "cc-safety-net";
@@ -8882,12 +8938,14 @@ function explainCommand2(command2, options2) {
8882
8938
  token: redactEnvAssignmentsInString(segment[0]),
8883
8939
  matched: false
8884
8940
  });
8885
- if (segmentChangesCwd(segment)) {
8886
- segmentSteps.push({
8887
- type: "cwd-change",
8888
- segment: redactEnvAssignmentsInString(segment.join(" ")),
8889
- effectiveCwdNowUnknown: true
8890
- });
8941
+ const nextCwd2 = resolveCwdAfterSegment(segment, effectiveCwd);
8942
+ if (nextCwd2 !== undefined) {
8943
+ if (nextCwd2 !== null) {
8944
+ effectiveCwd = nextCwd2;
8945
+ trace.segments.push({ index: i, steps: segmentSteps });
8946
+ continue;
8947
+ }
8948
+ segmentSteps.push(cwdChangeStep(segment));
8891
8949
  effectiveCwd = null;
8892
8950
  }
8893
8951
  trace.segments.push({ index: i, steps: segmentSteps });
@@ -8903,12 +8961,15 @@ function explainCommand2(command2, options2) {
8903
8961
  blockReason = result.reason;
8904
8962
  blockSegment = redactEnvAssignmentsInString(segment.join(" "));
8905
8963
  }
8906
- if (segmentChangesCwd(segment)) {
8907
- segmentSteps.push({
8908
- type: "cwd-change",
8909
- segment: redactEnvAssignmentsInString(segment.join(" ")),
8910
- effectiveCwdNowUnknown: true
8911
- });
8964
+ const nextCwd = resolveCwdAfterSegment(segment, effectiveCwd);
8965
+ if (nextCwd !== undefined) {
8966
+ if (nextCwd !== null) {
8967
+ effectiveCwd = nextCwd;
8968
+ applyShellGitContextEnvSegment(segment, shellGitContextState);
8969
+ trace.segments.push({ index: i, steps: segmentSteps });
8970
+ continue;
8971
+ }
8972
+ segmentSteps.push(cwdChangeStep(segment));
8912
8973
  effectiveCwd = null;
8913
8974
  }
8914
8975
  applyShellGitContextEnvSegment(segment, shellGitContextState);
@@ -8924,6 +8985,13 @@ function explainCommand2(command2, options2) {
8924
8985
  configValid
8925
8986
  };
8926
8987
  }
8988
+ function cwdChangeStep(segment) {
8989
+ return {
8990
+ type: "cwd-change",
8991
+ segment: redactEnvAssignmentsInString(segment.join(" ")),
8992
+ effectiveCwdNowUnknown: true
8993
+ };
8994
+ }
8927
8995
  function getCustomRuleMetadata(reason, options2, cwd) {
8928
8996
  const id = reason?.match(/^\[([^\]]+)]/)?.[1];
8929
8997
  if (!id)
@@ -9327,7 +9395,7 @@ function formatTraceJson(result) {
9327
9395
  return JSON.stringify(result, null, 2);
9328
9396
  }
9329
9397
  // src/bin/help.ts
9330
- var version = "1.0.1";
9398
+ var version = "1.0.2";
9331
9399
  var INDENT = " ";
9332
9400
  var PROGRAM_NAME = "cc-safety-net";
9333
9401
  function formatOptionFlags(option) {
@@ -8,3 +8,4 @@ export type InternalOptions = AnalyzeOptions & {
8
8
  };
9
9
  export declare function analyzeSegment(tokens: string[], depth: number, options: InternalOptions): string | null;
10
10
  export declare function segmentChangesCwd(segment: readonly string[]): boolean;
11
+ export declare function resolveCwdAfterSegment(segment: readonly string[], cwd: string | null | undefined): string | null | undefined;
package/dist/index.js CHANGED
@@ -282,6 +282,10 @@ function dangerousInText(text) {
282
282
  return null;
283
283
  }
284
284
 
285
+ // src/core/analyze/segment.ts
286
+ import { realpathSync as realpathSync6 } from "node:fs";
287
+ import { normalize as normalize3 } from "node:path";
288
+
285
289
  // src/core/analyze/awk.ts
286
290
  var AWK_INTERPRETERS = new Set(["awk", "gawk", "nawk", "mawk"]);
287
291
  var REASON_AWK_SYSTEM_DYNAMIC = "Detected awk system() call with dynamic command that cannot be safely analyzed.";
@@ -4087,8 +4091,7 @@ function analyzeParallelCommand(context) {
4087
4091
  }
4088
4092
  var CWD_CHANGE_REGEX = /^\s*(?:\$\(\s*)?[({]*\s*(?:command\s+|builtin\s+)?(?:cd|pushd|popd)(?:\s|$)/;
4089
4093
  function segmentChangesCwd(segment) {
4090
- const stripped = stripLeadingGrouping(segment);
4091
- const unwrapped = stripWrappers([...stripped]);
4094
+ const unwrapped = getCwdChangeTokens(segment);
4092
4095
  if (unwrapped.length === 0) {
4093
4096
  return false;
4094
4097
  }
@@ -4107,6 +4110,32 @@ function segmentChangesCwd(segment) {
4107
4110
  const joined = segment.join(" ");
4108
4111
  return CWD_CHANGE_REGEX.test(joined);
4109
4112
  }
4113
+ function resolveCwdAfterSegment(segment, cwd) {
4114
+ if (!segmentChangesCwd(segment)) {
4115
+ return;
4116
+ }
4117
+ if (!cwd) {
4118
+ return null;
4119
+ }
4120
+ const unwrapped = getCwdChangeTokens(segment, cwd);
4121
+ const cdIndex = getCdCommandIndex(unwrapped);
4122
+ if (cdIndex === -1 || unwrapped[cdIndex] !== "cd") {
4123
+ return null;
4124
+ }
4125
+ const target = unwrapped[cdIndex + 1];
4126
+ if (!target || target === "-" || target.includes("$") || target.includes("`")) {
4127
+ return null;
4128
+ }
4129
+ try {
4130
+ const resolved = resolveChdirTarget(cwd, target);
4131
+ if (samePath(resolved, cwd)) {
4132
+ return cwd;
4133
+ }
4134
+ } catch {
4135
+ return null;
4136
+ }
4137
+ return null;
4138
+ }
4110
4139
  function getHeadAfterTimePrefix(tokens, startIndex) {
4111
4140
  let i = startIndex;
4112
4141
  while (tokens[i]?.startsWith("-")) {
@@ -4114,6 +4143,31 @@ function getHeadAfterTimePrefix(tokens, startIndex) {
4114
4143
  }
4115
4144
  return tokens[i] ?? "";
4116
4145
  }
4146
+ function getCdCommandIndex(tokens) {
4147
+ let headIndex = 0;
4148
+ if (tokens[0] === "builtin" && tokens.length > 1) {
4149
+ headIndex = 1;
4150
+ }
4151
+ if (tokens[headIndex] !== "time") {
4152
+ return headIndex;
4153
+ }
4154
+ let i = headIndex + 1;
4155
+ while (tokens[i]?.startsWith("-")) {
4156
+ i++;
4157
+ }
4158
+ return i;
4159
+ }
4160
+ function getCwdChangeTokens(segment, cwd) {
4161
+ const stripped = stripLeadingGrouping(segment);
4162
+ return stripWrappers([...stripped], cwd);
4163
+ }
4164
+ function samePath(a, b) {
4165
+ try {
4166
+ return normalize3(realpathSync6(a)) === normalize3(realpathSync6(b));
4167
+ } catch {
4168
+ return normalize3(a) === normalize3(b);
4169
+ }
4170
+ }
4117
4171
  function stripLeadingGrouping(tokens) {
4118
4172
  let i = 0;
4119
4173
  while (i < tokens.length) {
@@ -4464,8 +4518,9 @@ function analyzeCommandInternal(command2, depth, options2) {
4464
4518
  if (textReason) {
4465
4519
  return { reason: textReason, segment: segmentStr };
4466
4520
  }
4467
- if (segmentChangesCwd(segment)) {
4468
- effectiveCwd = null;
4521
+ const nextCwd2 = resolveCwdAfterSegment(segment, effectiveCwd);
4522
+ if (nextCwd2 !== undefined) {
4523
+ effectiveCwd = nextCwd2;
4469
4524
  }
4470
4525
  continue;
4471
4526
  }
@@ -4487,8 +4542,9 @@ function analyzeCommandInternal(command2, depth, options2) {
4487
4542
  if (reason) {
4488
4543
  return { reason, segment: segmentStr };
4489
4544
  }
4490
- if (segmentChangesCwd(segment)) {
4491
- effectiveCwd = null;
4545
+ const nextCwd = resolveCwdAfterSegment(segment, effectiveCwd);
4546
+ if (nextCwd !== undefined) {
4547
+ effectiveCwd = nextCwd;
4492
4548
  }
4493
4549
  applyShellGitContextEnvSegment(segment, shellGitContextState);
4494
4550
  }
package/dist/pi/index.js CHANGED
@@ -346,6 +346,10 @@ function dangerousInText(text) {
346
346
  return null;
347
347
  }
348
348
 
349
+ // src/core/analyze/segment.ts
350
+ import { realpathSync as realpathSync6 } from "node:fs";
351
+ import { normalize as normalize3 } from "node:path";
352
+
349
353
  // src/core/analyze/awk.ts
350
354
  var AWK_INTERPRETERS = new Set(["awk", "gawk", "nawk", "mawk"]);
351
355
  var REASON_AWK_SYSTEM_DYNAMIC = "Detected awk system() call with dynamic command that cannot be safely analyzed.";
@@ -4151,8 +4155,7 @@ function analyzeParallelCommand(context) {
4151
4155
  }
4152
4156
  var CWD_CHANGE_REGEX = /^\s*(?:\$\(\s*)?[({]*\s*(?:command\s+|builtin\s+)?(?:cd|pushd|popd)(?:\s|$)/;
4153
4157
  function segmentChangesCwd(segment) {
4154
- const stripped = stripLeadingGrouping(segment);
4155
- const unwrapped = stripWrappers([...stripped]);
4158
+ const unwrapped = getCwdChangeTokens(segment);
4156
4159
  if (unwrapped.length === 0) {
4157
4160
  return false;
4158
4161
  }
@@ -4171,6 +4174,32 @@ function segmentChangesCwd(segment) {
4171
4174
  const joined = segment.join(" ");
4172
4175
  return CWD_CHANGE_REGEX.test(joined);
4173
4176
  }
4177
+ function resolveCwdAfterSegment(segment, cwd) {
4178
+ if (!segmentChangesCwd(segment)) {
4179
+ return;
4180
+ }
4181
+ if (!cwd) {
4182
+ return null;
4183
+ }
4184
+ const unwrapped = getCwdChangeTokens(segment, cwd);
4185
+ const cdIndex = getCdCommandIndex(unwrapped);
4186
+ if (cdIndex === -1 || unwrapped[cdIndex] !== "cd") {
4187
+ return null;
4188
+ }
4189
+ const target = unwrapped[cdIndex + 1];
4190
+ if (!target || target === "-" || target.includes("$") || target.includes("`")) {
4191
+ return null;
4192
+ }
4193
+ try {
4194
+ const resolved = resolveChdirTarget(cwd, target);
4195
+ if (samePath(resolved, cwd)) {
4196
+ return cwd;
4197
+ }
4198
+ } catch {
4199
+ return null;
4200
+ }
4201
+ return null;
4202
+ }
4174
4203
  function getHeadAfterTimePrefix(tokens, startIndex) {
4175
4204
  let i = startIndex;
4176
4205
  while (tokens[i]?.startsWith("-")) {
@@ -4178,6 +4207,31 @@ function getHeadAfterTimePrefix(tokens, startIndex) {
4178
4207
  }
4179
4208
  return tokens[i] ?? "";
4180
4209
  }
4210
+ function getCdCommandIndex(tokens) {
4211
+ let headIndex = 0;
4212
+ if (tokens[0] === "builtin" && tokens.length > 1) {
4213
+ headIndex = 1;
4214
+ }
4215
+ if (tokens[headIndex] !== "time") {
4216
+ return headIndex;
4217
+ }
4218
+ let i = headIndex + 1;
4219
+ while (tokens[i]?.startsWith("-")) {
4220
+ i++;
4221
+ }
4222
+ return i;
4223
+ }
4224
+ function getCwdChangeTokens(segment, cwd) {
4225
+ const stripped = stripLeadingGrouping(segment);
4226
+ return stripWrappers([...stripped], cwd);
4227
+ }
4228
+ function samePath(a, b) {
4229
+ try {
4230
+ return normalize3(realpathSync6(a)) === normalize3(realpathSync6(b));
4231
+ } catch {
4232
+ return normalize3(a) === normalize3(b);
4233
+ }
4234
+ }
4181
4235
  function stripLeadingGrouping(tokens) {
4182
4236
  let i = 0;
4183
4237
  while (i < tokens.length) {
@@ -4528,8 +4582,9 @@ function analyzeCommandInternal(command2, depth, options2) {
4528
4582
  if (textReason) {
4529
4583
  return { reason: textReason, segment: segmentStr };
4530
4584
  }
4531
- if (segmentChangesCwd(segment)) {
4532
- effectiveCwd = null;
4585
+ const nextCwd2 = resolveCwdAfterSegment(segment, effectiveCwd);
4586
+ if (nextCwd2 !== undefined) {
4587
+ effectiveCwd = nextCwd2;
4533
4588
  }
4534
4589
  continue;
4535
4590
  }
@@ -4551,8 +4606,9 @@ function analyzeCommandInternal(command2, depth, options2) {
4551
4606
  if (reason) {
4552
4607
  return { reason, segment: segmentStr };
4553
4608
  }
4554
- if (segmentChangesCwd(segment)) {
4555
- effectiveCwd = null;
4609
+ const nextCwd = resolveCwdAfterSegment(segment, effectiveCwd);
4610
+ if (nextCwd !== undefined) {
4611
+ effectiveCwd = nextCwd;
4556
4612
  }
4557
4613
  applyShellGitContextEnvSegment(segment, shellGitContextState);
4558
4614
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safety-net",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
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",