Commit graph

21 commits

Author SHA1 Message Date
A
6fda75ccc8
security: validate base64 output in cloud_exec and soak.sh (defense-in-depth) (#2532)
Add base64 character validation ([A-Za-z0-9+/=]) before use in SSH
command strings for gcp.sh, aws.sh, and hetzner.sh cloud_exec
functions -- matching the existing fix in digitalocean.sh (#2528).

Also add a validated _encode_b64 helper to soak.sh and use it for
all Telegram bot token encoding, preventing corrupted base64 from
breaking out of single-quoted SSH command strings.

Closes #2527

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-12 09:32:48 -04:00
A
76399eafd9
security: validate base64 in digitalocean.sh SSH exec (defense-in-depth) (#2528)
Add explicit base64 character validation in _digitalocean_exec after
encoding the command, matching the existing pattern in provision.sh.
This ensures the encoded value contains only [A-Za-z0-9+/=] before
embedding it in the SSH command string.

Note: #2527 (provision.sh base64 validation) was already fixed in a
prior commit — the validation at lines 284-289 already rejects
non-base64 characters and empty output.

Fixes #2526

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 08:16:48 -04:00
A
85a2289bb0
fix(e2e): dynamically calculate DigitalOcean parallel capacity from account limit (#2518)
Previously, _digitalocean_max_parallel() always returned 3, assuming all
quota slots were available. When pre-existing droplets occupy slots, the
batch-3 parallel runs fail with "droplet limit exceeded" API errors.

Now queries /v2/account for the actual droplet_limit and subtracts the
current droplet count to compute available capacity. Falls back to 3 if
the API is unreachable.

-- qa/e2e-tester

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
2026-03-12 02:50:48 -04:00
A
e9f8d5ec2d
fix: secure curl header args and provision.sh export whitelist (fixes #2464, fixes #2465) (#2471)
- Replace `-H "Authorization: Bearer ..."` curl args with temp curl config
  files (`-K`) in digitalocean.sh and hetzner.sh e2e drivers, keeping API
  tokens out of `ps` output
- Replace dangerous-var blocklist in provision.sh with a positive whitelist
  of allowed cloud_headless_env variable names

Agent: complexity-hunter

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-10 17:54:32 -07:00
A
1bddd713ea
fix: base64-encode commands in SSH exec to prevent injection (#2448)
All four SSH-based cloud drivers (aws, digitalocean, gcp, hetzner)
passed the command string directly as an SSH argument, which gets
interpreted by the remote shell. While current callers pass trusted
E2E test code, this creates a security footgun for future changes.

Fix: base64-encode the command locally and decode it on the remote
side before piping to bash. The encoded string contains only safe
characters [A-Za-z0-9+/=], eliminating any injection vector. Stdin
is preserved for callers that pipe data into cloud_exec.

Closes #2432, closes #2433, closes #2434, closes #2435

Agent: complexity-hunter

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
2026-03-10 13:22:33 -04:00
A
47b26deafa
fix: harden Sprite exec against injection via org flags and grep patterns (#2446)
- Replace word-split _sprite_org_flags() call sites with _sprite_cmd()
  helper that uses a proper bash array for the -o flag, eliminating
  injection risk from org names with spaces or shell metacharacters
- Validate _SPRITE_ORG against [A-Za-z0-9_-]+ in _sprite_validate_env
- Use grep -qF (fixed-string) instead of grep -q for app name matching
  to prevent regex metacharacters in names from causing false matches
- Use mktemp for _stderr_tmp in _sprite_exec instead of predictable
  PID-based path (/tmp/sprite-exec-err.$$) to prevent symlink attacks

Closes #2436

Agent: complexity-hunter

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
2026-03-10 10:08:17 -07:00
A
3724bb8ba4
fix: address SSH command injection risks in e2e cloud drivers (#2447)
Add defense-in-depth validation across all e2e cloud driver scripts:

- Validate IP addresses match IPv4 format before use in SSH commands
  (aws, digitalocean, gcp, hetzner)
- Validate SSH username contains only safe characters (gcp)
- Validate resource IDs are numeric before interpolating into API URLs
  (digitalocean droplet IDs, hetzner server IDs)
- URL-encode app name in Hetzner API query parameter to prevent
  query parameter injection
- Validate numeric env vars (INPUT_TEST_TIMEOUT, PROVISION_TIMEOUT,
  INSTALL_WAIT) that get interpolated into remote command strings

Fixes #2432, #2433, #2434, #2435, #2442

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 12:27:47 -04:00
A
c4ae16849d
refactor: remove dead cloud_exec_long and _*_exec_long functions (#2407)
The cloud_exec_long dispatcher in common.sh and all five cloud-specific
_exec_long implementations (aws, digitalocean, gcp, hetzner, sprite)
were defined but never called by any code in the e2e test suite.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 19:39:53 -07:00
A
bd1399c861
fix: use mktemp in _sprite_fix_config to prevent race conditions (#2359)
Replaces ${cfg}.fix$$ temp pattern with mktemp for guaranteed uniqueness.
Both temp file usages in the function are updated.

Fixes #2354

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 18:46:48 -07:00
A
de732fa695
fix: prevent command injection in _sprite_exec via stdin piping (#2329)
Pipe the command via stdin to bash instead of embedding it in a bash -c
string. This eliminates shell injection risk from unquoted cmd parameter,
consistent with _sprite_exec_long in the same file and other cloud drivers.

Fixes #2327

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-08 06:44:19 -04:00
A
1740274323
fix: replace base64 interpolation with stdin piping in all cloud exec_long functions (#2290)
Replace unsafe pattern where base64-encoded commands were interpolated
into remote command strings with secure stdin piping — command data now
travels as stdin rather than as part of the command string, eliminating
injection risk from shell metacharacter interpretation.

Affected functions across all 5 cloud drivers:
- _hetzner_exec_long
- _aws_exec_long
- _gcp_exec_long
- _digitalocean_exec_long
- _sprite_exec_long

Fixes #2286
Fixes #2287

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-07 14:09:15 -05:00
A
035e4bf830
Remove Daytona cloud provider from codebase (#2261)
Simplify the cloud matrix by removing Daytona. All Daytona-specific code,
scripts, tests, and configuration have been removed. Daytona has been moved
to "Previously Considered" in the Cloud Provider Wishlist (#1183) and can
be revived on community demand.

Closes #2260

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 18:53:08 -05:00
A
7cc21e4111
fix(security): quote timeout var and validate numeric in sprite.sh (#2120)
Fixes unquoted ${timeout} in _sprite_exec_long that could allow
command injection if timeout contained shell metacharacters.
Adds numeric validation before use.

Fixes #2117

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-02 16:47:39 -05:00
Ahmed Abushagur
9242d44cbb
fix(e2e): add --force to sprite destroy in teardown (#2100)
Without --force, sprite destroy prompts for confirmation in
non-interactive E2E mode and silently fails ("Ok, come back later!"),
leaving stale instances running indefinitely.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 00:24:43 -08:00
A
548cfdf0b1
fix(security): apply base64 exec escaping to remaining 4 cloud drivers (#2067)
PR #2064 fixed _exec_long shell injection for DigitalOcean and Sprite
but missed the same bash -c '${cmd}' pattern in Hetzner, GCP, AWS, and
Daytona. Apply the same base64-encoding fix to all four.

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-01 11:50:33 -08:00
A
862030b776
fix(security): escape cmd args in _exec_long to prevent shell injection (#2064)
Base64-encode the command before embedding it in bash -c to prevent
single-quote breakout in _sprite_exec_long and _digitalocean_exec_long.

Fixes #2063

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 12:42:27 -05:00
Ahmed Abushagur
45caf4b96b
fix(sprite): fix all 6 Sprite agent installs for E2E (#2057)
* fix(sprite): fix all 6 Sprite agent installs for E2E

- Use `npm install -g --prefix` instead of `npm config set prefix` to
  avoid creating .npmrc that conflicts with nvm on Sprite VMs
- Fix shell environment setup to only modify .bash_profile (not .bashrc)
  so non-interactive bash -c commands retain PATH config
- Add $HOME/.cargo/bin to PATH for zeroclaw (Sprite has no ~/.cargo/env)
- Add $HOME/.local/bin to PATH config for Sprite shell environment
- Add sprite E2E cloud driver with org detection, config corruption fix,
  direct command embedding (not $1 positional), and retry logic
- Fix provision.sh to kill full process tree after timeout (prevents
  orphaned sprite exec sessions from corrupting config)
- Fix verify.sh zeroclaw check to not rely on ~/.cargo/env existing

Tested: 6/6 Sprite agents pass E2E (claude, codex, openclaw, zeroclaw,
opencode, kilocode). Hermes is not in the Sprite manifest.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: biome format - collapse runSprite call to single line

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-01 07:15:09 -05:00
Ahmed Abushagur
cd758589c3
fix(e2e): robust DigitalOcean teardown with retry and deletion confirmation (#2046)
The teardown was doing a single DELETE without --max-time, so connection
timeouts caused HTTP 000 and the droplet was never deleted. When running
6 agents in batches of 3, batch 1's stale droplet caused batch 2 to fail
with "will exceed your droplet limit."

Fix:
- Add --max-time 30 to prevent curl hangs
- Retry DELETE up to 3 times on failure
- Poll the API after DELETE to confirm the droplet is actually gone (up to 60s)
- Remove -f flag from curl so %{http_code} is always captured

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:09:15 -05:00
A
54c7764d03
fix(security): prevent cmd injection in sprite exec via positional args (#2021)
Replace bash -c "${cmd}" with bash -c '$1' _ "${cmd}" so the
command is passed as a positional argument, not interpolated into
the shell string. Same pattern applied to the timeout wrapper.

Fixes #2018

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 09:42:13 -05:00
Ahmed Abushagur
c1e605c884
fix(e2e): increase server sizes and install timeouts (#2014)
E2E tests were failing because agent installs didn't complete within
the default 120s timeout, and small VMs ran out of memory during builds.

- INSTALL_WAIT: 120s → 300s (with per-cloud override via cloud_install_wait)
- AWS: nano_3_0 → medium_3_0 (all agents need 4GB for reliable installs)
- DigitalOcean: s-1vcpu-512mb-10gb → s-2vcpu-2gb, cap at 3 parallel
- GCP: e2-medium → e2-standard-2
- Hetzner: cap at 5 parallel (primary IP limit)
- Sprite: 300s install wait (slower exec than SSH)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-02-28 00:25:36 -08:00
Ahmed Abushagur
627026a26b
feat(e2e): multi-cloud test suite with cloud driver pattern (#2004)
* feat(e2e): multi-cloud test suite with cloud driver pattern

Scale the E2E test suite from AWS-only to all 6 infrastructure clouds
(aws, hetzner, digitalocean, gcp, daytona, sprite) with parallel
execution support.

Architecture:
- Cloud driver pattern: each cloud implements _cloudname_func() functions
- load_cloud_driver() wires cloud-specific functions to generic names
  (cloud_exec, cloud_teardown, etc.)
- Shared orchestration stays in one place, cloud details are isolated

New files:
- sh/e2e/e2e.sh — unified entry point with --cloud flag
- sh/e2e/lib/clouds/{aws,hetzner,digitalocean,gcp,daytona,sprite}.sh

Refactored:
- common.sh — removed AWS constants, added load_cloud_driver()
- provision.sh — cloud-agnostic via cloud_headless_env/cloud_provision_verify
- verify.sh — replaced aws_ssh with cloud_exec/cloud_exec_long
- teardown.sh/cleanup.sh — delegate to cloud driver functions
- aws-e2e.sh — thin wrapper: exec e2e.sh --cloud aws

Usage:
  e2e.sh --cloud aws                     # Single cloud
  e2e.sh --cloud aws --cloud hetzner     # Multiple clouds in parallel
  e2e.sh --cloud all --parallel 3        # All clouds, 3 agents parallel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(e2e): prevent subshell EXIT trap inheritance and single-cloud early exit

- Reset EXIT trap in multi-cloud subshells to prevent LOG_DIR deletion
  before the main process reads log files
- Use `|| true` for single-cloud run_agents_for_cloud to prevent set -e
  from skipping the summary on env validation failure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: default to parallel agent provisioning in e2e tests

All agents within a cloud now run in parallel by default instead of
sequentially. Use --sequential to restore the old behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: cap sprite parallelism, 4GB for openclaw, remove stderr suppression

- Sprite: add _sprite_max_parallel (cap 2 concurrent agents) to avoid
  CLI rate limiting that caused all 6 agents to fail
- AWS: use medium_3_0 (4GB) bundle for openclaw which needs more RAM
- Input tests: remove 2>/dev/null from agent commands so failures
  produce visible error output instead of empty responses
- Add cloud_max_parallel to driver interface, respected by e2e.sh

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use bash instead of sh for exec_long across all cloud drivers

Ubuntu's /bin/sh is dash, which doesn't support bash-specific PATH
sourcing from .spawnrc/.cargo/env. This caused codex and zeroclaw
input tests to fail with "command not found" even though verify passed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: codex input test uses positional prompt, not -q flag

codex CLI takes prompt as positional arg: `codex "PROMPT"`.
The -q flag doesn't exist, causing "Usage:" error output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use codex exec -q for non-interactive input test

codex requires `exec` subcommand for non-interactive mode.
Plain `codex PROMPT` expects a TTY (stdin is not a terminal).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: codex exec takes no -q flag, just positional prompt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use cx23 instead of deprecated cx22 for Hetzner e2e tests

Hetzner deprecated server type cx22 (ID 104). The default now uses cx23.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-02-27 19:28:08 -08:00