Repository navigation
Conversation
5 tasks done
Add two new GCP modules: - gcp.iam_policy observer: queries Resource Manager API for IAM policy bindings, checks for overly permissive roles (owner, editor, etc.) - gcp.public_bucket tester: performs unauthenticated access probe against Cloud Storage buckets to verify public access is denied Includes OAuth2 JWT-based service account authentication with RS256 signing for the observer, and full test transcripts for the tester. Both modules use stdlib HTTP via ureq (no GCP SDK dependency). Rebased onto current main (was 65 commits behind, 0 commits ahead became 1) to resolve conflicts in Cargo.toml/Cargo.lock and the observers/testers mod.rs registries against main's azure module and Cargo.toml crypto-dependency changes. During the rebase, the RS256 JWT signer was switched from the `rsa` crate to `ring`: rsa's PKCS#1v1.5 signing has no constant-time guarantee (RUSTSEC-2023-0071, the "Marvin Attack" -- the advisory's patched-versions list is empty, i.e. no fixed version exists), which this repo's cargo-audit/cargo-deny gates would flag. `ring` is already a transitive dependency via rustls and is pre-certified safe-to-deploy in supply-chain/audits.toml ([[trusted.ring]]), so this adds no new dependency and required no new cargo-vet exemption. Added tests that verify the replacement signer produces a real, cryptographically verifiable RS256 signature (sign+verify round-trip against a known public key, tamper detection, and malformed-key error paths) rather than just exercising the code path. Closes #12 The RS256 tests generate a throwaway RSA key pair once per run with the openssl CLI (public half derived by openssl too, independently of the code under test), so no private key is stored in the repository, and the non-PKCS#8 error-path stub is a bare base64 body. An earlier version of this branch embedded a test key under a renamed PEM label that hid it from secret scanners; that is folded out of the history here. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012hwWMsTiv9Uf2gNbMh75uk AI-Assisted: true AI-Tool: Claude Code AI-Model: Opus 4.6, Sonnet 5, Opus 5.5 AI-Role: draft AI-Dependency-Review: approved
cargo-deny's advisory check (required by CI's deny job) flags rustls 0.23.37, pulled in transitively via ureq, for RUSTSEC-2026-0285: TLS 1.3 handshake messages were incorrectly accepted across encryption level boundaries (the handshake transcript stays authenticated, so this is not exploitable for tampering, but rustls should have rejected the malformed sequence per RFC 8446 S5.1 and didn't). Fixed upstream in 0.23.45; this bumps the same major.minor via `cargo update -p rustls`, which also pulled rustls-webpki 0.103.13 -> 0.103.15. No Cargo.toml change needed -- ureq's own dependency spec already allows this range. This predates this PR: origin/main's own Cargo.lock already pins 0.23.37 and fails the same `cargo deny check` today, independent of anything this PR changes. Fixing it here is necessary to get this PR's own CI green regardless of origin. cargo-vet initially flagged the two bumped versions as unvetted (vet certifications are pinned per-version, not per-semver-range). Both crates already carry long-standing [[trusted.*]] entries for "safe-to-deploy" (rustls: Daniel McCarney through 2027-03-29; rustls-webpki: three trusted publishers, same end date) -- the gap was only that supply-chain/imports.lock's cached publisher record was pinned to the old versions. Resolved via `cargo vet regenerate imports` (refreshes the publisher cache from the configured import sources) + `cargo vet fmt`, not by adding a new exemption or weakening any criteria. `cargo vet check --locked` now succeeds with the same 181 fully audited / 6 partially audited / 202 exempted counts as before the bump. Verified: cargo build --all-features, cargo deny --all-features check all (advisories/bans/licenses/sources all ok), cargo vet check --locked, cargo fmt --check, cargo clippy -- -D warnings (CI's exact invocation) all green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012hwWMsTiv9Uf2gNbMh75uk AI-Assisted: true AI-Tool: Claude Code AI-Model: Sonnet 5 AI-Role: refactor AI-Dependency-Review: approved
CI's lint job (cargo fmt --check) requires rustfmt-clean source. The GCP modules as originally authored predate this PR's rebase and had drifted from the current stable rustfmt's formatting (mostly line-wrapping of short call chains and fn signatures that now fit on one line). No behavior change, cargo fmt only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012hwWMsTiv9Uf2gNbMh75uk AI-Assisted: true AI-Tool: Claude Code AI-Model: Sonnet 5 AI-Role: refactor
…ying CI's unit-test step (cargo test --lib --bins) runs this suite, and the IAM policy observer's HTTP-mock tests were intermittently failing with "parsing GCP IAM policy response: Failed to read JSON: Invalid argument (os error 22)" on the client side -- a different test each run, reproduced locally in roughly half of repeated runs (both --test-threads=1 and default parallelism). Root cause: mock_server's acceptor did exactly one read() into an 8192-byte buffer and then wrote the response immediately. The IAM-policy request carries Authorization + Content-Type headers and a JSON body (more than github/okta/azure's simpler calls through the same pattern elsewhere in this crate), so it's more likely to arrive split across more than one TCP segment. When that happens, the single read() only captures part of it; dropping the stream right after writing the response then leaves bytes unread in the kernel receive buffer, and the OS answers with an RST instead of a graceful close -- which corrupts the client's read of the response that was already written, surfacing as this error on ureq's side. Fix: loop reading with a short (50ms) read timeout until nothing more arrives, before writing the response. This drains the request fully without needing to parse HTTP framing (Content-Length, chunked, etc.) and is bounded, so it cannot hang a test. Verified empirically rather than assumed: 20/20 clean runs at --test-threads=1 and 15/15 clean runs at default parallelism after the fix (0 flakes in 35 runs), versus frequent failures before it across the same kind of repeated-run sampling. cargo fmt --check and cargo clippy -- -D warnings (CI's exact invocation) both still pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012hwWMsTiv9Uf2gNbMh75uk AI-Assisted: true AI-Tool: Claude Code AI-Model: Sonnet 5 AI-Role: test
This branch has not been 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.
Update 2026-10-06: this branch was rebased onto
mainand changed since it was opened. Signing now usesringinstead of thersacrate, rustls is bumped for RUSTSEC-2026-0285, and the RS256 tests no longer store a private key. Provenance is resolved: every commit now carries honest AI trailers, and the owner approved the dependency changes on 2026-10-12. See Provenance below. The text below describes the branch at61ca4bf.Summary
Adds GCP modules to cover the third major cloud provider alongside AWS (closes #12):
gcp.iam_policyobserver — Queries GCP Resource Manager API for IAM policy bindings, checks for overly permissive roles (roles/owner,roles/editor,roles/iam.securityAdmin,roles/resourcemanager.projectIamAdmin). Producespassive_observationevidence.gcp.public_buckettester — Safety class:safe(read-only probe). Attempts unauthenticated access to Cloud Storage bucket object listing. Verifies bucket is not publicly accessible. Full test transcript included.Credentials
GCP_SERVICE_ACCOUNT_KEY— JSON service account key (string)GCP_PROJECT_ID— Target projectGCP_ACCESS_TOKEN— Optional: skip JWT flow (useful for testing)Implementation Details
ring(sign_rs256). The original version used thersacrate, which carries RUSTSEC-2023-0071 (Marvin timing side channel, unpatched);ringwas already in the dependency tree through rustls.ureq+ JSON, matching project conventionmodules/observers/mod.rsandmodules/testers/mod.rsDependency changes
Cargo.toml: addsring = "0.17"as a direct dependency (no new crate inCargo.lock; it was already there transitively).Cargo.lock:rustls0.23.37 → 0.23.45 andrustls-webpki0.103.13 → 0.103.15, for RUSTSEC-2026-0285.supply-chain/imports.lockandaudits.tomlrefreshed for those versions (cargo vet).Test plan
cargo teston this code (head61ca4bf): 1549 passed, 0 failed (44 of them in the two GCP modules)cargo clippy -- -D warningsandcargo fmt --checkcleansign_rs256: a signature verifies against the matching public key and fails on a tampered message; malformed PEM and non-PKCS#8 input are rejectedstd::net::TcpListenermock serversopensslCLI, and the non-PKCS#8 error-path stub is a bare base64 body. An earlier version of the branch embedded a test key under a renamed PEM label so secret scanners would not see it, a scanner-evasion; that fix is folded into the feature commit, so no commit on the branch carries key material (verified commit by commit). gitleaks and trufflehog (all result types) find nothing in the file.Provenance
All four commits were made with Claude Code and carry
AI-Assisted,AI-Tool,AI-ModelandAI-Roletrailers. The two that change dependency manifests (0f394b3the GCP feature,c7b9e25the rustls bump) also carryAI-Dependency-Review: approved, recording the repository owner's review of exactly the changes listed under Dependency changes, given on 2026-10-12 before the trailers were written. The trailers were missing at first because onmainthe files in.sscsb/hooks/are stored non-executable (mode100644), so git skipped the commit-msg gate; #22 fixes that mode.🤖 Generated with Claude Code
https://claude.ai/code/session_012hwWMsTiv9Uf2gNbMh75uk