patchpal 0.24.1__tar.gz → 0.24.3__tar.gz

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.
Files changed (70) hide show
  1. {patchpal-0.24.1/patchpal.egg-info → patchpal-0.24.3}/PKG-INFO +1 -1
  2. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/__init__.py +1 -1
  3. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/agent/bedrock_profile_utils.py +26 -1
  4. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/cli/sandbox.py +122 -11
  5. {patchpal-0.24.1 → patchpal-0.24.3/patchpal.egg-info}/PKG-INFO +1 -1
  6. {patchpal-0.24.1 → patchpal-0.24.3}/LICENSE +0 -0
  7. {patchpal-0.24.1 → patchpal-0.24.3}/MANIFEST.in +0 -0
  8. {patchpal-0.24.1 → patchpal-0.24.3}/README.md +0 -0
  9. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/agent/__init__.py +0 -0
  10. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/agent/function_calling.py +0 -0
  11. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/agent/react.py +0 -0
  12. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/cli/__init__.py +0 -0
  13. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/cli/autopilot.py +0 -0
  14. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/cli/interactive.py +0 -0
  15. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/cli/mcp.py +0 -0
  16. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/cli/streaming.py +0 -0
  17. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/config.py +0 -0
  18. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/context.py +0 -0
  19. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/permissions.py +0 -0
  20. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/prompts/react_prompt.md +0 -0
  21. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/prompts/system_prompt.md +0 -0
  22. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/skills.py +0 -0
  23. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/__init__.py +0 -0
  24. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/audit.py +0 -0
  25. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/browser_tools.py +0 -0
  26. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/code_analysis.py +0 -0
  27. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/common.py +0 -0
  28. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/definitions.py +0 -0
  29. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/file_reading.py +0 -0
  30. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/file_writing.py +0 -0
  31. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/find_tool.py +0 -0
  32. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/grep_tool.py +0 -0
  33. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/image_handler.py +0 -0
  34. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/mcp.py +0 -0
  35. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/repo_map.py +0 -0
  36. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/shell_tools.py +0 -0
  37. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/todo_tools.py +0 -0
  38. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/tool_schema.py +0 -0
  39. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/user_interaction.py +0 -0
  40. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal/tools/web_tools.py +0 -0
  41. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal.egg-info/SOURCES.txt +0 -0
  42. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal.egg-info/dependency_links.txt +0 -0
  43. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal.egg-info/entry_points.txt +0 -0
  44. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal.egg-info/requires.txt +0 -0
  45. {patchpal-0.24.1 → patchpal-0.24.3}/patchpal.egg-info/top_level.txt +0 -0
  46. {patchpal-0.24.1 → patchpal-0.24.3}/pyproject.toml +0 -0
  47. {patchpal-0.24.1 → patchpal-0.24.3}/setup.cfg +0 -0
  48. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_agent.py +0 -0
  49. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_cli.py +0 -0
  50. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_config_dynamic.py +0 -0
  51. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_context.py +0 -0
  52. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_custom_tools.py +0 -0
  53. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_enabled_tools.py +0 -0
  54. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_find_tool.py +0 -0
  55. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_guardrails.py +0 -0
  56. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_image_blocking.py +0 -0
  57. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_maximum_security.py +0 -0
  58. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_mcp_config.py +0 -0
  59. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_memory.py +0 -0
  60. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_memory_locations.py +0 -0
  61. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_operational_safety.py +0 -0
  62. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_optional_tools.py +0 -0
  63. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_permissions.py +0 -0
  64. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_react.py +0 -0
  65. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_reasoning_content.py +0 -0
  66. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_repo_map.py +0 -0
  67. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_simplified_prompt.py +0 -0
  68. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_skills.py +0 -0
  69. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_streaming.py +0 -0
  70. {patchpal-0.24.1 → patchpal-0.24.3}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchpal
