@kaminari-ad/mcp 0.16.0 → 0.17.0

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/CHANGELOG.md CHANGED
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.17.0] - 2026-09-03
11
+
12
+ ### Added
13
+
14
+ - **`regexp_request_body` — rules can match what a page's sub-resources
15
+ contain.** The three custom-rule writers (`create_custom_rule`,
16
+ `update_custom_rule`, `test_custom_rule`) now accept the new rule type
17
+ and enforce its contract locally: the same `{pattern, flags}` config as
18
+ `regexp_request_url` and the same fixed `page` target, rejected before
19
+ the request leaves the process.
20
+
21
+ It exists because address-based matching stopped being enough. The
22
+ malvertising kits rotate every filename on every visit, so a
23
+ `regexp_request_url` pattern matches the names it was written against
24
+ and nothing after; the code inside those files changes far more slowly.
25
+
26
+ Two limits the tool descriptions now state, because both look like a
27
+ non-match when they bite: only the scripts, fetch/XHR responses and
28
+ iframe documents are captured (never images, video, fonts or
29
+ stylesheets), up to 400 resources, 128 KB each and 8 MB per scan — and
30
+ the captured contents are kept for **one day**, so
31
+ `test_custom_rule` against an older scan reports no match with nothing
32
+ left to read.
33
+
34
+ Needs the api and crawler sides deployed first.
35
+
36
+ ### Changed
37
+
38
+ - The internal request-URL rule guard is now a shared pattern-rule guard
39
+ covering both types, so their contracts cannot drift apart.
40
+
10
41
  ## [0.16.0] - 2026-08-26
11
42
 
12
43
  ### Added
package/dist/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { NAME, VERSION, err, ok } from './chunk-LTUO4SP6.js';
2
+ import { NAME, VERSION, err, ok } from './chunk-OX65SNEN.js';
3
3
  import process from 'process';
4
4
  import { z } from 'zod';
5
5
 
@@ -160,10 +160,10 @@ async function main() {
160
160
  }
161
161
  const config = configResult.value;
162
162
  if (config.transport === "stdio") {
163
- const { bootstrapStdio } = await import('./stdio-bootstrap-LLCHIZ4X.js');
163
+ const { bootstrapStdio } = await import('./stdio-bootstrap-NVDBVJ7K.js');
164
164
  return bootstrapStdio(config);
165
165
  }
166
- const { bootstrapHttp } = await import('./http-bootstrap-PDCTS4IS.js');
166
+ const { bootstrapHttp } = await import('./http-bootstrap-DKGGQURI.js');
167
167
  return bootstrapHttp(config);
168
168
  }
169
169
  main().then(
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { err, ok } from './chunk-LTUO4SP6.js';
2
+ import { err, ok } from './chunk-OX65SNEN.js';
3
3
  import { createHash, randomUUID } from 'crypto';
4
4
  import createClient from 'openapi-fetch';
5
5
  import { fetch } from 'undici';
@@ -4223,28 +4223,32 @@ var updateCampaignTool = {
4223
4223
  return ok(result.value);
4224
4224
  }
4225
4225
  };
