clawdi 0.14.49 → 0.14.51

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.
@@ -13,7 +13,7 @@ import re
13
13
  from datetime import UTC, datetime
14
14
  from pathlib import Path
15
15
  from typing import Any
16
- from urllib.parse import parse_qs, urlsplit
16
+ from urllib.parse import parse_qs, quote, urlsplit
17
17
 
18
18
  try: # pragma: no cover - exercised only under mitmdump.
19
19
  from mitmproxy import ctx, http
@@ -408,13 +408,26 @@ def path_matcher_matches(path: str, matcher: dict[str, Any], secrets: dict[str,
408
408
  secret = secrets.get(str(matcher.get("secretRef", "")))
409
409
  if secret is None:
410
410
  return False
411
- expected = f"{matcher.get('prefix', '')}{secret}{matcher.get('suffix', '')}"
412
- if matcher_type == "secretRefEquals":
413
- return path == expected
414
- return path.startswith(expected)
411
+ matched = match_secret_path_prefix(path, matcher, secret)
412
+ return matched is not None and (
413
+ matcher_type == "secretRefPrefix" or matched.end() == len(path)
414
+ )
415
415
  return False
416
416
 
417
417
 
418
+ def match_secret_path_prefix(
419
+ path: str, matcher: dict[str, Any], secret: str
420
+ ) -> re.Match[str] | None:
421
+ # Encode only credential characters, never path structure or incoming escapes.
422
+ parts = [re.escape(str(matcher.get("prefix", "")))]
423
+ for char in secret:
424
+ encoded = quote(char, safe="/\\")
425
+ literal = re.escape(char)
426
+ parts.append(literal if encoded == char else f"(?:(?i:{encoded})|{literal})")
427
+ parts.append(re.escape(str(matcher.get("suffix", ""))))
428
+ return re.match("".join(parts), path)
429
+
430
+
418
431
  def matcher_matches(value: str | None, matcher: Any, secrets: dict[str, str]) -> bool:
419
432
  if not isinstance(matcher, dict):
420
433
  return False
@@ -523,10 +536,12 @@ def apply_path_replace(request_path: str, rewrite: dict[str, Any], secrets: dict
523
536
  split = urlsplit(request_path or "/")
524
537
  prefix = str(replacement.get("prefix", ""))
525
538
  suffix = str(replacement.get("suffix", ""))
526
- expected = f"{prefix}{placeholder}{suffix}"
527
- if not split.path.startswith(expected):
539
+ matched = match_secret_path_prefix(split.path, replacement, placeholder)
540
+ if matched is None:
528
541
  return request_path
529
- replaced_path = f"{prefix}{actual}{suffix}{split.path[len(expected):]}"
542
+ # Keep path characters, but encode data that could start a query or fragment.
543
+ actual = quote(actual, safe="/:@!$&'()*+,;=")
544
+ replaced_path = f"{prefix}{actual}{suffix}{split.path[matched.end():]}"
530
545
  return f"{replaced_path}?{split.query}" if split.query else replaced_path
531
546
 
532
547
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdi",
3
- "version": "0.14.49",
3
+ "version": "0.14.51",
4
4
  "description": "The best home for all your AI agents. Run them in the cloud or connect your own—with their context and tools in one place.",
5
5
  "license": "MIT",
6
6
  "type": "module",