@vm0/runner 3.12.1 → 3.12.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.
Files changed (2) hide show
  1. package/index.js +18 -511
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -48,7 +48,9 @@ var runnerPaths = {
48
48
  /** Check if a directory name is a VM workspace */
49
49
  isVmWorkspace: (dirname) => dirname.startsWith(VM_WORKSPACE_PREFIX),
50
50
  /** Extract vmId from workspace directory name */
51
- extractVmId: (dirname) => createVmId(dirname.replace(VM_WORKSPACE_PREFIX, ""))
51
+ extractVmId: (dirname) => createVmId(dirname.replace(VM_WORKSPACE_PREFIX, "")),
52
+ /** VM registry file for proxy IP → run mapping */
53
+ vmRegistry: (baseDir) => path.join(baseDir, "vm-registry.json")
52
54
  };
53
55
  var vmPaths = {
54
56
  /** Firecracker config file (used with --config-file) */
@@ -73,8 +75,6 @@ var snapshotOutputPaths = {
73
75
  var tempPaths = {
74
76
  /** Default proxy CA directory */
75
77
  proxyDir: `${VM0_TMP_PREFIX}-proxy`,
76
- /** VM registry for proxy */
77
- vmRegistry: `${VM0_TMP_PREFIX}-vm-registry.json`,
78
78
  /** Network log file for a run */
79
79
  networkLog: (runId) => `${VM0_TMP_PREFIX}-network-${runId}.jsonl`
80
80
  };
@@ -9195,11 +9195,10 @@ var ENV_LOADER_PATH = "/usr/local/bin/vm0-agent/env-loader.mjs";
9195
9195
  // src/lib/proxy/vm-registry.ts
9196
9196
  import fs6 from "fs";
9197
9197
  var logger5 = createLogger("VMRegistry");
9198
- var DEFAULT_REGISTRY_PATH = tempPaths.vmRegistry;
9199
9198
  var VMRegistry = class {
9200
9199
  registryPath;
9201
9200
  data;
9202
- constructor(registryPath = DEFAULT_REGISTRY_PATH) {
9201
+ constructor(registryPath) {
9203
9202
  this.registryPath = registryPath;
9204
9203
  this.data = this.load();
9205
9204
  }
@@ -9286,7 +9285,9 @@ var VMRegistry = class {
9286
9285
  var globalRegistry = null;
9287
9286
  function getVMRegistry() {
9288
9287
  if (!globalRegistry) {
9289
- globalRegistry = new VMRegistry();
9288
+ throw new Error(
9289
+ "VMRegistry not initialized. Call initVMRegistry(registryPath) first."
9290
+ );
9290
9291
  }
9291
9292
  return globalRegistry;
9292
9293
  }
@@ -9299,502 +9300,16 @@ function initVMRegistry(registryPath) {
9299
9300
  import { spawn as spawn2 } from "child_process";
9300
9301
  import fs7 from "fs";
9301
9302
  import path5 from "path";
9302
-
9303
- // src/lib/proxy/mitm-addon-script.ts
9304
- var RUNNER_MITM_ADDON_SCRIPT = `#!/usr/bin/env python3
9305
- """
9306
- mitmproxy addon for VM0 runner-level network security mode.
9307
-
9308
- This addon runs on the runner HOST (not inside VMs) and:
9309
- 1. Intercepts all HTTPS requests from VMs
9310
- 2. Looks up the source VM's runId and firewall rules from the VM registry
9311
- 3. Evaluates firewall rules (first-match-wins) to ALLOW or DENY
9312
- 4. For MITM mode: Rewrites requests to go through VM0 Proxy endpoint
9313
- 5. For SNI-only mode: Passes through or blocks without decryption
9314
- 6. Logs network activity per-run to JSONL files
9315
- """
9316
- import os
9317
- import json
9318
- import time
9319
- import urllib.parse
9320
- import ipaddress
9321
- import socket
9322
- from mitmproxy import http, ctx, tls
9323
-
9324
-
9325
- # VM0 Proxy configuration from environment
9326
- API_URL = os.environ.get("VM0_API_URL", "https://www.vm0.ai")
9327
- REGISTRY_PATH = os.environ.get("VM0_REGISTRY_PATH", "/tmp/vm0-vm-registry.json")
9328
- VERCEL_BYPASS = os.environ.get("VERCEL_AUTOMATION_BYPASS_SECRET", "")
9329
-
9330
- # Construct proxy URL
9331
- PROXY_URL = f"{API_URL}/api/webhooks/agent/proxy"
9332
-
9333
- # Cache for VM registry (reloaded periodically)
9334
- _registry_cache = {}
9335
- _registry_cache_time = 0
9336
- REGISTRY_CACHE_TTL = 2 # seconds
9337
-
9338
- # Track request start times for latency calculation
9339
- request_start_times = {}
9340
-
9341
-
9342
- def load_registry() -> dict:
9343
- """Load the VM registry from file, with caching."""
9344
- global _registry_cache, _registry_cache_time
9345
-
9346
- now = time.time()
9347
- if now - _registry_cache_time < REGISTRY_CACHE_TTL:
9348
- return _registry_cache
9349
-
9350
- try:
9351
- if os.path.exists(REGISTRY_PATH):
9352
- with open(REGISTRY_PATH, "r") as f:
9353
- data = json.load(f)
9354
- _registry_cache = data.get("vms", {})
9355
- _registry_cache_time = now
9356
- return _registry_cache
9357
- except Exception as e:
9358
- ctx.log.warn(f"Failed to load VM registry: {e}")
9359
-
9360
- return _registry_cache
9361
-
9362
-
9363
- def get_vm_info(client_ip: str) -> dict | None:
9364
- """Look up VM info by client IP address."""
9365
- registry = load_registry()
9366
- return registry.get(client_ip)
9367
-
9368
-
9369
- def get_network_log_path(run_id: str) -> str:
9370
- """Get the network log file path for a run."""
9371
- return f"/tmp/vm0-network-{run_id}.jsonl"
9372
-
9373
-
9374
- def log_network_entry(run_id: str, entry: dict) -> None:
9375
- """Write a network log entry to the per-run JSONL file."""
9376
- if not run_id:
9377
- return
9378
-
9379
- log_path = get_network_log_path(run_id)
9380
- try:
9381
- fd = os.open(log_path, os.O_CREAT | os.O_APPEND | os.O_WRONLY, 0o644)
9382
- try:
9383
- os.write(fd, (json.dumps(entry) + "\\n").encode())
9384
- finally:
9385
- os.close(fd)
9386
- except Exception as e:
9387
- ctx.log.warn(f"Failed to write network log: {e}")
9388
-
9389
-
9390
- def get_original_url(flow: http.HTTPFlow) -> str:
9391
- """Reconstruct the original target URL from the request."""
9392
- scheme = "https" if flow.request.port == 443 else "http"
9393
- host = flow.request.pretty_host
9394
- port = flow.request.port
9395
-
9396
- if (scheme == "https" and port != 443) or (scheme == "http" and port != 80):
9397
- host_with_port = f"{host}:{port}"
9398
- else:
9399
- host_with_port = host
9400
-
9401
- path = flow.request.path
9402
- return f"{scheme}://{host_with_port}{path}"
9403
-
9404
-
9405
- # ============================================================================
9406
- # Firewall Rule Matching
9407
- # ============================================================================
9408
-
9409
- def match_domain(pattern: str, hostname: str) -> bool:
9410
- """
9411
- Match hostname against domain pattern.
9412
- Supports exact match and wildcard prefix (*.example.com).
9413
- """
9414
- if not pattern or not hostname:
9415
- return False
9416
-
9417
- pattern = pattern.lower()
9418
- hostname = hostname.lower()
9419
-
9420
- if pattern.startswith("*."):
9421
- # Wildcard: *.example.com matches sub.example.com, www.example.com
9422
- # Also matches example.com itself (without subdomain)
9423
- suffix = pattern[1:] # .example.com
9424
- base = pattern[2:] # example.com
9425
- return hostname.endswith(suffix) or hostname == base
9426
-
9427
- return hostname == pattern
9428
-
9429
-
9430
- def match_ip(cidr: str, ip_str: str) -> bool:
9431
- """
9432
- Match IP address against CIDR range.
9433
- Supports single IPs (1.2.3.4) and ranges (10.0.0.0/8).
9434
- """
9435
- if not cidr or not ip_str:
9436
- return False
9437
-
9438
- try:
9439
- # Parse CIDR (automatically handles single IPs as /32)
9440
- if "/" not in cidr:
9441
- cidr = f"{cidr}/32"
9442
- network = ipaddress.ip_network(cidr, strict=False)
9443
- ip = ipaddress.ip_address(ip_str)
9444
- return ip in network
9445
- except ValueError:
9446
- return False
9447
-
9448
-
9449
- def resolve_hostname_to_ip(hostname: str) -> str | None:
9450
- """Resolve hostname to IP address for IP-based rule matching."""
9451
- try:
9452
- return socket.gethostbyname(hostname)
9453
- except socket.gaierror:
9454
- return None
9455
-
9456
-
9457
- def evaluate_rules(rules: list, hostname: str, ip_str: str = None) -> tuple[str, str | None]:
9458
- """
9459
- Evaluate firewall rules against hostname/IP.
9460
- Returns (action, matched_rule_description).
9461
-
9462
- Rule evaluation is first-match-wins (top to bottom).
9463
-
9464
- Rule formats:
9465
- - Domain/IP rule: { domain: "*.example.com", action: "ALLOW" }
9466
- - Terminal rule: { final: "DENY" }
9467
- """
9468
- if not rules:
9469
- return ("ALLOW", None) # No rules = allow all
9470
-
9471
- for rule in rules:
9472
- # Final/terminal rule - value is the action
9473
- final_action = rule.get("final")
9474
- if final_action:
9475
- return (final_action, "final")
9476
-
9477
- # Domain rule
9478
- domain = rule.get("domain")
9479
- if domain and match_domain(domain, hostname):
9480
- return (rule.get("action", "DENY"), f"domain:{domain}")
9481
-
9482
- # IP rule
9483
- ip_pattern = rule.get("ip")
9484
- if ip_pattern:
9485
- target_ip = ip_str
9486
- if not target_ip:
9487
- target_ip = resolve_hostname_to_ip(hostname)
9488
- if target_ip and match_ip(ip_pattern, target_ip):
9489
- return (rule.get("action", "DENY"), f"ip:{ip_pattern}")
9490
-
9491
- # No rule matched - default deny (zero-trust)
9492
- return ("DENY", "default")
9493
-
9494
-
9495
- # ============================================================================
9496
- # TLS ClientHello Handler (SNI-only mode)
9497
- # ============================================================================
9498
-
9499
- def tls_clienthello(data: tls.ClientHelloData) -> None:
9500
- """
9501
- Handle TLS ClientHello for SNI-based filtering.
9502
- This is called BEFORE TLS decryption, allowing SNI-only filtering.
9503
- """
9504
- client_ip = data.context.client.peername[0] if data.context.client.peername else None
9505
- if not client_ip:
9506
- return
9507
-
9508
- vm_info = get_vm_info(client_ip)
9509
- if not vm_info:
9510
- # Not a registered VM - pass through without MITM interception
9511
- # This is critical for CIDR-based rules where all VM traffic is redirected
9512
- data.ignore_connection = True
9513
- return
9514
-
9515
- # If MITM is enabled, let the normal flow handle it
9516
- if vm_info.get("mitmEnabled", False):
9517
- return
9518
-
9519
- # SNI-only mode: check rules based on SNI
9520
- sni = data.context.client.sni
9521
- run_id = vm_info.get("runId", "")
9522
- rules = vm_info.get("firewallRules", [])
9523
-
9524
- # Auto-allow VM0 API requests - the agent MUST be able to communicate with VM0
9525
- if API_URL and sni:
9526
- parsed_api = urllib.parse.urlparse(API_URL)
9527
- api_hostname = parsed_api.hostname.lower() if parsed_api.hostname else ""
9528
- sni_lower = sni.lower()
9529
- if api_hostname and (sni_lower == api_hostname or sni_lower.endswith(f".{api_hostname}")):
9530
- ctx.log.info(f"[{run_id}] SNI-only auto-allow VM0 API: {sni}")
9531
- log_network_entry(run_id, {
9532
- "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()),
9533
- "mode": "sni",
9534
- "action": "ALLOW",
9535
- "host": sni,
9536
- "port": 443,
9537
- "rule_matched": "vm0-api",
9538
- })
9539
- data.ignore_connection = True # Pass through without MITM
9540
- return
9541
-
9542
- if not sni:
9543
- # No SNI, can't determine target - block for security
9544
- ctx.log.warn(f"[{run_id}] SNI-only: No SNI in ClientHello, blocking")
9545
- log_network_entry(run_id, {
9546
- "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()),
9547
- "mode": "sni",
9548
- "action": "DENY",
9549
- "host": "",
9550
- "port": 443,
9551
- "rule_matched": "no-sni",
9552
- })
9553
- # Don't set ignore_connection - mitmproxy will attempt MITM handshake
9554
- # Since VM doesn't have CA cert (SNI-only mode), TLS will fail immediately
9555
- return
9556
-
9557
- # Evaluate rules
9558
- action, matched_rule = evaluate_rules(rules, sni)
9559
-
9560
- # Log the connection
9561
- log_network_entry(run_id, {
9562
- "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()),
9563
- "mode": "sni",
9564
- "action": action,
9565
- "host": sni,
9566
- "port": 443,
9567
- "rule_matched": matched_rule,
9568
- })
9569
-
9570
- if action == "ALLOW":
9571
- # Pass through without MITM - mitmproxy will relay without decryption
9572
- ctx.log.info(f"[{run_id}] SNI-only ALLOW: {sni} (rule: {matched_rule})")
9573
- data.ignore_connection = True
9574
- else:
9575
- # Block the connection by NOT setting ignore_connection
9576
- # mitmproxy will attempt MITM handshake, but since VM doesn't have
9577
- # our CA certificate installed (SNI-only mode), the TLS handshake
9578
- # will fail immediately with a certificate error.
9579
- ctx.log.warn(f"[{run_id}] SNI-only DENY: {sni} (rule: {matched_rule})")
9580
- # Client will see: SSL certificate problem / certificate verify failed
9581
-
9582
-
9583
- # ============================================================================
9584
- # HTTP Request Handler (MITM mode)
9585
- # ============================================================================
9586
-
9587
- def request(flow: http.HTTPFlow) -> None:
9588
- """
9589
- Intercept request and apply firewall rules.
9590
- For MITM mode, rewrites allowed requests to VM0 Proxy.
9591
- """
9592
- # Track request start time
9593
- request_start_times[flow.id] = time.time()
9594
-
9595
- # Get client IP (source VM)
9596
- client_ip = flow.client_conn.peername[0] if flow.client_conn.peername else None
9597
-
9598
- if not client_ip:
9599
- ctx.log.warn("No client IP available, passing through")
9600
- return
9601
-
9602
- # Look up VM info from registry
9603
- vm_info = get_vm_info(client_ip)
9604
-
9605
- if not vm_info:
9606
- # Not a registered VM, pass through without proxying
9607
- ctx.log.info(f"No VM registration for {client_ip}, passing through")
9608
- return
9609
-
9610
- run_id = vm_info.get("runId", "")
9611
- sandbox_token = vm_info.get("sandboxToken", "")
9612
- mitm_enabled = vm_info.get("mitmEnabled", False)
9613
- rules = vm_info.get("firewallRules", [])
9614
-
9615
- # Store info for response handler
9616
- flow.metadata["vm_run_id"] = run_id
9617
- flow.metadata["vm_client_ip"] = client_ip
9618
- flow.metadata["vm_mitm_enabled"] = mitm_enabled
9619
-
9620
- # Get target hostname
9621
- hostname = flow.request.pretty_host.lower()
9622
-
9623
- # Auto-allow VM0 API requests - the agent MUST be able to communicate with VM0
9624
- # This is checked before user firewall rules to ensure agent functionality
9625
- if API_URL:
9626
- parsed_api = urllib.parse.urlparse(API_URL)
9627
- api_hostname = parsed_api.hostname.lower() if parsed_api.hostname else ""
9628
- if api_hostname and (hostname == api_hostname or hostname.endswith(f".{api_hostname}")):
9629
- ctx.log.info(f"[{run_id}] Auto-allow VM0 API: {hostname}")
9630
- flow.metadata["firewall_action"] = "ALLOW"
9631
- flow.metadata["firewall_rule"] = "vm0-api"
9632
- # Continue to skip rewrite check below
9633
- flow.metadata["original_url"] = get_original_url(flow)
9634
- flow.metadata["skip_rewrite"] = True
9635
- return
9636
-
9637
- # Evaluate firewall rules
9638
- action, matched_rule = evaluate_rules(rules, hostname)
9639
- flow.metadata["firewall_action"] = action
9640
- flow.metadata["firewall_rule"] = matched_rule
9641
-
9642
- if action == "DENY":
9643
- ctx.log.warn(f"[{run_id}] Firewall DENY: {hostname} (rule: {matched_rule})")
9644
- # Kill the flow and return error response
9645
- flow.response = http.Response.make(
9646
- 403,
9647
- b"Blocked by firewall",
9648
- {"Content-Type": "text/plain"}
9649
- )
9650
- return
9651
-
9652
- # Request is ALLOWED - proceed with processing
9653
-
9654
- # Skip if no API URL configured
9655
- if not API_URL:
9656
- ctx.log.warn("VM0_API_URL not set, passing through")
9657
- return
9658
-
9659
- # Skip rewriting requests already going to VM0 (avoid loops)
9660
- if API_URL in flow.request.pretty_url:
9661
- flow.metadata["original_url"] = flow.request.pretty_url
9662
- flow.metadata["skip_rewrite"] = True
9663
- return
9664
-
9665
- # Skip rewriting requests to trusted storage domains (S3, etc.)
9666
- # S3 presigned URLs have signatures that break when proxied
9667
- TRUSTED_DOMAINS = [
9668
- ".s3.amazonaws.com",
9669
- ".s3-", # Regional S3 endpoints like s3-us-west-2.amazonaws.com
9670
- "s3.amazonaws.com",
9671
- ".r2.cloudflarestorage.com",
9672
- ".storage.googleapis.com",
9673
- ]
9674
- for domain in TRUSTED_DOMAINS:
9675
- if domain in hostname or hostname.endswith(domain.lstrip(".")):
9676
- ctx.log.info(f"[{run_id}] Skipping trusted storage domain: {hostname}")
9677
- flow.metadata["original_url"] = get_original_url(flow)
9678
- flow.metadata["skip_rewrite"] = True
9679
- return
9680
-
9681
- # Get original target URL
9682
- original_url = get_original_url(flow)
9683
- flow.metadata["original_url"] = original_url
9684
-
9685
- # If MITM is not enabled, just allow the request through without rewriting
9686
- if not mitm_enabled:
9687
- ctx.log.info(f"[{run_id}] Firewall ALLOW (no MITM): {hostname}")
9688
- return
9689
-
9690
- # MITM mode: rewrite to VM0 Proxy
9691
- ctx.log.info(f"[{run_id}] Proxying via MITM: {original_url}")
9692
-
9693
- # Parse proxy URL
9694
- parsed = urllib.parse.urlparse(PROXY_URL)
9695
-
9696
- # Build query params
9697
- query_params = {"url": original_url}
9698
- if run_id:
9699
- query_params["runId"] = run_id
9700
- query_string = urllib.parse.urlencode(query_params)
9701
-
9702
- # Rewrite request to proxy
9703
- flow.request.host = parsed.hostname
9704
- flow.request.port = 443 if parsed.scheme == "https" else 80
9705
- flow.request.scheme = parsed.scheme
9706
- flow.request.path = f"{parsed.path}?{query_string}"
9707
-
9708
- # Save original Authorization header before overwriting
9709
- if "Authorization" in flow.request.headers:
9710
- flow.request.headers["x-vm0-original-authorization"] = flow.request.headers["Authorization"]
9711
-
9712
- # Add sandbox authentication token
9713
- if sandbox_token:
9714
- flow.request.headers["Authorization"] = f"Bearer {sandbox_token}"
9715
-
9716
- # Add Vercel bypass header if configured
9717
- if VERCEL_BYPASS:
9718
- flow.request.headers["x-vercel-protection-bypass"] = VERCEL_BYPASS
9719
-
9720
-
9721
- def response(flow: http.HTTPFlow) -> None:
9722
- """
9723
- Handle response and log network activity.
9724
- """
9725
- # Calculate latency
9726
- start_time = request_start_times.pop(flow.id, None)
9727
- latency_ms = int((time.time() - start_time) * 1000) if start_time else 0
9728
-
9729
- # Get stored info
9730
- run_id = flow.metadata.get("vm_run_id", "")
9731
- original_url = flow.metadata.get("original_url", flow.request.pretty_url)
9732
- mitm_enabled = flow.metadata.get("vm_mitm_enabled", False)
9733
- firewall_action = flow.metadata.get("firewall_action", "ALLOW")
9734
- firewall_rule = flow.metadata.get("firewall_rule")
9735
-
9736
- # Calculate sizes
9737
- request_size = len(flow.request.content) if flow.request.content else 0
9738
- response_size = len(flow.response.content) if flow.response and flow.response.content else 0
9739
- status_code = flow.response.status_code if flow.response else 0
9740
-
9741
- # Parse URL for host
9742
- try:
9743
- parsed_url = urllib.parse.urlparse(original_url)
9744
- host = parsed_url.hostname or flow.request.pretty_host
9745
- port = parsed_url.port or (443 if parsed_url.scheme == "https" else 80)
9746
- except:
9747
- host = flow.request.pretty_host
9748
- port = flow.request.port
9749
-
9750
- # Log network entry for this run
9751
- if run_id:
9752
- log_entry = {
9753
- "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()),
9754
- "mode": "mitm" if mitm_enabled else "sni",
9755
- "action": firewall_action,
9756
- "host": host,
9757
- "port": port,
9758
- "rule_matched": firewall_rule,
9759
- }
9760
-
9761
- # Add HTTP details only in MITM mode
9762
- if mitm_enabled:
9763
- log_entry.update({
9764
- "method": flow.request.method,
9765
- "path": flow.request.path.split("?")[0], # Path without query
9766
- "url": original_url,
9767
- "status": status_code,
9768
- "latency_ms": latency_ms,
9769
- "request_size": request_size,
9770
- "response_size": response_size,
9771
- })
9772
-
9773
- log_network_entry(run_id, log_entry)
9774
-
9775
- # Log errors to mitmproxy console
9776
- if flow.response and flow.response.status_code >= 400:
9777
- ctx.log.warn(
9778
- f"[{run_id}] Response {flow.response.status_code}: {original_url}"
9779
- )
9780
-
9781
-
9782
- # mitmproxy addon registration
9783
- addons = [tls_clienthello, request, response]
9784
- `;
9785
-
9786
- // src/lib/proxy/proxy-manager.ts
9787
9303
  var logger6 = createLogger("ProxyManager");
9788
9304
  var DEFAULT_PROXY_OPTIONS = {
9789
- port: 8080,
9790
- registryPath: DEFAULT_REGISTRY_PATH
9305
+ port: 8080
9791
9306
  };
9792
9307
  var ProxyManager = class {
9793
9308
  config;
9794
9309
  process = null;
9795
9310
  isRunning = false;
9796
9311
  constructor(config) {
9797
- const addonPath = path5.join(config.caDir, "mitm_addon.py");
9312
+ const addonPath = path5.join(config.caDir, "mitm-addon.py");
9798
9313
  this.config = {
9799
9314
  ...DEFAULT_PROXY_OPTIONS,
9800
9315
  ...config,
@@ -9817,19 +9332,6 @@ var ProxyManager = class {
9817
9332
  });
9818
9333
  });
9819
9334
  }
9820
- /**
9821
- * Ensure the addon script exists at the configured path
9822
- */
9823
- ensureAddonScript() {
9824
- const addonDir = path5.dirname(this.config.addonPath);
9825
- if (!fs7.existsSync(addonDir)) {
9826
- fs7.mkdirSync(addonDir, { recursive: true });
9827
- }
9828
- fs7.writeFileSync(this.config.addonPath, RUNNER_MITM_ADDON_SCRIPT, {
9829
- mode: 493
9830
- });
9831
- logger6.log(`Addon script written to ${this.config.addonPath}`);
9832
- }
9833
9335
  /**
9834
9336
  * Validate proxy configuration
9835
9337
  */
@@ -9841,7 +9343,9 @@ var ProxyManager = class {
9841
9343
  if (!fs7.existsSync(caCertPath)) {
9842
9344
  throw new Error(`Proxy CA certificate not found: ${caCertPath}`);
9843
9345
  }
9844
- this.ensureAddonScript();
9346
+ if (!fs7.existsSync(this.config.addonPath)) {
9347
+ throw new Error(`Addon script not found: ${this.config.addonPath}`);
9348
+ }
9845
9349
  }
9846
9350
  /**
9847
9351
  * Start mitmproxy
@@ -10588,11 +10092,13 @@ async function setupEnvironment(options) {
10588
10092
  process.exit(1);
10589
10093
  }
10590
10094
  logger12.log("Initializing network proxy...");
10591
- initVMRegistry();
10095
+ const registryPath = runnerPaths.vmRegistry(config.base_dir);
10096
+ initVMRegistry(registryPath);
10592
10097
  const proxyManager = initProxyManager({
10593
10098
  apiUrl: config.server.url,
10594
10099
  port: config.proxy.port,
10595
- caDir: config.proxy.ca_dir
10100
+ caDir: config.proxy.ca_dir,
10101
+ registryPath
10596
10102
  });
10597
10103
  let proxyEnabled = false;
10598
10104
  try {
@@ -11502,6 +11008,7 @@ var benchmarkCommand = new Command4("benchmark").description(
11502
11008
  }
11503
11009
  process.exit(1);
11504
11010
  }
11011
+ initVMRegistry(runnerPaths.vmRegistry(config.base_dir));
11505
11012
  timer.log("Initializing pools...");
11506
11013
  const snapshotConfig = config.firecracker.snapshot;
11507
11014
  await initOverlayPool({
@@ -11747,7 +11254,7 @@ var snapshotCommand = new Command5("snapshot").description("Generate a Firecrack
11747
11254
  );
11748
11255
 
11749
11256
  // src/index.ts
11750
- var version = true ? "3.12.1" : "0.1.0";
11257
+ var version = true ? "3.12.2" : "0.1.0";
11751
11258
  program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
11752
11259
  program.addCommand(startCommand);
11753
11260
  program.addCommand(doctorCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/runner",
3
- "version": "3.12.1",
3
+ "version": "3.12.2",
4
4
  "description": "Self-hosted runner for VM0 agents",
5
5
  "repository": {
6
6
  "type": "git",