4226
- var requestUrlRuleConfigSchema = z.object({
4227
- pattern: z.string().min(1).max(4096).describe("Non-empty request-URL regular expression, at most 4,096 characters."),
4226
+ var PATTERN_RULE_TYPES = ["regexp_request_url", "regexp_request_body"];
4227
+ var patternRuleConfigSchema = z.object({
4228
+ pattern: z.string().min(1).max(4096).describe("Non-empty regular expression, at most 4,096 characters."),
4228
4229
  flags: z.enum(["", "i"]).optional().describe("Omit or use '' for case-sensitive matching; use 'i' to ignore case.")
4229
4230
  }).strict();
4230
- var REQUEST_URL_RULE_CONFIG_DOC = "For `rule_type='regexp_request_url'`, `config` must be exactly `{ pattern: string, flags?: '' | 'i' }`: pattern is non-empty and at most 4,096 characters; omit flags or use `''` for case-sensitive matching, or `'i'` for case-insensitive matching. `target` must be `'page'`. Fresh scans inspect up to 5,000 captured request URLs. Tests and rechecks of stored scans reconstruct main-frame hops plus up to 200 persisted subrequests with selected resource types omitted, so historical matching is best-effort.";
4231
- function requestUrlRuleInputError(input) {
4232
- if (input.rule_type !== "regexp_request_url") return null;
4231
+ var PATTERN_RULE_CONFIG_DOC = "For `rule_type='regexp_request_url'` and `rule_type='regexp_request_body'`, `config` must be exactly `{ pattern: string, flags?: '' | 'i' }`: pattern is non-empty and at most 4,096 characters; omit flags or use `''` for case-sensitive matching, or `'i'` for case-insensitive matching. `target` must be `'page'` for both. `regexp_request_url` inspects up to 5,000 captured request URLs on a fresh scan; tests and rechecks of stored scans reconstruct main-frame hops plus up to 200 persisted subrequests with selected resource types omitted, so historical matching is best-effort. `regexp_request_body` inspects the CONTENTS of the page's scripts, fetch/XHR responses and iframe documents \u2014 never images, video, fonts or stylesheets \u2014 capped at 400 resources, 128 KB each and 8 MB per scan. Those contents are kept for ONE DAY, so a test or recheck against an older scan reports no match because there is nothing left to read.";
4232
+ function isPatternRuleType(ruleType) {
4233
+ return PATTERN_RULE_TYPES.some((known) => known === ruleType);
4234
+ }
4235
+ function patternRuleInputError(input) {
4236
+ if (!isPatternRuleType(input.rule_type)) return null;
4233
4237
  if (input.target !== void 0 && input.target !== "page") {
4234
4238
  return {
4235
4239
  kind: "invalid-input",
4236
- message: "regexp_request_url requires target='page'.",
4237
- fieldErrors: { target: ["Must be 'page' for regexp_request_url."] }
4240
+ message: `${input.rule_type} requires target='page'.`,
4241
+ fieldErrors: { target: [`Must be 'page' for ${input.rule_type}.`] }
4238
4242
  };
4239
4243
  }
4240
- const parsed = requestUrlRuleConfigSchema.safeParse(input.config);
4244
+ const parsed = patternRuleConfigSchema.safeParse(input.config);
4241
4245
  if (parsed.success) return null;
4242
4246
  const issue = parsed.error.issues[0];
4243
4247
  const field = issue?.path.length ? `config.${issue.path.join(".")}` : "config";
4244
- const message = issue?.message ?? "Invalid request-URL rule config.";
4248
+ const message = issue?.message ?? "Invalid pattern rule config.";
4245
4249
  return {
4246
4250
  kind: "invalid-input",
4247
- message: `Invalid regexp_request_url config: ${message}`,
4251
+ message: `Invalid ${input.rule_type} config: ${message}`,
4248
4252
  fieldErrors: { [field]: [message] }
4249
4253
  };
4250
4254
  }
@@ -4260,10 +4264,7 @@ var ruleConfigField = z.record(z.unknown()).superRefine((config, ctx) => {
4260
4264
  });
4261
4265
  }
4262
4266
  });
4263
- var requestUrlAwareRuleConfigField = z.union([
4264
- requestUrlRuleConfigSchema,
4265
- ruleConfigField
4266
- ]);
4267
+ var patternAwareRuleConfigField = z.union([patternRuleConfigSchema, ruleConfigField]);
4267
4268
 
4268
4269
  // src/application/tools/custom-rules/create-custom-rule.tool.ts
4269
4270
  var CreateCustomRuleInputShape = {
@@ -4274,18 +4275,18 @@ var CreateCustomRuleInputShape = {
4274
4275
  "Tag slug to assign on match. Empty = create-only (advanced). The API auto-registers a custom tag definition for this slug with `display_name = name`. **MUST NOT collide with a built-in system tag slug** (see `list_tags` where `scope=system`); colliding requests return 422 with code `checking.system_slug_reserved`. For `rule_type='llm'` use `config.tags` keys instead and leave `tag_slug` empty."
4275
4276
  ),
4276
4277
  rule_type: z.string().max(50).describe(
4277
- "Rule engine. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `regexp_request_url`, `blacklist_domain`, `combo`, `llm`. `regexp_url` checks redirect-chain URLs only; `regexp_request_url` checks captured network and subresource URLs. The API validates."
4278
+ "Rule engine. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `regexp_request_url`, `regexp_request_body`, `blacklist_domain`, `combo`, `llm`. `regexp_url` checks redirect-chain URLs only; `regexp_request_url` checks captured network and subresource URLs; `regexp_request_body` checks what those sub-resources contained. The API validates."
4278
4279
  ),
4279
- config: requestUrlAwareRuleConfigField.describe(
4280
- "Rule-type-specific configuration object. Shape depends on `rule_type`. " + REQUEST_URL_RULE_CONFIG_DOC + " `regexp_url` remains redirect-chain-only. For `rule_type='llm'` the shape is `{ prompt: string, tags: { <tag_slug>: <description>, ... } }`; each key in `config.tags` is auto-registered as a custom tag definition AND must not collide with a system slug (same 422 contract as `tag_slug`). " + COMBO_MATCH_SCOPE_DOC
4280
+ config: patternAwareRuleConfigField.describe(
4281
+ "Rule-type-specific configuration object. Shape depends on `rule_type`. " + PATTERN_RULE_CONFIG_DOC + " `regexp_url` remains redirect-chain-only. For `rule_type='llm'` the shape is `{ prompt: string, tags: { <tag_slug>: <description>, ... } }`; each key in `config.tags` is auto-registered as a custom tag definition AND must not collide with a system slug (same 422 contract as `tag_slug`). " + COMBO_MATCH_SCOPE_DOC
4281
4282
  ),
4282
4283
  target: z.string().max(30).optional().describe(
4283
- "Where to apply the rule (e.g. 'page' for landing HTML). `regexp_request_url` requires `target='page'`. Default: page. See API docs for the full set of valid values."
4284
+ "Where to apply the rule (e.g. 'page' for landing HTML). `regexp_request_url` and `regexp_request_body` require `target='page'`. Default: page. See API docs for the full set of valid values."
4284
4285
  )
4285
4286
  };
4286
4287
  var createCustomRuleTool = {
4287
4288
  name: "create_custom_rule",
4288
- description: "Define a custom tag-detection rule. Use `rule_type='regexp_request_url'` to match captured network and subresource URLs on the fixed `page` target; fresh scans carry up to 5,000 URLs, while later tests/rechecks use a reduced persisted request tree and are best-effort. `rule_type='regexp_url'` remains redirect-chain-only. The API auto-registers a tag definition for each emitted slug and rejects built-in system-slug collisions with HTTP 422 / `checking.system_slug_reserved`. Matches tag future scans; existing scans are untouched until `recheck_scans`.",
4289
+ description: "Define a custom tag-detection rule. Use `rule_type='regexp_request_url'` to match captured network and subresource URLs on the fixed `page` target; fresh scans carry up to 5,000 URLs, while later tests/rechecks use a reduced persisted request tree and are best-effort. Use `rule_type='regexp_request_body'` to match the CONTENTS of those sub-resources instead \u2014 the right choice when the code you want to catch keeps changing its filename; those contents are kept for one day. `rule_type='regexp_url'` remains redirect-chain-only. The API auto-registers a tag definition for each emitted slug and rejects built-in system-slug collisions with HTTP 422 / `checking.system_slug_reserved`. Matches tag future scans; existing scans are untouched until `recheck_scans`.",
4289
4290
  annotations: {
4290
4291
  title: "Create Custom Rule",
4291
4292
  readOnlyHint: false,
@@ -4295,7 +4296,7 @@ var createCustomRuleTool = {
4295
4296
  },
4296
4297
  inputSchema: z.object(CreateCustomRuleInputShape),
4297
4298
  handler: async (input, ctx) => {
4298
- const inputError = requestUrlRuleInputError(input);
4299
+ const inputError = patternRuleInputError(input);
4299
4300
  if (inputError) return err(inputError);
4300
4301
  const body = {
4301
4302
  name: input.name,
@@ -4370,19 +4371,19 @@ var listCustomRulesTool = {
4370
4371
  };
4371
4372
  var TestCustomRuleInputShape = {
4372
4373
  rule_type: z.string().max(50).describe(
4373
- "Rule engine type. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `regexp_request_url`, `blacklist_domain`, `combo`, `llm`. `regexp_url` checks redirect-chain URLs only; `regexp_request_url` checks captured network and subresource URLs."
4374
+ "Rule engine type. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `regexp_request_url`, `regexp_request_body`, `blacklist_domain`, `combo`, `llm`. `regexp_url` checks redirect-chain URLs only; `regexp_request_url` checks captured network and subresource URLs; `regexp_request_body` checks what those sub-resources contained."
4374
4375
  ),
4375
- config: requestUrlAwareRuleConfigField.describe(
4376
- "Rule-type-specific config to test. Same shape as `create_custom_rule`'s `config`. " + REQUEST_URL_RULE_CONFIG_DOC + " `regexp_url` remains redirect-chain-only. NOTE: `test_custom_rule` evaluates the rule against a scan WITHOUT persisting it, so slug-collision validation does NOT run here \u2014 verify slugs against `list_tags` (`scope=system`) before promoting to `create_custom_rule`. " + COMBO_MATCH_SCOPE_DOC
4376
+ config: patternAwareRuleConfigField.describe(
4377
+ "Rule-type-specific config to test. Same shape as `create_custom_rule`'s `config`. " + PATTERN_RULE_CONFIG_DOC + " `regexp_url` remains redirect-chain-only. NOTE: `test_custom_rule` evaluates the rule against a scan WITHOUT persisting it, so slug-collision validation does NOT run here \u2014 verify slugs against `list_tags` (`scope=system`) before promoting to `create_custom_rule`. " + COMBO_MATCH_SCOPE_DOC
4377
4378
  ),
4378
4379
  target: z.string().max(30).describe(
4379
- "Where to apply the rule (e.g. 'page' for landing HTML). `regexp_request_url` requires `target='page'`. See API docs for the full set of valid values."
4380
+ "Where to apply the rule (e.g. 'page' for landing HTML). `regexp_request_url` and `regexp_request_body` require `target='page'`. See API docs for the full set of valid values."
4380
4381
  ),
4381
4382
  scan_id: z.string().uuid().describe("Existing scan UUID to evaluate the rule against.")
4382
4383
  };
4383
4384
  var testCustomRuleTool = {
4384
4385
  name: "test_custom_rule",
4385
- description: "Preview-test a rule definition against a stored scan without persisting it. For `regexp_request_url`, the historical snapshot contains main-frame hops plus at most 200 persisted subrequests with selected resource types omitted, so a no-match does not prove the original fresh scan lacked the request. Returns match state, elapsed time, and per-tag detail; the preview response does not expose the matched request URL separately. Slug-collision validation does not run in preview mode.",
4386
+ description: "Preview-test a rule definition against a stored scan without persisting it. For `regexp_request_url`, the historical snapshot contains main-frame hops plus at most 200 persisted subrequests with selected resource types omitted, so a no-match does not prove the original fresh scan lacked the request. For `regexp_request_body`, the captured contents live for ONE DAY \u2014 pick a scan from the last 24 hours, because an older one reports no match with nothing left to read. Returns match state, elapsed time, and per-tag detail; the preview response does not expose the matched request URL separately. Slug-collision validation does not run in preview mode.",
4386
4387
  annotations: {
4387
4388
  title: "Test Custom Rule",
4388
4389
  readOnlyHint: true,
@@ -4392,7 +4393,7 @@ var testCustomRuleTool = {
4392
4393
  },
4393
4394
  inputSchema: z.object(TestCustomRuleInputShape),
4394
4395
  handler: async (input, ctx) => {
4395
- const inputError = requestUrlRuleInputError(input);
4396
+ const inputError = patternRuleInputError(input);
4396
4397
  if (inputError) return err(inputError);
4397
4398
  const result = await ctx.api.testCustomRule({
4398
4399
  rule_type: input.rule_type,
@@ -4412,17 +4413,17 @@ var UpdateCustomRuleInputShape = {
4412
4413
  tag_slug: z.string().max(100).optional().describe(
4413
4414
  "New tag slug to assign on match. **MUST NOT collide with a built-in system tag slug** (see `list_tags` where `scope=system`); colliding requests return 422 with code `checking.system_slug_reserved`. Leaving a GLOBAL rule on the same slug preserves its admin-managed tag metadata."
4414
4415
  ),
4415
- config: requestUrlAwareRuleConfigField.optional().describe(
4416
- "New rule-type-specific config object. Replaces the stored config wholesale \u2014 resend every key you want to keep, including a combo rule's `match_scope`. For `rule_type='llm'` the keys of `config.tags` are auto-registered as tag definitions; any key that collides with a system slug returns the same 422 contract. " + REQUEST_URL_RULE_CONFIG_DOC + " Read the rule first because `rule_type` is immutable and is not repeated in this update input. " + COMBO_MATCH_SCOPE_DOC
4416
+ config: patternAwareRuleConfigField.optional().describe(
4417
+ "New rule-type-specific config object. Replaces the stored config wholesale \u2014 resend every key you want to keep, including a combo rule's `match_scope`. For `rule_type='llm'` the keys of `config.tags` are auto-registered as tag definitions; any key that collides with a system slug returns the same 422 contract. " + PATTERN_RULE_CONFIG_DOC + " Read the rule first because `rule_type` is immutable and is not repeated in this update input. " + COMBO_MATCH_SCOPE_DOC
4417
4418
  ),
4418
4419
  target: z.string().max(30).optional().describe(
4419
- "Where to apply the rule. `regexp_request_url` is fixed to `page`; do not change it. See API docs for the valid targets of other rule types."
4420
+ "Where to apply the rule. `regexp_request_url` and `regexp_request_body` are fixed to `page`; do not change it. See API docs for the valid targets of other rule types."
4420
4421
  ),
4421
4422
  is_active: z.boolean().optional().describe("Enable/disable the rule.")
4422
4423
  };
4423
4424
  var updateCustomRuleTool = {
4424
4425
  name: "update_custom_rule",
4425
- description: "Update a custom tag-detection rule. Only supplied fields are sent, but `config` replaces the stored object wholesale; read the rule first and resend every required key. `regexp_request_url` needs a non-empty pattern (max 4,096), flags `''`/`'i'`, and the fixed `page` target. Same-slug GLOBAL rule edits preserve separately managed tag metadata; use `update_tag_definition` to change it. Existing scans are not re-evaluated until `recheck_scans`.",
4426
+ description: "Update a custom tag-detection rule. Only supplied fields are sent, but `config` replaces the stored object wholesale; read the rule first and resend every required key. `regexp_request_url` and `regexp_request_body` need a non-empty pattern (max 4,096), flags `''`/`'i'`, and the fixed `page` target. Same-slug GLOBAL rule edits preserve separately managed tag metadata; use `update_tag_definition` to change it. Existing scans are not re-evaluated until `recheck_scans`.",
4426
4427
  annotations: {
4427
4428
  title: "Update Custom Rule",
4428
4429
  readOnlyHint: false,
@@ -6226,5 +6227,5 @@ function formatToolError(error) {
6226
6227
  }
6227
6228
 
6228
6229
  export { BearerToken, SERVER_INSTRUCTIONS, createHttpApiGateway, createPinoLogger, declareEmptyResourcesAndPrompts, newRequestId, wireToolsIntoMcpServer };
6229
- //# sourceMappingURL=chunk-YYS2HD6K.js.map
6230
- //# sourceMappingURL=chunk-YYS2HD6K.js.map
6230
+ //# sourceMappingURL=chunk-FA5A6Y4H.js.map
6231
+ //# sourceMappingURL=chunk-FA5A6Y4H.js.map