3
- Version: 0.24.1
3
+ Version: 0.24.3
4
4
  Summary: An agentic coding and automation assistant, supporting both local and cloud LLMs
5
5
  Author: PatchPal Contributors
6
6
  License-Expression: Apache-2.0
@@ -1,6 +1,6 @@
1
1
  """PatchPal - An open-source Claude Code clone implemented purely in Python."""
2
2
 
3
- __version__ = "0.24.1"
3
+ __version__ = "0.24.3"
4
4
 
5
5
  from patchpal.agent import create_agent, create_react_agent
6
6
  from patchpal.cli.autopilot import autopilot_loop
@@ -29,8 +29,33 @@ def _extract_model_from_arn(arn: str) -> str | None:
29
29
  else:
30
30
  region = os.getenv("AWS_REGION_NAME") or os.getenv("AWS_REGION") or "us-east-1"
31
31
 
32
+ # Check for custom Bedrock endpoint (VPC endpoint, custom domain, etc.)
33
+ # The control plane and runtime may use the same VPC endpoint base
34
+ custom_endpoint = (
35
+ os.getenv("AWS_BEDROCK_ENDPOINT")
36
+ or os.getenv("AWS_ENDPOINT_URL_BEDROCK")
37
+ or os.getenv("AWS_ENDPOINT_URL")
38
+ )
39
+
40
+ # CRITICAL FIX: Derive control plane endpoint from runtime VPC endpoint
41
+ # Scenario: User has VPC endpoint for runtime but not explicit control plane endpoint
42
+ # Problem: boto3.client("bedrock") defaults to public endpoint, not VPC endpoint
43
+ # Solution: Automatically derive control plane VPC endpoint from runtime endpoint
44
+ # VPC endpoint pattern:
45
+ # Runtime: vpce-{id}.bedrock-runtime.{region}.vpce.amazonaws.com
46
+ # Control plane: vpce-{id}.bedrock.{region}.vpce.amazonaws.com
47
+ # This matches the automatic derivation in sandbox.py endpoint detection
48
+ if custom_endpoint and "bedrock-runtime" in custom_endpoint:
49
+ # Try to construct control plane VPC endpoint by removing -runtime
50
+ control_endpoint = custom_endpoint.replace("bedrock-runtime", "bedrock")
51
+ else:
52
+ control_endpoint = custom_endpoint
53
+
32
54
  # Create bedrock client
33
- bedrock = boto3.client("bedrock", region_name=region)
55
+ if control_endpoint:
56
+ bedrock = boto3.client("bedrock", region_name=region, endpoint_url=control_endpoint)
57
+ else:
58
+ bedrock = boto3.client("bedrock", region_name=region)
34
59
 
35
60
  # Get inference profile details
36
61
  response = bedrock.get_inference_profile(inferenceProfileIdentifier=arn)
@@ -144,6 +144,7 @@ def detect_llm_endpoints_from_env():
144
144
  "ANTHROPIC_API_BASE",
145
145
  "AWS_BEDROCK_ENDPOINT",
146
146
  "AWS_ENDPOINT_URL_BEDROCK_RUNTIME",
147
+ "AWS_ENDPOINT_URL_BEDROCK", # Control plane endpoint
147
148
  "AWS_ENDPOINT_URL",
148
149
  "AWS_BEDROCK_RUNTIME_ENDPOINT",
149
150
  "AZURE_OPENAI_ENDPOINT",
@@ -162,6 +163,18 @@ def detect_llm_endpoints_from_env():
162
163
  # Ensure it's a valid URL
163
164
  if value.startswith("http://") or value.startswith("https://"):
164
165
  detected_urls.append(value)
