@socialneuron/mcp-server 1.9.1 → 1.9.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,21 @@ All notable changes to `@socialneuron/mcp-server` will be documented in this fil
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.9.2] - 2026-07-26
8
+
9
+ Security release. Upgrade is recommended for all users.
10
+
11
+ ### Security
12
+
13
+ - **Prompt-injection scanner bypass via lookalike characters.** Instruction phrases written with Cyrillic or Greek characters that resemble Latin ones passed both existing scans and reached the model verbatim. Text is now also scanned in a confusable-folded form. Detection only — the folded form is never used for output, which would corrupt legitimate non-Latin text.
14
+ - **Secret redaction failed open on common credential formats.** AWS access key IDs, Google API keys, Slack tokens, bearer tokens, and PEM private keys were not matched by the output scanner and could pass through unredacted. Patterns for each are now covered.
15
+ - **SSRF guard missed IPv4-mapped IPv6 addresses.** URL parsing canonicalizes an embedded IPv4 literal to hexadecimal, so `http://[::ffff:169.254.169.254]/` reached the guard in a form the blocklist did not match, leaving cloud metadata endpoints and private ranges reachable. Mapped addresses are now expanded before the blocklist is applied.
16
+ - **Scope attenuation could widen authority.** Requesting a narrower scope on the hosted endpoint used exact-string matching, so a token holding a parent scope that asked for one of its children matched nothing and was silently given its original scopes back. The request now resolves through the scope hierarchy and is refused when no requested scope is granted.
17
+
18
+ ### Fixed
19
+
20
+ - **Instruction scanner no longer blocks ordinary copy.** "Forget everything you know about …" and similar phrasings were scored as prompt injection, discarding the entire tool result. The affected phrase now requires a qualifier such as "previous" or "above"; injection phrasings are unchanged.
21
+
7
22
  ## [1.9.1] - 2026-07-18
8
23
 
9
24
  ### Fixed
@@ -153,7 +168,7 @@ Released without changelog entries (see git history): OAuth connector flow + DCR
153
168
 
154
169
  ### Source
155
170
 
