Repository navigation
feat(code-editing): Step 1 — dependency pipeline (install-RCE control) - #60
Merged
Merged
Conversation
The security core's install side. A user's requirements are validated against a
curated pinned+hashed allowlist, installed wheels-only in an isolated mirror-only-
egress build sandbox, and snapshotted into a per-version image. Pure-additive/inert.
services/agent_deps.py:
• GATEKEEPER validate_requirements / resolve_install_set — fail-closed: only
exact name==version from the allowlist passes; rejects unpinned, ranges, markers,
unknown pkg, disallowed version, duplicates, -e/-r/git+/url lines, >20 deps. Base
closure (wayforth-sdk + httpx + httpx's pinned deps) baked into every image (the
§3 prerequisite for Step 2's gateway-only run egress).
• pip_install_command — the install-RCE control: --only-binary=:all: (no setup.py
execution) --require-hashes --no-deps --index-url <mirror>.
• build_egress / run_egress — the TWO-allowlist separation: build = deny 0.0.0.0/0
+ allow [mirror] only (NOT gateway); run = allow [gateway] only (NOT mirror).
• build_agent_image — validate → mirror-only build sandbox → wheels-only hashed
install (capped 180s / 500MB) → create_snapshot → per-version image_ref. Injectable
sandbox factory; default uses the e2b snapshot mechanism proven live in §4.
• caps: MAX_DIRECT_DEPS=20, BUILD_TIMEOUT_S=180, MAX_IMAGE_DELTA_MB=500.
services/agent_deps_lock.json — seed allowlist with REAL sha256 hashes fetched in the
linux container (so wheels match E2B's platform): base closure + requests/bs4/urllib3/
charset-normalizer/python-dateutil/six. Expanded later via a curation queue.
Tests (28): gatekeeper accept + every reject path; base-closure + conflict; lock-file
hashes; wheels-only/hashed/mirror command; two-allowlist separation; build
orchestration via fake sandbox (egress=mirror-only, files+lock written, install ran,
snapshot named per-version, killed); invalid-reqs rejected BEFORE any sandbox spun;
install-failure raises+kills; path-traversal rejected. Full suite green (577 passed).
NOT in this PR (tracked): the private mirror server (infra; DEPS_MIRROR_URL config);
the live §6 ship-gate tests (malicious-package canary etc.) before user-visible; the
E2B Firecracker-token confirmation (launch gate). Step 2 (run-egress lock) builds on
the baked base deps here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses the three pre-merge confirmations: #1 Hash ENFORCEMENT (not just presence): added scripts/deps_live_proof.py — a gated (DEPS_LIVE_PROOF=1) §6 ship-gate that feeds a TAMPERED hash to the real pip command and asserts the install is rejected. Proven live: pip computed the real wheel hash (d909fccc...), compared to the lockfile's tampered 0000..., and failed the install ('do not match'). A swapped artifact IS caught. #2 Parser bypass-resistance (unit tests): -r/recursive mixed with a valid line → rejected, resolve_install_set returns [] (no partial build, no sandbox spun); whitespace around '==' fail-closed (rejected); PEP-503 case+separator normalization consistent (Python_Dateutil / python.dateutil / PYTHON__DATEUTIL all → one key) so the allowlist can't be dodged by casing/spacing. #3 Base-deps bake is REAL (live-proven, in the gated script): pipeline build → create_snapshot → boot a run sandbox (gateway-only egress) with httpx 0.28.1 + wayforth-sdk 0.9.0 importable and PyPI unreachable (000 ec=35). This is the §3 prerequisite that unblocks Step 2's run-egress lock — not assumed. The gated proof also re-checks the two-allowlist separation (build can't reach gateway; run can't reach mirror). 35 agent_deps tests; full suite green (584 passed).
|
🚅 Deployed to the wayforth-pr-60 environment in wayforth
8 services not affected by this PR
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Agent code-editing — Step 1 (the security core's install side). Pure-additive/inert (nothing imports it yet). Review at run-token-money-path intensity — this is the install-time-RCE control.
The gatekeeper (
validate_requirements/resolve_install_set)Fail-closed: only exact
name==versionfrom the curated allowlist passes. Rejects unpinned, ranges, markers, unknown package, disallowed version, duplicates,-e/-r/git+/url lines, and>20deps. The base closure (wayforth-sdk+httpx+ httpx's pinned deps) is baked into every image — the §3 prerequisite so Step 2 can lock run egress to gateway-only (no run-time pip).The install command (
pip_install_command)pip install --only-binary=:all: --require-hashes --no-deps --index-url <mirror> …--only-binary=:all:→ nosetup.py/build hooks execute at install--require-hashes→ exact artifacts--no-deps+ locked closure → no surprise transitive pulls--index-url <mirror>→ resolver can't reach public PyPIThe two-allowlist separation (
build_egress/run_egress)deny 0.0.0.0/0+allow [mirror](not the gateway)allow [gateway](not the mirror)Tested that build can't reach gateway and run can't reach the mirror.
Orchestration (
build_agent_image)validate → mirror-only-egress build sandbox → wheels-only hashed install (capped 180s / 500MB / ≤20 deps) →
create_snapshot→ per-versionimage_ref. Injectable sandbox factory (tests use a fake); the default uses the e2b snapshot mechanism proven live in §4. Invalid requirements are rejected before any sandbox is spun; the build sandbox is always killed; path traversal in file paths is rejected.The lockfile (
agent_deps_lock.json)Seed allowlist with real sha256 hashes fetched inside the linux container (so wheels match E2B's platform): the base closure +
requests/beautifulsoup4/urllib3/charset-normalizer/python-dateutil/six. Expanded later via the curation queue.Tests (28)
Gatekeeper accept + every reject path; base-closure + conflict; lockfile hashes; wheels-only/hashed/mirror command; two-allowlist separation; orchestration via fake sandbox (egress=mirror-only, files+lock written, install ran, snapshot named per-version, killed); invalid reqs rejected before any sandbox; install-failure raises+kills; path-traversal rejected; lockfile loads with hashed base deps. Full suite green: 577 passed.
Explicitly NOT in this PR (tracked)
DEPS_MIRROR_URLis config the code consumes.POSTs externally → blocked at run egress), hash-mismatch / sdist-only / cap-trip — must-pass-before-user-visible, run live like the rotation proof.No merge — your review.
🤖 Generated with Claude Code