1010
1111#[ path = "common/mod.rs" ]
1212mod common;
13+ #[ path = "docker_common/mod.rs" ]
14+ mod docker_common;
1315use common:: git_sha256;
14-
15- use std:: process:: Command ;
16+ use docker_common:: { assert_vex_agent_attested, run_container, skip_if_no_image} ;
1617
1718use base64:: Engine ;
1819use wiremock:: matchers:: { method, path, path_regex} ;
1920use wiremock:: { Mock , MockServer , ResponseTemplate } ;
2021
22+ const IMAGE : & str = "socket-patch-test-cargo:latest" ;
2123const ORG : & str = "test-org" ;
2224const PURL : & str = "pkg:cargo/cfg-if@1.0.0" ;
2325const UUID : & str = "14141414-1414-4141-8141-141414141414" ;
@@ -31,26 +33,6 @@ const PATCHED_RS: &[u8] = b"// SOCKET-PATCH-E2E-MARKER\n\
3133 #[macro_export]\n \
3234 macro_rules! cfg_if {\n ($($t:tt)*) => {};\n }\n ";
3335
34- /// See docker_e2e_npm.rs::cov_docker_args for the coverage hook
35- /// semantics. The CI coverage-docker job sets the env vars; locally
36- /// they're unset and this returns an empty Vec.
37- fn cov_docker_args ( ) -> Vec < String > {
38- let Ok ( bin) = std:: env:: var ( "SOCKET_PATCH_COV_BIN" ) else {
39- return Vec :: new ( ) ;
40- } ;
41- let Ok ( dir) = std:: env:: var ( "SOCKET_PATCH_COV_PROFRAW_DIR" ) else {
42- return Vec :: new ( ) ;
43- } ;
44- vec ! [
45- "-v" . into( ) ,
46- format!( "{bin}:/usr/local/bin/socket-patch:ro" ) ,
47- "-v" . into( ) ,
48- format!( "{dir}:/coverage" ) ,
49- "-e" . into( ) ,
50- "LLVM_PROFILE_FILE=/coverage/docker-e2e-%p-%14m.profraw" . into( ) ,
51- ]
52- }
53-
5436async fn make_mock_server ( after_hash : & str ) -> MockServer {
5537 let listener = std:: net:: TcpListener :: bind ( "0.0.0.0:0" ) . expect ( "bind wiremock" ) ;
5638 let server = MockServer :: builder ( ) . listener ( listener) . start ( ) . await ;
@@ -324,96 +306,15 @@ exit 0
324306 )
325307}
326308
327- /// Host-side oracle over the VEX document the container emitted between the
328- /// `===VEX DOC BEGIN===` / `===VEX DOC END===` markers (these agent suites run
329- /// the workspace inside the container with no bind mount, so the doc is parsed
330- /// from captured stdout). Asserts exactly one statement attesting the agent
331- /// patch: the fixture GHSA, `not_affected`, the installed-package subcomponent
332- /// purl, and a PLAIN impact statement with NO `(vendored)`/`(redirected)`
333- /// marker — the marker's absence is what distinguishes agent provenance.
334- fn assert_vex_agent_attested ( stdout : & str , subcomponent_purl : & str ) {
335- const BEGIN : & str = "===VEX DOC BEGIN===" ;
336- const END : & str = "===VEX DOC END===" ;
337- let start = stdout
338- . find ( BEGIN )
339- . unwrap_or_else ( || panic ! ( "VEX DOC BEGIN marker missing from stdout:\n {stdout}" ) )
340- + BEGIN . len ( ) ;
341- let stop = stdout[ start..]
342- . find ( END )
343- . unwrap_or_else ( || panic ! ( "VEX DOC END marker missing from stdout:\n {stdout}" ) )
344- + start;
345- let doc: serde_json:: Value = serde_json:: from_str ( stdout[ start..stop] . trim ( ) )
346- . expect ( "emitted VEX document must be valid JSON" ) ;
347- let stmts = doc[ "statements" ]
348- . as_array ( )
349- . expect ( "VEX document must have a statements array" ) ;
350- assert_eq ! ( stmts. len( ) , 1 , "exactly one VEX statement expected: {doc}" ) ;
351- let st = & stmts[ 0 ] ;
352- assert_eq ! ( st[ "vulnerability" ] [ "name" ] , GHSA , "attested GHSA mismatch" ) ;
353- assert_eq ! ( st[ "status" ] , "not_affected" ) ;
354- assert_eq ! (
355- st[ "products" ] [ 0 ] [ "subcomponents" ] [ 0 ] [ "@id" ] , subcomponent_purl,
356- "subcomponent must be the patched package purl"
357- ) ;
358- let impact = st[ "impact_statement" ]
359- . as_str ( )
360- . expect ( "statement must carry an impact_statement" ) ;
361- assert ! (
362- impact. contains( "Patched via Socket patch" )
363- && !impact. contains( "(vendored)" )
364- && !impact. contains( "(redirected)" ) ,
365- "agent-mode attestation must carry a PLAIN impact statement (no vendored/redirected marker): {impact}"
366- ) ;
367- }
368-
369- /// Returns `true` when the test should skip (docker missing, image
370- /// missing). Prints a skip notice to stderr in that case so the test
371- /// log shows *why* the test did nothing — the test still reports as
372- /// `ok` because Rust integration tests have no native "skipped" outcome.
373- ///
374- /// Local devs: build the image with
375- /// `docker build -f tests/docker/Dockerfile.base -t socket-patch-test-base:latest .`
376- /// then
377- /// `docker build -f tests/docker/Dockerfile.cargo -t socket-patch-test-cargo:latest .`
378- #[ must_use]
379- fn skip_if_no_image ( ) -> bool {
380- let Ok ( out) = Command :: new ( "docker" )
381- . args ( [ "image" , "inspect" , "socket-patch-test-cargo:latest" ] )
382- . output ( )
383- else {
384- eprintln ! ( "skipping: `docker` not on PATH" ) ;
385- return true ;
386- } ;
387- if !out. status . success ( ) {
388- eprintln ! ( "skipping: docker image `socket-patch-test-cargo:latest` not present" ) ;
389- return true ;
390- }
391- false
392- }
393-
394309#[ tokio:: test]
395310async fn cargo_fetch_full_apply_chain ( ) {
396311 let after_hash = git_sha256 ( PATCHED_RS ) ;
397312 let server = make_mock_server ( & after_hash) . await ;
398313 let api_url = format ! ( "http://host.docker.internal:{}" , server. address( ) . port( ) ) ;
399- if skip_if_no_image ( ) {
314+ if skip_if_no_image ( IMAGE ) {
400315 return ;
401316 }
402- let mut cmd = Command :: new ( "docker" ) ;
403- cmd. args ( [
404- "run" ,
405- "--rm" ,
406- "--add-host=host.docker.internal:host-gateway" ,
407- "-i" ,
408- ] )
409- . args ( cov_docker_args ( ) )
410- . args ( [
411- "socket-patch-test-cargo:latest" ,
412- "bash" ,
413- "-c" ,
414- & local_script ( & api_url, & after_hash) ,
415- ] ) ;
416- let out = cmd. output ( ) . expect ( "docker run" ) ;
317+ let out = run_container ( IMAGE , & local_script ( & api_url, & after_hash) ) ;
417318 let stdout = String :: from_utf8_lossy ( & out. stdout ) ;
418319 let stderr = String :: from_utf8_lossy ( & out. stderr ) ;
419320 assert ! (
@@ -429,7 +330,7 @@ async fn cargo_fetch_full_apply_chain() {
429330 stderr. contains( "===VEX VERIFIED===" ) ,
430331 "agent-mode VEX leg did not run/pass (===VEX VERIFIED=== missing).\n stderr=\n {stderr}"
431332 ) ;
432- assert_vex_agent_attested ( & stdout, PURL ) ;
333+ assert_vex_agent_attested ( & stdout, PURL , GHSA ) ;
433334
434335 // The script gates on an exact git-blob-hash match; confirm the
435336 // expected hash actually appears in the log so a future edit that
0 commit comments