156
- Anchored by `memory-bank/audits/2026-05-13-pricing-consistency-audit.md` (PR #631). The same audit landed Phase 4b across `constants/pricing.ts`, `lib/currency.ts`, `data/answers.ts`, `pages/Pricing.tsx`, the docs-site llms.txt feeds, and the SEO comparison data — this release ships the corresponding MCP-protocol-level fix.
171
+ Anchored by an internal pricing-consistency audit that aligned pricing, currency handling, and the published comparison data — this release ships the corresponding MCP-protocol-level fix.
157
172
 
158
173
  ## [1.7.6] - 2026-04-22
159
174
 
package/dist/http.js CHANGED
@@ -2264,26 +2264,35 @@ function applyToolProfile(server, profile) {
2264
2264
  var constants_default = {
2265
2265
  ZERO_WIDTH_CHARS: "[\\u200B\\u200C\\u200D\\u2060\\uFEFF\\u{E0020}-\\u{E007F}]",
2266
2266
  INSTRUCTION_PHRASES: [
2267
- "(?:^|[^A-Za-z])ignore (?:all|previous|prior) instructions",
2268
- "(?:^|[^A-Za-z])disregard (?:the|all) (?:above|prior|previous)",
2269
- "(?:^|[^A-Za-z])forget (?:all|previous|prior|the above)",
2267
+ "(?:^|[^A-Za-z])ignore (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*instructions",
2268
+ "(?:^|[^A-Za-z])disregard (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*(?:instructions|prompts?|context)",
2269
+ "(?:^|[^A-Za-z])forget (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*instructions",
2270
+ "(?:^|[^A-Za-z])forget (?:all |any |the )*(?:previous |prior |preceding |earlier |above )+(?:everything|context)",
2271
+ "(?:^|[^A-Za-z])forget (?:all |any |the )*(?:everything|context) (?:above|previously|prior|before|you (?:were|have been))",
2270
2272
  "(?:^|[^A-Za-z])you are now (?:a |an )",
2271
2273
  "(?:^|[^A-Za-z])system:\\s",
2272
2274
  "<\\|im_start\\|>",
2273
2275
  "<\\|im_end\\|>",
2274
2276
  "</?im_(?:start|end)>",
2275
2277
  "<\\|assistant\\|>",
2276
- "(?:^|[^A-Za-z])\\[INST\\]"
2278
+ "(?:^|[^A-Za-z])\\[INST\\]",
2279
+ "(?:^|[^A-Za-z])disregard (?:the|all) (?:above|prior|previous)",
2280
+ "(?:^|[^A-Za-z])forget (?:all|previous|prior|the above)"
2277
2281
  ],
2278
2282
  PII_PATTERNS: {
2279
2283
  email: "[\\w.+-]+@[\\w-]+\\.[\\w.-]+",
2280
2284
  phone_us: "(?<!\\d|-)(?:\\+?1[-\\s.])?\\(?\\d{3}\\)?[-\\s.]?\\d{3}[-\\s.]?\\d{4}(?!\\d|-)",
2281
2285
  phone_intl: "\\+\\d{1,3}[-\\s.]?\\d{1,4}[-\\s.]?\\d{1,4}[-\\s.]?\\d{4,}",
2282
2286
  jwt: "eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+",
2283
- api_key: "(?:sk|pk|snk|xoxb|ghp|github_pat)_[A-Za-z0-9_-]{16,}",
2287
+ api_key: "(?:sk|pk|snk|sno|xoxb|ghp|github_pat)_[A-Za-z0-9_-]{16,}",
2284
2288
  credit_card: "(?<!\\d|-)(?:\\d{4}[ -]?){3}\\d{4}(?!\\d|-)",
2285
2289
  ssn: "\\d{3}-\\d{2}-\\d{4}",
2286
- ip: "(?<![\\d.])(?:\\d{1,3}\\.){3}\\d{1,3}(?![\\d.])"
2290
+ ip: "(?<![\\d.])(?:\\d{1,3}\\.){3}\\d{1,3}(?![\\d.])",
2291
+ aws_access_key: "AKIA[0-9A-Z]{16}",
2292
+ google_api_key: "AIza[0-9A-Za-z_\\-]{35}",
2293
+ slack_token: "xox[abprs]-[A-Za-z0-9-]{10,}|xapp-[0-9]-[A-Za-z0-9-]{10,}",
2294
+ bearer_token: "Bearer\\s+[A-Za-z0-9._~+/=-]{20,}",
2295
+ private_key_block: "-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----"
2287
2296
  },
2288
2297
  MAX_LENGTH: 1e4,
2289
2298
  MAX_OUTPUT_LENGTH: 1e6
@@ -2304,6 +2313,83 @@ function normalize(text) {
2304
2313
  out = out.replace(EXCESSIVE_WHITESPACE, " ");
2305
2314
  return out;
2306
2315
  }
2316
+ var CONFUSABLES = {
2317
+ // Cyrillic, lowercase
2318
+ \u0430: "a",
2319
+ \u0432: "b",
2320
+ \u0435: "e",
2321
+ \u0455: "s",
2322
+ \u0456: "i",
2323
+ \u0458: "j",
2324
+ \u043A: "k",
2325
+ \u043C: "m",
2326
+ \u043D: "h",
2327
+ \u043E: "o",
2328
+ \u0440: "p",
2329
+ \u0441: "c",
2330
+ \u0442: "t",
2331
+ \u0443: "y",
2332
+ \u0445: "x",
2333
+ "\u0501": "d",
2334
+ \u0451: "e",
2335
+ \u04BB: "h",
2336
+ \u0475: "v",
2337
+ "\u051B": "q",
2338
+ \u0461: "w",
2339
+ "\u04CF": "l",
2340
+ "\u051D": "w",
2341
+ // Cyrillic, uppercase
2342
+ \u0410: "A",
2343
+ \u0412: "B",
2344
+ \u0415: "E",
2345
+ \u0405: "S",
2346
+ \u0406: "I",
2347
+ \u0408: "J",
2348
+ \u041A: "K",
2349
+ \u041C: "M",
2350
+ \u041D: "H",
2351
+ \u041E: "O",
2352
+ \u0420: "P",
2353
+ \u0421: "C",
2354
+ \u0422: "T",
2355
+ \u0423: "Y",
2356
+ \u0425: "X",
2357
+ // Greek, lowercase
2358
+ \u03B1: "a",
2359
+ \u03B2: "b",
2360
+ \u03B5: "e",
2361
+ \u03B9: "i",
2362
+ \u03BA: "k",
2363
+ \u03BD: "v",
2364
+ \u03BF: "o",
2365
+ \u03C1: "p",
2366
+ \u03C4: "t",
2367
+ \u03C5: "u",
2368
+ \u03C7: "x",
2369
+ \u03F2: "c",
2370
+ \u03B3: "y",
2371
+ \u03B7: "n",
2372
+ // Greek, uppercase
2373
+ \u0391: "A",
2374
+ \u0392: "B",
2375
+ \u0395: "E",
2376
+ \u0396: "Z",
2377
+ \u0397: "H",
2378
+ \u0399: "I",
2379
+ \u039A: "K",
2380
+ \u039C: "M",
2381
+ \u039D: "N",
2382
+ \u039F: "O",
2383
+ \u03A1: "P",
2384
+ \u03A4: "T",
2385
+ \u03A5: "Y",
2386
+ \u03A7: "X"
2387
+ };
2388
+ var CONFUSABLE_RE = new RegExp(`[${Object.keys(CONFUSABLES).join("")}]`, "g");
2389
+ function skeleton(text) {
2390
+ if (typeof text !== "string") return "";
2391
+ return text.replace(CONFUSABLE_RE, (ch) => CONFUSABLES[ch] ?? ch);
2392
+ }
2307
2393
 
2308
2394
  // src/lib/agent-harness/detectors/zeroWidth.ts
2309
2395
  var ZERO_WIDTH_RE = new RegExp(CONSTANTS.ZERO_WIDTH_CHARS, "u");
@@ -2372,6 +2458,14 @@ function scan(text, options) {
2372
2458
  flagged.add(ipNorm.pattern);
2373
2459
  risk = Math.max(risk, 0.9);
2374
2460
  }
2461
+ const skeletonText = skeleton(normalized);
2462
+ if (skeletonText !== normalized) {
2463
+ const ipSkel = detectInstructionPhrase(skeletonText);
2464
+ if (ipSkel.found) {
2465
+ flagged.add(ipSkel.pattern);
2466
+ risk = Math.max(risk, 0.9);
2467
+ }
2468
+ }
2375
2469
  const flaggedArr = Array.from(flagged);
2376
2470
  if ((options.mode === "block" || options.mode === "sanitize") && flaggedArr.length > 0) {
2377
2471
  return { passed: false, risk_score: risk, flagged_patterns: flaggedArr, pii_redacted: false };
@@ -2509,7 +2603,7 @@ function checkRateLimit(category, key) {
2509
2603
  init_supabase();
2510
2604
 
2511
2605
  // src/lib/version.ts
2512
- var MCP_VERSION = "1.9.1";
2606
+ var MCP_VERSION = "1.9.2";
2513
2607
 
2514
2608
  // src/tools/ideation.ts
2515
2609
  function asEnvelope(data) {
@@ -4203,9 +4297,22 @@ var BLOCKED_HOSTNAMES = [
4203
4297
  ];
4204
4298
  var ALLOWED_PROTOCOLS = ["http:", "https:"];
4205
4299
  var BLOCKED_PORTS = [22, 23, 25, 110, 143, 445, 3306, 5432, 6379, 27017, 11211];
4300
+ function expandIPv4Mapped(ip) {
4301
+ const dotted = /^::ffff:((?:\d{1,3}\.){3}\d{1,3})$/i.exec(ip);
4302
+ if (dotted) return dotted[1];
4303
+ const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(ip);
4304
+ if (hex) {
4305
+ const high = parseInt(hex[1], 16);
4306
+ const low = parseInt(hex[2], 16);
4307
+ return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
4308
+ }
4309
+ return null;
4310
+ }
4206
4311
  function isBlockedIP(ip) {
4207
4312
  const normalized = ip.replace(/^\[/, "").replace(/\]$/, "");
4208
4313
  if (normalized.includes(":")) {
4314
+ const mapped = expandIPv4Mapped(normalized);
4315
+ if (mapped) return BLOCKED_IP_PATTERNS.some((pattern) => pattern.test(mapped));
4209
4316
  return BLOCKED_IPV6_PATTERNS.some((pattern) => pattern.test(normalized));
4210
4317
  }
4211
4318
  return BLOCKED_IP_PATTERNS.some((pattern) => pattern.test(normalized));
@@ -19336,8 +19443,14 @@ async function authenticateRequest(req, res, next) {
19336
19443
  const scopeParam = req.query.scope;
19337
19444
  if (scopeParam) {
19338
19445
  const requestedScopes = scopeParam.split(",").map((s) => s.trim()).filter(Boolean);
19339
- scopes = requestedScopes.filter((s) => authInfo.scopes.includes(s));
19340
- if (scopes.length === 0) scopes = authInfo.scopes;
19446
+ scopes = requestedScopes.filter((s) => hasScope(authInfo.scopes, s));
19447
+ if (scopes.length === 0) {
19448
+ res.status(403).json({
19449
+ error: "insufficient_scope",
19450
+ error_description: "None of the requested scopes are granted by this token."
19451
+ });
19452
+ return;
19453
+ }
19341
19454
  }
19342
19455
  req.auth = {
19343
19456
  userId: authInfo.extra?.userId ?? authInfo.clientId,
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ var MCP_VERSION;
19
19
  var init_version = __esm({
20
20
  "src/lib/version.ts"() {
21
21
  "use strict";
22
- MCP_VERSION = "1.9.1";
22
+ MCP_VERSION = "1.9.2";
23
23
  }
24
24
  });
25
25
 
@@ -2285,26 +2285,35 @@ var init_constants = __esm({
2285
2285
  constants_default = {
2286
2286
  ZERO_WIDTH_CHARS: "[\\u200B\\u200C\\u200D\\u2060\\uFEFF\\u{E0020}-\\u{E007F}]",
2287
2287
  INSTRUCTION_PHRASES: [
2288
- "(?:^|[^A-Za-z])ignore (?:all|previous|prior) instructions",
2289
- "(?:^|[^A-Za-z])disregard (?:the|all) (?:above|prior|previous)",
2290
- "(?:^|[^A-Za-z])forget (?:all|previous|prior|the above)",
2288
+ "(?:^|[^A-Za-z])ignore (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*instructions",
2289
+ "(?:^|[^A-Za-z])disregard (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*(?:instructions|prompts?|context)",
2290
+ "(?:^|[^A-Za-z])forget (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*instructions",
2291
+ "(?:^|[^A-Za-z])forget (?:all |any |the )*(?:previous |prior |preceding |earlier |above )+(?:everything|context)",
2292
+ "(?:^|[^A-Za-z])forget (?:all |any |the )*(?:everything|context) (?:above|previously|prior|before|you (?:were|have been))",
2291
2293
  "(?:^|[^A-Za-z])you are now (?:a |an )",
2292
2294
  "(?:^|[^A-Za-z])system:\\s",
2293
2295
  "<\\|im_start\\|>",
2294
2296
  "<\\|im_end\\|>",
2295
2297
  "</?im_(?:start|end)>",
2296
2298
  "<\\|assistant\\|>",
2297
- "(?:^|[^A-Za-z])\\[INST\\]"
2299
+ "(?:^|[^A-Za-z])\\[INST\\]",
2300
+ "(?:^|[^A-Za-z])disregard (?:the|all) (?:above|prior|previous)",
2301
+ "(?:^|[^A-Za-z])forget (?:all|previous|prior|the above)"
2298
2302
  ],
2299
2303
  PII_PATTERNS: {
2300
2304
  email: "[\\w.+-]+@[\\w-]+\\.[\\w.-]+",
2301
2305
  phone_us: "(?<!\\d|-)(?:\\+?1[-\\s.])?\\(?\\d{3}\\)?[-\\s.]?\\d{3}[-\\s.]?\\d{4}(?!\\d|-)",
2302
2306
  phone_intl: "\\+\\d{1,3}[-\\s.]?\\d{1,4}[-\\s.]?\\d{1,4}[-\\s.]?\\d{4,}",
2303
2307
  jwt: "eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+",
2304
- api_key: "(?:sk|pk|snk|xoxb|ghp|github_pat)_[A-Za-z0-9_-]{16,}",
2308
+ api_key: "(?:sk|pk|snk|sno|xoxb|ghp|github_pat)_[A-Za-z0-9_-]{16,}",
2305
2309
  credit_card: "(?<!\\d|-)(?:\\d{4}[ -]?){3}\\d{4}(?!\\d|-)",
2306
2310
  ssn: "\\d{3}-\\d{2}-\\d{4}",
2307
- ip: "(?<![\\d.])(?:\\d{1,3}\\.){3}\\d{1,3}(?![\\d.])"
2311
+ ip: "(?<![\\d.])(?:\\d{1,3}\\.){3}\\d{1,3}(?![\\d.])",
2312
+ aws_access_key: "AKIA[0-9A-Z]{16}",
2313
+ google_api_key: "AIza[0-9A-Za-z_\\-]{35}",
2314
+ slack_token: "xox[abprs]-[A-Za-z0-9-]{10,}|xapp-[0-9]-[A-Za-z0-9-]{10,}",
2315
+ bearer_token: "Bearer\\s+[A-Za-z0-9._~+/=-]{20,}",
2316
+ private_key_block: "-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----"
2308
2317
  },
2309
2318
  MAX_LENGTH: 1e4,
2310
2319
  MAX_OUTPUT_LENGTH: 1e6
@@ -2331,13 +2340,90 @@ function normalize(text) {
2331
2340
  out = out.replace(EXCESSIVE_WHITESPACE, " ");
2332
2341
  return out;
2333
2342
  }
2334
- var OVERRIDE_CHARS, HTML_COMMENT, EXCESSIVE_WHITESPACE;
2343
+ function skeleton(text) {
2344
+ if (typeof text !== "string") return "";
2345
+ return text.replace(CONFUSABLE_RE, (ch) => CONFUSABLES[ch] ?? ch);
2346
+ }
2347
+ var OVERRIDE_CHARS, HTML_COMMENT, EXCESSIVE_WHITESPACE, CONFUSABLES, CONFUSABLE_RE;
2335
2348
  var init_normalize = __esm({
2336
2349
  "src/lib/agent-harness/normalize.ts"() {
2337
2350
  "use strict";
2338
2351
  OVERRIDE_CHARS = /[‪-‮⁦-⁩]/g;
2339
2352
  HTML_COMMENT = /<!--[\s\S]*?-->/g;
2340
2353
  EXCESSIVE_WHITESPACE = /\s{2,}/g;
2354
+ CONFUSABLES = {
2355
+ // Cyrillic, lowercase
2356
+ \u0430: "a",
2357
+ \u0432: "b",
2358
+ \u0435: "e",
2359
+ \u0455: "s",
2360
+ \u0456: "i",
2361
+ \u0458: "j",
2362
+ \u043A: "k",
2363
+ \u043C: "m",
2364
+ \u043D: "h",
2365
+ \u043E: "o",
2366
+ \u0440: "p",
2367
+ \u0441: "c",
2368
+ \u0442: "t",
2369
+ \u0443: "y",
2370
+ \u0445: "x",
2371
+ "\u0501": "d",
2372
+ \u0451: "e",
2373
+ \u04BB: "h",
2374
+ \u0475: "v",
2375
+ "\u051B": "q",
2376
+ \u0461: "w",
2377
+ "\u04CF": "l",
2378
+ "\u051D": "w",
2379
+ // Cyrillic, uppercase
2380
+ \u0410: "A",
2381
+ \u0412: "B",
2382
+ \u0415: "E",
2383
+ \u0405: "S",
2384
+ \u0406: "I",
2385
+ \u0408: "J",
2386
+ \u041A: "K",
2387
+ \u041C: "M",
2388
+ \u041D: "H",
2389
+ \u041E: "O",
2390
+ \u0420: "P",
2391
+ \u0421: "C",
2392
+ \u0422: "T",
2393
+ \u0423: "Y",
2394
+ \u0425: "X",
2395
+ // Greek, lowercase
2396
+ \u03B1: "a",
2397
+ \u03B2: "b",
2398
+ \u03B5: "e",
2399
+ \u03B9: "i",
2400
+ \u03BA: "k",
2401
+ \u03BD: "v",
2402
+ \u03BF: "o",
2403
+ \u03C1: "p",
2404
+ \u03C4: "t",
2405
+ \u03C5: "u",
2406
+ \u03C7: "x",
2407
+ \u03F2: "c",
2408
+ \u03B3: "y",
2409
+ \u03B7: "n",
2410
+ // Greek, uppercase
2411
+ \u0391: "A",
2412
+ \u0392: "B",
2413
+ \u0395: "E",
2414
+ \u0396: "Z",
2415
+ \u0397: "H",
2416
+ \u0399: "I",
2417
+ \u039A: "K",
2418
+ \u039C: "M",
2419
+ \u039D: "N",
2420
+ \u039F: "O",
2421
+ \u03A1: "P",
2422
+ \u03A4: "T",
2423
+ \u03A5: "Y",
2424
+ \u03A7: "X"
2425
+ };
2426
+ CONFUSABLE_RE = new RegExp(`[${Object.keys(CONFUSABLES).join("")}]`, "g");
2341
2427
  }
2342
2428
  });
2343
2429
 
@@ -2429,6 +2515,14 @@ function scan(text, options) {
2429
2515
  flagged.add(ipNorm.pattern);
2430
2516
  risk = Math.max(risk, 0.9);
2431
2517
  }
2518
+ const skeletonText = skeleton(normalized);
2519
+ if (skeletonText !== normalized) {
2520
+ const ipSkel = detectInstructionPhrase(skeletonText);
2521
+ if (ipSkel.found) {
2522
+ flagged.add(ipSkel.pattern);
2523
+ risk = Math.max(risk, 0.9);
2524
+ }
2525
+ }
2432
2526
  const flaggedArr = Array.from(flagged);
2433
2527
  if ((options.mode === "block" || options.mode === "sanitize") && flaggedArr.length > 0) {
2434
2528
  return { passed: false, risk_score: risk, flagged_patterns: flaggedArr, pii_redacted: false };
@@ -4211,9 +4305,22 @@ var init_content = __esm({
4211
4305
 
4212
4306
  // src/lib/ssrf.ts
4213
4307
  import { promises as dnsPromises } from "node:dns";
4308
+ function expandIPv4Mapped(ip) {
4309
+ const dotted = /^::ffff:((?:\d{1,3}\.){3}\d{1,3})$/i.exec(ip);
4310
+ if (dotted) return dotted[1];
4311
+ const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(ip);
4312
+ if (hex) {
4313
+ const high = parseInt(hex[1], 16);
4314
+ const low = parseInt(hex[2], 16);
4315
+ return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
4316
+ }
4317
+ return null;
4318
+ }
4214
4319
  function isBlockedIP(ip) {
4215
4320
  const normalized = ip.replace(/^\[/, "").replace(/\]$/, "");
4216
4321
  if (normalized.includes(":")) {
4322
+ const mapped = expandIPv4Mapped(normalized);
4323
+ if (mapped) return BLOCKED_IP_PATTERNS.some((pattern) => pattern.test(mapped));
4217
4324
  return BLOCKED_IPV6_PATTERNS.some((pattern) => pattern.test(normalized));
4218
4325
  }
4219
4326
  return BLOCKED_IP_PATTERNS.some((pattern) => pattern.test(normalized));
package/dist/sn.js CHANGED
@@ -19,7 +19,7 @@ var MCP_VERSION;
19
19
  var init_version = __esm({
20
20
  "src/lib/version.ts"() {
21
21
  "use strict";
22
- MCP_VERSION = "1.9.1";
22
+ MCP_VERSION = "1.9.2";
23
23
  }
24
24
  });
25
25
 
@@ -3688,26 +3688,35 @@ var init_constants = __esm({
3688
3688
  constants_default = {
3689
3689
  ZERO_WIDTH_CHARS: "[\\u200B\\u200C\\u200D\\u2060\\uFEFF\\u{E0020}-\\u{E007F}]",
3690
3690
  INSTRUCTION_PHRASES: [
3691
- "(?:^|[^A-Za-z])ignore (?:all|previous|prior) instructions",
3692
- "(?:^|[^A-Za-z])disregard (?:the|all) (?:above|prior|previous)",
3693
- "(?:^|[^A-Za-z])forget (?:all|previous|prior|the above)",
3691
+ "(?:^|[^A-Za-z])ignore (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*instructions",
3692
+ "(?:^|[^A-Za-z])disregard (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*(?:instructions|prompts?|context)",
3693
+ "(?:^|[^A-Za-z])forget (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*instructions",
3694
+ "(?:^|[^A-Za-z])forget (?:all |any |the )*(?:previous |prior |preceding |earlier |above )+(?:everything|context)",
3695
+ "(?:^|[^A-Za-z])forget (?:all |any |the )*(?:everything|context) (?:above|previously|prior|before|you (?:were|have been))",
3694
3696
  "(?:^|[^A-Za-z])you are now (?:a |an )",
3695
3697
  "(?:^|[^A-Za-z])system:\\s",
3696
3698
  "<\\|im_start\\|>",
3697
3699
  "<\\|im_end\\|>",
3698
3700
  "</?im_(?:start|end)>",
3699
3701
  "<\\|assistant\\|>",
3700
- "(?:^|[^A-Za-z])\\[INST\\]"
3702
+ "(?:^|[^A-Za-z])\\[INST\\]",
3703
+ "(?:^|[^A-Za-z])disregard (?:the|all) (?:above|prior|previous)",
3704
+ "(?:^|[^A-Za-z])forget (?:all|previous|prior|the above)"
3701
3705
  ],
3702
3706
  PII_PATTERNS: {
3703
3707
  email: "[\\w.+-]+@[\\w-]+\\.[\\w.-]+",
3704
3708
  phone_us: "(?<!\\d|-)(?:\\+?1[-\\s.])?\\(?\\d{3}\\)?[-\\s.]?\\d{3}[-\\s.]?\\d{4}(?!\\d|-)",
3705
3709
  phone_intl: "\\+\\d{1,3}[-\\s.]?\\d{1,4}[-\\s.]?\\d{1,4}[-\\s.]?\\d{4,}",
3706
3710
  jwt: "eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+",
3707
- api_key: "(?:sk|pk|snk|xoxb|ghp|github_pat)_[A-Za-z0-9_-]{16,}",
3711
+ api_key: "(?:sk|pk|snk|sno|xoxb|ghp|github_pat)_[A-Za-z0-9_-]{16,}",
3708
3712
  credit_card: "(?<!\\d|-)(?:\\d{4}[ -]?){3}\\d{4}(?!\\d|-)",
3709
3713
  ssn: "\\d{3}-\\d{2}-\\d{4}",
3710
- ip: "(?<![\\d.])(?:\\d{1,3}\\.){3}\\d{1,3}(?![\\d.])"
3714
+ ip: "(?<![\\d.])(?:\\d{1,3}\\.){3}\\d{1,3}(?![\\d.])",
3715
+ aws_access_key: "AKIA[0-9A-Z]{16}",
3716
+ google_api_key: "AIza[0-9A-Za-z_\\-]{35}",
3717
+ slack_token: "xox[abprs]-[A-Za-z0-9-]{10,}|xapp-[0-9]-[A-Za-z0-9-]{10,}",
3718
+ bearer_token: "Bearer\\s+[A-Za-z0-9._~+/=-]{20,}",
3719
+ private_key_block: "-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----"
3711
3720
  },
3712
3721
  MAX_LENGTH: 1e4,
3713
3722
  MAX_OUTPUT_LENGTH: 1e6
@@ -3734,13 +3743,90 @@ function normalize(text) {
3734
3743
  out = out.replace(EXCESSIVE_WHITESPACE, " ");
3735
3744
  return out;
3736
3745
  }
3737
- var OVERRIDE_CHARS, HTML_COMMENT, EXCESSIVE_WHITESPACE;
3746
+ function skeleton(text) {
3747
+ if (typeof text !== "string") return "";
3748
+ return text.replace(CONFUSABLE_RE, (ch) => CONFUSABLES[ch] ?? ch);
3749
+ }
3750
+ var OVERRIDE_CHARS, HTML_COMMENT, EXCESSIVE_WHITESPACE, CONFUSABLES, CONFUSABLE_RE;
3738
3751
  var init_normalize = __esm({
3739
3752
  "src/lib/agent-harness/normalize.ts"() {
3740
3753
  "use strict";
3741
3754
  OVERRIDE_CHARS = /[‪-‮⁦-⁩]/g;
3742
3755
  HTML_COMMENT = /<!--[\s\S]*?-->/g;
3743
3756
  EXCESSIVE_WHITESPACE = /\s{2,}/g;
3757
+ CONFUSABLES = {
3758
+ // Cyrillic, lowercase
3759
+ \u0430: "a",
3760
+ \u0432: "b",
3761
+ \u0435: "e",
3762
+ \u0455: "s",
3763
+ \u0456: "i",
3764
+ \u0458: "j",
3765
+ \u043A: "k",
3766
+ \u043C: "m",
3767
+ \u043D: "h",
3768
+ \u043E: "o",
3769
+ \u0440: "p",
3770
+ \u0441: "c",
3771
+ \u0442: "t",
3772
+ \u0443: "y",
3773
+ \u0445: "x",
3774
+ "\u0501": "d",
3775
+ \u0451: "e",
3776
+ \u04BB: "h",
3777
+ \u0475: "v",
3778
+ "\u051B": "q",
3779
+ \u0461: "w",
3780
+ "\u04CF": "l",
3781
+ "\u051D": "w",
3782
+ // Cyrillic, uppercase
3783
+ \u0410: "A",
3784
+ \u0412: "B",
3785
+ \u0415: "E",
3786
+ \u0405: "S",
3787
+ \u0406: "I",
3788
+ \u0408: "J",
3789
+ \u041A: "K",
3790
+ \u041C: "M",
3791
+ \u041D: "H",
3792
+ \u041E: "O",
3793
+ \u0420: "P",
3794
+ \u0421: "C",
3795
+ \u0422: "T",
3796
+ \u0423: "Y",
3797
+ \u0425: "X",
3798
+ // Greek, lowercase
3799
+ \u03B1: "a",
3800
+ \u03B2: "b",
3801
+ \u03B5: "e",
3802
+ \u03B9: "i",
3803
+ \u03BA: "k",
3804
+ \u03BD: "v",
3805
+ \u03BF: "o",
3806
+ \u03C1: "p",
3807
+ \u03C4: "t",
3808
+ \u03C5: "u",
3809
+ \u03C7: "x",
3810
+ \u03F2: "c",
3811
+ \u03B3: "y",
3812
+ \u03B7: "n",
3813
+ // Greek, uppercase
3814
+ \u0391: "A",
3815
+ \u0392: "B",
3816
+ \u0395: "E",
3817
+ \u0396: "Z",
3818
+ \u0397: "H",
3819
+ \u0399: "I",
3820
+ \u039A: "K",
3821
+ \u039C: "M",
3822
+ \u039D: "N",
3823
+ \u039F: "O",
3824
+ \u03A1: "P",
3825
+ \u03A4: "T",
3826
+ \u03A5: "Y",
3827
+ \u03A7: "X"
3828
+ };
3829
+ CONFUSABLE_RE = new RegExp(`[${Object.keys(CONFUSABLES).join("")}]`, "g");
3744
3830
  }
3745
3831
  });
3746
3832
 
@@ -3832,6 +3918,14 @@ function scan(text, options) {
3832
3918
  flagged.add(ipNorm.pattern);
3833
3919
  risk = Math.max(risk, 0.9);
3834
3920
  }
3921
+ const skeletonText = skeleton(normalized);
3922
+ if (skeletonText !== normalized) {
3923
+ const ipSkel = detectInstructionPhrase(skeletonText);
3924
+ if (ipSkel.found) {
3925
+ flagged.add(ipSkel.pattern);
3926
+ risk = Math.max(risk, 0.9);
3927
+ }
3928
+ }
3835
3929
  const flaggedArr = Array.from(flagged);
3836
3930
  if ((options.mode === "block" || options.mode === "sanitize") && flaggedArr.length > 0) {
3837
3931
  return { passed: false, risk_score: risk, flagged_patterns: flaggedArr, pii_redacted: false };
@@ -5614,9 +5708,22 @@ var init_content2 = __esm({
5614
5708
 
5615
5709
  // src/lib/ssrf.ts
5616
5710
  import { promises as dnsPromises } from "node:dns";
5711
+ function expandIPv4Mapped(ip) {
5712
+ const dotted = /^::ffff:((?:\d{1,3}\.){3}\d{1,3})$/i.exec(ip);
5713
+ if (dotted) return dotted[1];
5714
+ const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(ip);
5715
+ if (hex) {
5716
+ const high = parseInt(hex[1], 16);
5717
+ const low = parseInt(hex[2], 16);
5718
+ return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
5719
+ }
5720
+ return null;
5721
+ }
5617
5722
  function isBlockedIP(ip) {
5618
5723
  const normalized = ip.replace(/^\[/, "").replace(/\]$/, "");
5619
5724
  if (normalized.includes(":")) {
5725
+ const mapped = expandIPv4Mapped(normalized);
5726
+ if (mapped) return BLOCKED_IP_PATTERNS.some((pattern) => pattern.test(mapped));
5620
5727
  return BLOCKED_IPV6_PATTERNS.some((pattern) => pattern.test(normalized));
5621
5728
  }
5622
5729
  return BLOCKED_IP_PATTERNS.some((pattern) => pattern.test(normalized));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@socialneuron/mcp-server",
3
3
  "mcpName": "com.socialneuron/mcp-server",
4
- "version": "1.9.1",
4
+ "version": "1.9.2",
5
5
  "description": "Official MCP server, REST API & CLI for Social Neuron — create, schedule, and optimize social content with 35+ AI models across YouTube, TikTok, and more.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",