@kaitranntt/ccs 7.79.1-dev.17 → 7.79.1-dev.19

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.
@@ -639,7 +639,7 @@ function getTools() {
639
639
  },
640
640
  },
641
641
  priority: { type: 'integer' },
642
- action: { type: 'string', enum: ['continue', 'fail', 'fulfill'] },
642
+ action: { type: 'string', enum: getInterceptActionEnum() },
643
643
  statusCode: { type: 'integer', minimum: 100, maximum: 599 },
644
644
  responseHeaders: {
645
645
  type: 'array',
@@ -1209,7 +1209,18 @@ function getTools() {
1209
1209
  },
1210
1210
  event: {
1211
1211
  type: 'object',
1212
- description: 'Required event selector.',
1212
+ description:
1213
+ 'Required event selector. request events require urlIncludes; download events require urlIncludes or suggestedFilenameIncludes. URLs in returned details are redacted.',
1214
+ properties: {
1215
+ kind: { type: 'string', enum: ['dialog', 'navigation', 'request', 'download'] },
1216
+ dialogType: { type: 'string' },
1217
+ messageIncludes: { type: 'string' },
1218
+ urlIncludes: { type: 'string' },
1219
+ method: { type: 'string' },
1220
+ suggestedFilenameIncludes: { type: 'string' },
1221
+ },
1222
+ required: ['kind'],
1223
+ additionalProperties: false,
1213
1224
  },
1214
1225
  },
1215
1226
  required: ['event'],
@@ -1292,9 +1303,30 @@ function parseOptionalPageId(toolArgs) {
1292
1303
  : '';
1293
1304
  }
1294
1305
 
1306
+ function getBrowserInterceptFulfillMode() {
1307
+ return String(process.env.CCS_BROWSER_INTERCEPT_FULFILL_MODE || 'disabled').trim() === 'enabled'
1308
+ ? 'enabled'
1309
+ : 'disabled';
1310
+ }
1311
+
1312
+ function isBrowserInterceptFulfillEnabled() {
1313
+ return getBrowserInterceptFulfillMode() === 'enabled';
1314
+ }
1315
+
1316
+ function getInterceptActionEnum() {
1317
+ return isBrowserInterceptFulfillEnabled()
1318
+ ? ['continue', 'fail', 'fulfill']
1319
+ : ['continue', 'fail'];
1320
+ }
1321
+
1295
1322
  function parseInterceptAction(value) {
1323
+ if (value === 'fulfill' && !isBrowserInterceptFulfillEnabled()) {
1324
+ throw new Error(
1325
+ 'action fulfill is disabled by CCS_BROWSER_INTERCEPT_FULFILL_MODE=disabled; set it to enabled only for trusted local testing'
1326
+ );
1327
+ }
1296
1328
  if (value !== 'continue' && value !== 'fail' && value !== 'fulfill') {
1297
- throw new Error('action must be one of: continue, fail, fulfill');
1329
+ throw new Error(`action must be one of: ${getInterceptActionEnum().join(', ')}`);
1298
1330
  }
1299
1331
  return value;
1300
1332
  }
@@ -3327,24 +3359,80 @@ function parseEventCondition(value) {
3327
3359
  };
3328
3360
  }
3329
3361
  if (value.kind === 'request') {
3362
+ const urlIncludes = value.urlIncludes ? String(value.urlIncludes) : undefined;
3363
+ if (!urlIncludes) {
3364
+ throw new Error('request events require urlIncludes to limit network metadata exposure');
3365
+ }
3330
3366
  return {
3331
3367
  kind: 'request',
3332
- urlIncludes: value.urlIncludes ? String(value.urlIncludes) : undefined,
3368
+ urlIncludes,
3333
3369
  method: value.method ? String(value.method) : undefined,
3334
3370
  };
3335
3371
  }
3336
3372
  if (value.kind === 'download') {
3373
+ const urlIncludes = value.urlIncludes ? String(value.urlIncludes) : undefined;
3374
+ const suggestedFilenameIncludes = value.suggestedFilenameIncludes
3375
+ ? String(value.suggestedFilenameIncludes)
3376
+ : undefined;
3377
+ if (!urlIncludes && !suggestedFilenameIncludes) {
3378
+ throw new Error(
3379
+ 'download events require urlIncludes or suggestedFilenameIncludes to limit metadata exposure'
3380
+ );
3381
+ }
3337
3382
  return {
3338
3383
  kind: 'download',
3339
- urlIncludes: value.urlIncludes ? String(value.urlIncludes) : undefined,
3340
- suggestedFilenameIncludes: value.suggestedFilenameIncludes
3341
- ? String(value.suggestedFilenameIncludes)
3342
- : undefined,
3384
+ urlIncludes,
3385
+ suggestedFilenameIncludes,
3343
3386
  };
3344
3387
  }
3345
3388
  throw new Error(`unknown event kind: ${String(value.kind || '')}`);
3346
3389
  }
3347
3390
 
3391
+ function redactUrlForModel(value, options = {}) {
3392
+ const rawUrl = String(value || '');
3393
+ if (!rawUrl) {
3394
+ return '';
3395
+ }
3396
+ try {
3397
+ const url = new URL(rawUrl);
3398
+ if (options.originOnly) {
3399
+ return url.origin;
3400
+ }
3401
+ url.username = '';
3402
+ url.password = '';
3403
+ url.search = '';
3404
+ url.hash = '';
3405
+ return url.toString();
3406
+ } catch {
3407
+ return '[redacted-url]';
3408
+ }
3409
+ }
3410
+
3411
+ function redactObservedEvent(event, observed) {
3412
+ if (!observed || typeof observed !== 'object') {
3413
+ return observed;
3414
+ }
3415
+ if (event.kind === 'request') {
3416
+ return {
3417
+ url: redactUrlForModel(observed.url, { originOnly: true }),
3418
+ method: observed.method || '',
3419
+ };
3420
+ }
3421
+ if (event.kind === 'download') {
3422
+ return {
3423
+ url: redactUrlForModel(observed.url, { originOnly: true }),
3424
+ suggestedFilename: observed.suggestedFilename || '',
3425
+ };
3426
+ }
3427
+ if (event.kind === 'navigation' && Object.prototype.hasOwnProperty.call(observed, 'url')) {
3428
+ return {
3429
+ ...observed,
3430
+ url: redactUrlForModel(observed.url, { originOnly: true }),
3431
+ };
3432
+ }
3433
+ return observed;
3434
+ }
3435
+
3348
3436
  function matchesObservedEvent(event, observed) {
3349
3437
  if (event.kind === 'dialog') {
3350
3438
  return (
@@ -4798,7 +4886,8 @@ async function handleWaitForEvent(toolArgs) {
4798
4886
  );
4799
4887
  const event = parseEventCondition(toolArgs.event);
4800
4888
  const observed = await waitForMatchingEvent({ page, pageIndex, timeoutMs, event });
4801
- return `pageIndex: ${pageIndex}\nevent: ${event.kind}\nstatus: observed\ndetail: ${JSON.stringify(observed)}`;
4889
+ const safeObserved = redactObservedEvent(event, observed);
4890
+ return `pageIndex: ${pageIndex}\nevent: ${event.kind}\nstatus: observed\ndetail: ${JSON.stringify(safeObserved)}`;
4802
4891
  }
4803
4892
 
4804
4893
  function findInterceptRuleIndex(ruleId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaitranntt/ccs",
3
- "version": "7.79.1-dev.17",
3
+ "version": "7.79.1-dev.19",
4
4
  "description": "Claude Code Switch - Instant profile switching between Claude, GLM, Kimi, and more",
5
5
  "keywords": [
6
6
  "cli",