166
+
167
+ # CRITICAL FIX: For Bedrock VPC endpoints, derive control plane endpoint
168
+ # Scenario: User has VPC endpoint for runtime API (e.g., AWS GovCloud private network)
169
+ # Problem: Model capability detection needs control plane API (get_inference_profile)
170
+ # but boto3 doesn't automatically derive control plane from runtime endpoint
171
+ # VPC endpoint pattern:
172
+ # Runtime: vpce-{id}.bedrock-runtime.{region}.vpce.amazonaws.com
173
+ # Control plane: vpce-{id}.bedrock.{region}.vpce.amazonaws.com
174
+ # Without this, capability detection hangs trying to reach public control plane endpoint
175
+ if "bedrock-runtime" in value and ".vpce.amazonaws.com" in value:
176
+ control_plane_url = value.replace("bedrock-runtime", "bedrock")
177
+ detected_urls.append(control_plane_url)
165
178
  else:
166
179
  # Assume https if no protocol specified
167
180
  detected_urls.append(f"https://{value}")
@@ -177,18 +190,37 @@ def detect_llm_endpoints_from_env():
177
190
  if aws_region and (
178
191
  os.environ.get("AWS_ACCESS_KEY_ID") or os.environ.get("AWS_SECRET_ACCESS_KEY")
179
192
  ):
180
- # Add Bedrock endpoint for the region
193
+ # Add Bedrock endpoints for the region
181
194
  # Support both GovCloud and standard regions
182
195
  # Also support AWS China regions
196
+
197
+ # IMPORTANT: boto3 uses standard endpoint names by default, even in GovCloud
198
+ # Only use -fips suffix if AWS_USE_FIPS_ENDPOINT=true is explicitly set
199
+ # Scenario 1: Standard GovCloud → bedrock.us-gov-east-1.amazonaws.com (boto3 default)
200
+ # Scenario 2: FIPS required → bedrock-fips.us-gov-east-1.amazonaws.com (explicit opt-in)
201
+ use_fips = os.environ.get("AWS_USE_FIPS_ENDPOINT", "").lower() == "true"
202
+
183
203
  if "gov" in aws_region:
184
- # GovCloud regions use FIPS endpoints
185
- detected_urls.append(f"https://bedrock-runtime-fips.{aws_region}.amazonaws.com")
204
+ # GovCloud regions
205
+ # Add both runtime (for LLM inference) and control plane (for model capability detection)
206
+ if use_fips:
207
+ # FIPS endpoints (only when explicitly requested via AWS_USE_FIPS_ENDPOINT=true)
208
+ detected_urls.append(f"https://bedrock-runtime-fips.{aws_region}.amazonaws.com")
209
+ detected_urls.append(f"https://bedrock-fips.{aws_region}.amazonaws.com")
210
+ else:
211
+ # Standard GovCloud endpoints (boto3 default behavior)
212
+ # This is what boto3 uses unless FIPS is explicitly configured
213
+ detected_urls.append(f"https://bedrock-runtime.{aws_region}.amazonaws.com")
214
+ detected_urls.append(f"https://bedrock.{aws_region}.amazonaws.com")
186
215
  elif "cn-" in aws_region:
187
- # AWS China regions use .amazonaws.com.cn
216
+ # AWS China regions use .amazonaws.com.cn domain
188
217
  detected_urls.append(f"https://bedrock-runtime.{aws_region}.amazonaws.com.cn")
218
+ detected_urls.append(f"https://bedrock.{aws_region}.amazonaws.com.cn")
189
219
  else:
190
- # Standard commercial regions
220
+ # Standard commercial regions (us-east-1, eu-west-1, etc.)
221
+ # Add both runtime (for LLM inference) and control plane (for model capability detection)
191
222
  detected_urls.append(f"https://bedrock-runtime.{aws_region}.amazonaws.com")
223
+ detected_urls.append(f"https://bedrock.{aws_region}.amazonaws.com")
192
224
 
193
225
  # Azure OpenAI: Extract from resource name if AZURE_OPENAI_KEY is set
194
226
  azure_key = os.environ.get("AZURE_OPENAI_KEY") or os.environ.get("AZURE_API_KEY")
