Files
rustfs/docs/testing/security-regressions.md
T
Zhengchao An 7d96ffd7fb docs(testing): record GHSA-5354 and GHSA-3ppv regression guards (#5143)
PRs #5141 and #5142 fixed GHSA-5354 (service-account parent-scope enforcement) and GHSA-3ppv (versioned object-read authorization) but did not add the advisories to the security-regressions inventory that repo policy requires for every fixed GHSA. Add both rows to the advisory -> test mapping (advisory, class, fix PR, named regression test, layer) and list ghsa_5354_* / ghsa_3ppv_* under the default-CI unit-test execution map, since both tests live in the rustfs lib target and run in the default `cargo nextest run --profile ci --all --exclude e2e_test` pass.
2026-07-23 08:31:21 +08:00

5.8 KiB

Security Advisory Regression Tests

Every fixed RustFS GitHub Security Advisory (GHSA) should map to at least one named, discoverable regression test. The convention is: name the test (or a helper / doc comment on the exact assertion) after the advisory so that

rg -i "ghsa|3p3x|r5qv"

finds the guard for any advisory, and a future fix of a still-open advisory is forced to update its pinned test (red -> green).

This is the lightweight inventory. sec-14 (backlog#1151) formalizes the written admission policy in AGENTS.md; keep this file as the map.

Advisory -> test mapping

Advisory Class Fix PR Named regression tests Layer
GHSA-3p3x-734c-h5vx Constant-time secret comparison on WebDAV/FTPS password login rustfs/rustfs#4403 assert_ftps_ghsa_3p3x_wrong_credentials_rejected (crates/e2e_test/src/protocols/ftps_core.rs); GHSA-3p3x auth-failure block in test_webdav_core_operations (crates/e2e_test/src/protocols/webdav_core.rs) e2e (protocols suite)
GHSA-r5qv-rc46-hv8q Internode RPC authentication must fail closed rustfs/rustfs#4402 ghsa_r5qv_resolve_shared_secret_rejects_default_fallback, ghsa_r5qv_verify_rpc_signature_fails_closed_on_missing_or_invalid_auth (crates/ecstore/src/cluster/rpc/http_auth.rs) unit
GHSA-m77q-r63m-pj89 STS JWTs signed with shared root secret (intentionally unfixed) n/a test_ghsa_m77q_sts_session_token_signed_with_root_secret (flow-level pin: signing key == root secret, root-only decode, authorizes) and test_created_sts_credentials_authorize_with_session_token_claims (crates/iam/src/sys.rs); token_signing_key doc (crates/iam/src/root_credentials.rs) — pin current by-design behavior; fixing m77q must update red -> green unit
GHSA-5354-r3w2-34m8 Service-account parent must stay within caller scope — a non-owner holding CreateServiceAccountAdminAction could parent a service account to the root credential and authenticate as owner rustfs/rustfs#5141 ghsa_5354_non_owner_service_account_parent_confined_to_scope and the add_service_account_parent_within_scope invariant it pins (rustfs/src/admin/handlers/service_account.rs) unit
GHSA-3ppv-fx5m-m749 Versioned object reads must be authorized against s3:GetObjectVersion, not s3:GetObject (get_object, CopyObject source, UploadPartCopy source) rustfs/rustfs#5142 ghsa_3ppv_versioned_read_selects_get_object_version_action and the versioned_read_action helper it pins (rustfs/src/storage/access.rs) unit

Where these run (CI-execution map)

Every security regression must land where CI actually runs it — a named test in an unexecuted suite is theater. The suites split across three execution paths by topology:

  • Unit testsghsa_r5qv_* (crates/ecstore), the GHSA-m77q STS pinning (crates/iam), and ghsa_5354_* / ghsa_3ppv_* (rustfs lib) run automatically in the default CI pass (cargo nextest run --profile ci --all --exclude e2e_test) — no special wiring. This is the CI-executed regression for the RPC fail-closed (r5qv), STS-signing (m77q), service-account parent-scope (5354), and versioned-read authorization (3ppv) advisories.

  • S3-API negative-auth e2e (e2e-smoke, PR-gated) — the attacker-facing S3 auth-rejection suites run on every PR via the e2e-smoke nextest profile (.config/nextest.toml), which each spawns its own server on a random port and is parallel-safe:

    • negative_sigv4_test — tampered/wrong-key/skewed header SigV4 (sec-1)
    • presigned_negative_test — expired/tampered/wrong-key presigned URLs (sec-2)
    • admin_auth_test — non-admin denial + root-credential lifecycle (sec-4)

    A count-floor guard (scripts/check_security_smoke_count.sh, floor in .config/security-smoke-floor.txt) runs in the e2e-tests CI job and fails if a rename drops any of these out of the smoke filter (infra-12 mechanism). This is the sec-5 wiring: those merged suites were dead weight until listed here.

  • Protocol e2e (WebDAV/FTPS constant-time login, GHSA-3p3x) — lives in the e2e_test protocols suite (test_protocol_core_suite), which binds fixed ports, needs the ftps,webdav build features, and is #[serial]. Those three properties make it structurally incompatible with the random-port, default-feature, parallel e2e-smoke profile: it cannot be a filterset change, so sec-5 (scoped to a filterset change, global ruling G5) does not wire it. It runs manually today:

    RUSTFS_BUILD_FEATURES=ftps,webdav cargo test --package e2e_test \
      test_protocol_core_suite -- --test-threads=1 --nocapture
    

    Open gap: GHSA-3p3x's e2e layer has no PR-gated CI execution. A protocols e2e CI lane is a ci-domain concern (a new profile/job, not a filterset change); it is deliberately out of sec-5's boundary and left as a follow-up.

Adding a new advisory guard

  1. Reproduce the advisory's bypass form as a focused negative test.
  2. Name the test (or the helper/assertion) ghsa_<id>_*, or attach a GHSA-<id> doc comment with the advisory URL and fix PR.
  3. Add a row to the table above.
  4. Land it where CI runs it (see the map above): a unit guard in the default CI pass; an S3-API negative-auth e2e in the e2e-smoke filter (add the module to .config/nextest.toml and bump .config/security-smoke-floor.txt); a fixed-port protocol e2e in the protocols suite (still manual — see the open gap above).