From 7f527a7758ccc4dd6728e34586d1b0a7deea7d71 Mon Sep 17 00:00:00 2001 From: Dor Kalev Date: Wed, 5 Aug 2026 14:31:00 +0300 Subject: [PATCH 1/3] #13: detect evidence by relative path --- actions/shadow-ci/src/verify.rs | 40 ++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/actions/shadow-ci/src/verify.rs b/actions/shadow-ci/src/verify.rs index 7f46b08..de31c99 100644 --- a/actions/shadow-ci/src/verify.rs +++ b/actions/shadow-ci/src/verify.rs @@ -205,17 +205,18 @@ fn first_existing(root: &Path, paths: &[&str]) -> Option { } fn complete_artifact_under(root: &Path, relative: &str, needle: &str) -> Option { - fn walk(dir: &Path, needle: &str) -> Option { + fn walk(dir: &Path, scan_root: &Path, needle: &str) -> Option { let Ok(entries) = std::fs::read_dir(dir) else { return None; }; for entry in entries.flatten() { let path = entry.path(); - let name_matches = path - .file_name() - .and_then(|n| n.to_str()) - .is_some_and(|n| n.to_lowercase().contains(needle)); - if path.is_file() && name_matches { + let relative_path = path.strip_prefix(scan_root).unwrap_or(&path); + let path_matches = relative_path + .to_string_lossy() + .to_lowercase() + .contains(needle); + if path.is_file() && path_matches { let body = std::fs::read_to_string(&path).unwrap_or_default(); let lower = body.to_lowercase(); if body.trim().len() >= 80 @@ -226,14 +227,15 @@ fn complete_artifact_under(root: &Path, relative: &str, needle: &str) -> Option< } } if path.is_dir() { - if let Some(found) = walk(&path, needle) { + if let Some(found) = walk(&path, scan_root, needle) { return Some(found); } } } None } - walk(&root.join(relative), &needle.to_lowercase()) + let scan_root = root.join(relative); + walk(&scan_root, &scan_root, &needle.to_lowercase()) } fn attestation_observations(root: &Path, observed_at: &str) -> Vec { @@ -1355,4 +1357,26 @@ mod tests { assert!(DEPENDENCY_LOCK_PATHS.contains(&"app/package-lock.json")); assert!(DEPENDENCY_LOCK_PATHS.contains(&"functions/package-lock.json")); } + + #[test] + fn evidence_directory_name_can_identify_a_complete_artifact() { + let suffix = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); + let root = std::env::temp_dir().join(format!("shadow-evidence-path-{suffix}")); + let artifact = root.join("evidence/restore-tests/2026-Q3.md"); + std::fs::create_dir_all(artifact.parent().unwrap()).unwrap(); + std::fs::write( + &artifact, + "# Restore test\n\nPerformed: 2026-08-05\nResult: PASS\nA real isolated restore completed successfully and the temporary database was removed.", + ) + .unwrap(); + + assert_eq!( + complete_artifact_under(&root, "evidence", "restore"), + Some(artifact) + ); + std::fs::remove_dir_all(root).unwrap(); + } } From 10d1b3d84557c91539bb018576a1d607c2acda9c Mon Sep 17 00:00:00 2001 From: Dor Kalev Date: Wed, 5 Aug 2026 14:35:44 +0300 Subject: [PATCH 2/3] #15: migrate workflow actions to Node 24 --- .github/workflows/ci.yml | 2 +- .github/workflows/deterministic-dashboard.yml | 2 +- actions/workflows/compliance.yml | 2 +- actions/workflows/daily-verify.yml | 4 ++-- actions/workflows/deploy.yml | 2 +- actions/workflows/deterministic-verify.yml | 6 +++--- actions/workflows/quarterly-rituals.yml | 2 +- actions/workflows/restore-test.yml | 2 +- actions/workflows/test.yml | 2 +- provision/gcp-cloudsql/restore-test-cloudsql.yml | 6 +++--- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36f99f5..48b2c76 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: - name: Upload coverage reports if: always() - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: rust-coverage-${{ github.run_id }} path: coverage/*.json diff --git a/.github/workflows/deterministic-dashboard.yml b/.github/workflows/deterministic-dashboard.yml index 876cf6b..ec11dc0 100644 --- a/.github/workflows/deterministic-dashboard.yml +++ b/.github/workflows/deterministic-dashboard.yml @@ -56,7 +56,7 @@ jobs: - name: Upload dashboard and verification evidence if: always() - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: shadow-dashboard-${{ github.run_id }} path: | diff --git a/actions/workflows/compliance.yml b/actions/workflows/compliance.yml index e8be55d..7c02feb 100644 --- a/actions/workflows/compliance.yml +++ b/actions/workflows/compliance.yml @@ -66,7 +66,7 @@ jobs: - name: Upload report if: always() - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: compliance-report-${{ matrix.key }}-${{ github.event.pull_request.number }} path: compliance_report.json diff --git a/actions/workflows/daily-verify.yml b/actions/workflows/daily-verify.yml index 13c4b18..4799b08 100644 --- a/actions/workflows/daily-verify.yml +++ b/actions/workflows/daily-verify.yml @@ -52,7 +52,7 @@ jobs: - name: Upload verify report if: always() - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: shadow-verify-${{ github.run_id }} path: shadow/verify-*.md @@ -75,7 +75,7 @@ jobs: - name: Upload gauge site if: always() continue-on-error: true - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: shadow-gauge-${{ github.run_id }} path: dist/ diff --git a/actions/workflows/deploy.yml b/actions/workflows/deploy.yml index 828c98a..d8e5c41 100644 --- a/actions/workflows/deploy.yml +++ b/actions/workflows/deploy.yml @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - - uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2 + - uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3 with: workload_identity_provider: ${{ vars.GCP_WIF_PROVIDER }} service_account: ${{ vars.GCP_DEPLOY_SA }} diff --git a/actions/workflows/deterministic-verify.yml b/actions/workflows/deterministic-verify.yml index 0a9dcae..b888293 100644 --- a/actions/workflows/deterministic-verify.yml +++ b/actions/workflows/deterministic-verify.yml @@ -29,7 +29,7 @@ jobs: - name: Authenticate the read-only verifier to Google Cloud if: ${{ vars.GCP_VERIFY_SA != '' }} - uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2 + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3 with: workload_identity_provider: ${{ vars.GCP_WIF_PROVIDER }} service_account: ${{ vars.GCP_VERIFY_SA }} @@ -65,7 +65,7 @@ jobs: - name: Upload verification evidence if: always() - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: deterministic-shadow-verify-${{ github.run_id }} path: | @@ -77,7 +77,7 @@ jobs: - name: Upload dashboard snapshot if: always() - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: shadow-dashboard-${{ github.run_id }} path: dist/ diff --git a/actions/workflows/quarterly-rituals.yml b/actions/workflows/quarterly-rituals.yml index 7195f7a..4483cd6 100644 --- a/actions/workflows/quarterly-rituals.yml +++ b/actions/workflows/quarterly-rituals.yml @@ -32,7 +32,7 @@ jobs: run: cargo build --release --manifest-path .shadow/ci/Cargo.toml # Optional: authenticate gcloud for the GCP IAM sections. - # - uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2 + # - uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3 # with: # credentials_json: ${{ secrets.GCP_READONLY_SA_KEY }} diff --git a/actions/workflows/restore-test.yml b/actions/workflows/restore-test.yml index 1d911f7..ab89e01 100644 --- a/actions/workflows/restore-test.yml +++ b/actions/workflows/restore-test.yml @@ -30,7 +30,7 @@ jobs: with: fetch-depth: 0 - - uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2 + - uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3 with: # a scoped SA via WIF is ideal here too; a datastore.owner key-less # federation identity needs: roles/datastore.owner on the project diff --git a/actions/workflows/test.yml b/actions/workflows/test.yml index 75f1760..a3c4def 100644 --- a/actions/workflows/test.yml +++ b/actions/workflows/test.yml @@ -46,7 +46,7 @@ jobs: - name: Upload coverage reports if: always() - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: rust-coverage-${{ github.run_id }} path: coverage/*.json diff --git a/provision/gcp-cloudsql/restore-test-cloudsql.yml b/provision/gcp-cloudsql/restore-test-cloudsql.yml index 870f4f9..4f01f74 100644 --- a/provision/gcp-cloudsql/restore-test-cloudsql.yml +++ b/provision/gcp-cloudsql/restore-test-cloudsql.yml @@ -24,18 +24,18 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 with: fetch-depth: 0 - - uses: google-github-actions/auth@v2 + - uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3 with: # needs roles/cloudsql.admin on the project (a dedicated restore SA # via the same WIF pool is the clean setup) workload_identity_provider: ${{ vars.GCP_WIF_PROVIDER }} service_account: ${{ vars.GCP_RESTORE_SA || vars.GCP_DEPLOY_SA }} - - uses: google-github-actions/setup-gcloud@v2 + - uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3 - name: Restore latest backup into a throwaway clone id: restore From b9b8ebf06a123b075558d3767928e9d1be62dc92 Mon Sep 17 00:00:00 2001 From: Dor Kalev Date: Wed, 5 Aug 2026 14:39:49 +0300 Subject: [PATCH 3/3] #13: keep verifier test paths build-owned --- actions/shadow-ci/src/verify.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/actions/shadow-ci/src/verify.rs b/actions/shadow-ci/src/verify.rs index de31c99..defd4db 100644 --- a/actions/shadow-ci/src/verify.rs +++ b/actions/shadow-ci/src/verify.rs @@ -1360,11 +1360,10 @@ mod tests { #[test] fn evidence_directory_name_can_identify_a_complete_artifact() { - let suffix = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos(); - let root = std::env::temp_dir().join(format!("shadow-evidence-path-{suffix}")); + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("target/evidence-path-test"); + if root.exists() { + std::fs::remove_dir_all(&root).unwrap(); + } let artifact = root.join("evidence/restore-tests/2026-Q3.md"); std::fs::create_dir_all(artifact.parent().unwrap()).unwrap(); std::fs::write(