@@ -207,8 +239,15 @@ def detect_llm_endpoints_from_env():
207
239
  return list(set(detected_urls))
208
240
 
209
241
 
210
- def build_network_restriction_script(allowed_urls, test_mode=False):
211
- """Build bash script to configure iptables firewall for network restrictions."""
242
+ def build_network_restriction_script(allowed_urls, test_mode=False, block_dns_exfil=True):
243
+ """Build bash script to configure iptables firewall for network restrictions.
244
+
245
+ Args:
246
+ allowed_urls: List of allowed URL endpoints
247
+ test_mode: Whether to test restrictions and exit
248
+ block_dns_exfil: Whether to block DNS record types used for exfiltration (default: True)
249
+ Blocks TXT, NULL, SRV records and large DNS queries
250
+ """
212
251
  script = """
213
252
  echo "=== Pre-downloading required data (before network restrictions) ==="
214
253
 
@@ -276,9 +315,70 @@ fi
276
315
  # Configure iptables
277
316
  echo "Configuring firewall rules..."
278
317
 
279
- # Allow DNS (required for hostname resolution)
280
- iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
281
- iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
318
+ # Extract DNS resolver IP(s) from /etc/resolv.conf
319
+ # This locks DNS queries to only the container's configured resolver,
320
+ # preventing agents from directly querying attacker-controlled DNS servers
321
+ RESOLVER_IPS=$(grep '^nameserver' /etc/resolv.conf | awk '{print $2}')
322
+
323
+ if [ -z "$RESOLVER_IPS" ]; then
324
+ echo "❌ ERROR: Could not detect DNS resolver from /etc/resolv.conf"
325
+ echo "Cannot configure secure DNS filtering without a resolver"
326
+ exit 1
327
+ fi
328
+ """
329
+
330
+ # Add DNS exfiltration blocking if enabled
331
+ if block_dns_exfil:
332
+ script += """
333
+ # Block DNS record types used for data exfiltration (SECURITY ENHANCEMENT)
334
+ # This prevents DNS tunneling attacks that use TXT, NULL, or SRV records
335
+ # to encode and exfiltrate sensitive data through DNS queries
336
+ echo "Blocking DNS record types used for data exfiltration..."
337
+
338
+ # Block TXT records (type 16 = 0x0010)
339
+ # TXT records can carry up to 255 bytes of arbitrary text per record
340
+ # Commonly used for DNS tunneling due to large payload capacity
341
+ iptables -A OUTPUT -p udp --dport 53 -m string --hex-string "|00 10|" --algo bm -j LOG --log-prefix "BLOCKED-DNS-TXT: " --log-level 4
342
+ iptables -A OUTPUT -p udp --dport 53 -m string --hex-string "|00 10|" --algo bm -j DROP
343
+ iptables -A OUTPUT -p tcp --dport 53 -m string --hex-string "|00 10|" --algo bm -j LOG --log-prefix "BLOCKED-DNS-TXT: " --log-level 4
344
+ iptables -A OUTPUT -p tcp --dport 53 -m string --hex-string "|00 10|" --algo bm -j DROP
345
+
346
+ # Block NULL records (type 10 = 0x000a)
347
+ # NULL records are explicitly designed to carry arbitrary binary data
348
+ # No legitimate use in normal DNS resolution
349
+ iptables -A OUTPUT -p udp --dport 53 -m string --hex-string "|00 0a|" --algo bm -j LOG --log-prefix "BLOCKED-DNS-NULL: " --log-level 4
350
+ iptables -A OUTPUT -p udp --dport 53 -m string --hex-string "|00 0a|" --algo bm -j DROP
351
+ iptables -A OUTPUT -p tcp --dport 53 -m string --hex-string "|00 0a|" --algo bm -j DROP
352
+
353
+ # Block SRV records (type 33 = 0x0021)
354
+ # Service records can be abused for tunneling
355
+ iptables -A OUTPUT -p udp --dport 53 -m string --hex-string "|00 21|" --algo bm -j LOG --log-prefix "BLOCKED-DNS-SRV: " --log-level 4
356
+ iptables -A OUTPUT -p udp --dport 53 -m string --hex-string "|00 21|" --algo bm -j DROP
357
+ iptables -A OUTPUT -p tcp --dport 53 -m string --hex-string "|00 21|" --algo bm -j DROP
358
+
359
+ # Block large DNS queries (likely subdomain encoding for data exfiltration)
360
+ # Normal DNS queries: 50-100 bytes
361
+ # Queries with encoded data in subdomains: 200+ bytes
362
+ # This catches attempts to encode data in long subdomain names
363
+ iptables -A OUTPUT -p udp --dport 53 -m length --length 150:65535 -j LOG --log-prefix "BLOCKED-DNS-LARGE: " --log-level 4
364
+ iptables -A OUTPUT -p udp --dport 53 -m length --length 150:65535 -j DROP
365
+ iptables -A OUTPUT -p tcp --dport 53 -m length --length 150:65535 -j DROP
366
+
367
+ echo "✓ DNS exfiltration protections active (TXT/NULL/SRV/LARGE queries blocked)"
368
+ """
369
+
370
+ script += """
371
+ # Allow DNS ONLY to detected resolver IPs (SECURITY FIX)
372
+ # This prevents the OpenAI-style DNS tunneling attack where agents
373
+ # send DNS packets directly to attacker-controlled DNS servers
374
+ echo "Locking DNS queries to container's resolver(s):"
375
+ while IFS= read -r RESOLVER_IP; do
376
+ if [ -n "$RESOLVER_IP" ]; then
377
+ echo " $RESOLVER_IP (DNS - A/AAAA records only)"
378
+ iptables -A OUTPUT -d $RESOLVER_IP -p udp --dport 53 -j ACCEPT
379
+ iptables -A OUTPUT -d $RESOLVER_IP -p tcp --dport 53 -j ACCEPT
380
+ fi
381
+ done <<< "$RESOLVER_IPS"
282
382
 
283
383
  # Allow localhost
284
384
  iptables -A OUTPUT -d 127.0.0.1/8 -j ACCEPT
@@ -830,12 +930,23 @@ SECURITY:
830
930
 
831
931
  FIREWALL RULES:
832
932
  - Only allowed URLs are accessible (auto-detected + --allow-url)
833
- - DNS resolution permitted (required for hostname lookup)
933
+ - DNS resolution permitted (A/AAAA records only for hostname lookup)
934
+ - DNS exfiltration prevention: TXT, NULL, SRV records BLOCKED
935
+ - Large DNS queries (>150 bytes) BLOCKED (prevents subdomain encoding)
834
936
  - Localhost accessible (127.0.0.1/8)
835
937
  - All other network traffic BLOCKED and LOGGED
836
938
  - Web tools and MCP automatically disabled
837
939
  - Ideal for sensitive/regulated environments
838
940
 
941
+ DNS SECURITY:
942
+ * Blocks DNS tunneling attacks that exfiltrate data via:
943
+ - TXT records (up to 255 bytes per query)
944
+ - NULL records (arbitrary binary data)
945
+ - SRV records (service discovery abuse)
946
+ - Long subdomains (base64-encoded data in domain names)
947
+ * Only allows A and AAAA records (standard hostname resolution)
948
+ * Logs all blocked DNS queries for security auditing
949
+
839
950
  Use cases:
840
951
  * Compliance: Meet data governance requirements
841
952
  * Security: Prevent data exfiltration
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchpal
3
- Version: 0.24.1
3
+ Version: 0.24.3
4
4
  Summary: An agentic coding and automation assistant, supporting both local and cloud LLMs
5
5
  Author: PatchPal Contributors
6
6
  License-Expression: Apache-2.0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes