diff --git a/.capsec-baseline.json b/.capsec-baseline.json new file mode 100644 index 00000000..3941ecbd --- /dev/null +++ b/.capsec-baseline.json @@ -0,0 +1,514 @@ +[ + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/witness/server.rs", + "function": "run_server", + "call_text": "tokio::net::TcpListener::bind", + "category": "NET" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/config.rs", + "function": "from_env", + "call_text": "std::env::var", + "category": "ENV" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/config.rs", + "function": "from_env", + "call_text": "std::env::var", + "category": "ENV" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/config.rs", + "function": "from_env", + "call_text": "std::env::var", + "category": "ENV" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/config.rs", + "function": "from_env", + "call_text": "std::env::var", + "category": "ENV" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/config.rs", + "function": "from_env", + "call_text": "std::env::var", + "category": "ENV" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/config.rs", + "function": "from_env", + "call_text": "std::env::var", + "category": "ENV" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/config.rs", + "function": "from_env", + "call_text": "std::env::var", + "category": "ENV" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/config.rs", + "function": "from_env", + "call_text": "std::env::var", + "category": "ENV" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/config.rs", + "function": "from_env", + "call_text": "std::env::var", + "category": "ENV" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/config.rs", + "function": "from_env", + "call_text": "std::env::var", + "category": "ENV" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/agent/handle.rs", + "function": "shutdown", + "call_text": "std::fs::remove_file", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/agent/handle.rs", + "function": "shutdown", + "call_text": "std::fs::remove_file", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/agent/handle.rs", + "function": "test_agent_handle_shutdown", + "call_text": "std::fs::write", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/storage/windows_credential.rs", + "function": "new", + "call_text": "std::fs::create_dir_all", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/storage/windows_credential.rs", + "function": "load_index", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/storage/windows_credential.rs", + "function": "save_index", + "call_text": "std::fs::write", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/storage/encrypted_file.rs", + "function": "read_data", + "call_text": "std::fs::File::open", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/testing/builder.rs", + "function": "build", + "call_text": "std::process::Command::new", + "category": "PROC" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/testing/builder.rs", + "function": "build", + "call_text": "output", + "category": "PROC" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/api/runtime.rs", + "function": "start_agent_listener_with_handle", + "call_text": "std::fs::create_dir_all", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/api/runtime.rs", + "function": "start_agent_listener_with_handle", + "call_text": "std::fs::remove_file", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/trust/roots_file.rs", + "function": "load", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/trust/roots_file.rs", + "function": "create_temp_roots_file", + "call_text": "std::fs::File::create", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/trust/pinned.rs", + "function": "read_all", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/trust/pinned.rs", + "function": "write_all", + "call_text": "std::fs::create_dir_all", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/trust/pinned.rs", + "function": "write_all", + "call_text": "std::fs::File::create", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/trust/pinned.rs", + "function": "write_all", + "call_text": "std::fs::rename", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/trust/pinned.rs", + "function": "lock", + "call_text": "std::fs::create_dir_all", + "category": "FS" + }, + { + "crate_name": "auths-core", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-core/src/trust/pinned.rs", + "function": "test_concurrent_access_no_corruption", + "call_text": "std::fs::write", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "install_cache_hooks", + "call_text": "std::fs::create_dir_all", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "install_hook", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "install_hook", + "call_text": "std::fs::write", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "install_hook", + "call_text": "std::fs::metadata", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "find_git_dir", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "uninstall_cache_hooks", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "uninstall_cache_hooks", + "call_text": "std::fs::remove_file", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "uninstall_cache_hooks", + "call_text": "std::fs::write", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "install_linearity_hook", + "call_text": "std::fs::create_dir_all", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "install_linearity_hook", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "install_linearity_hook", + "call_text": "std::fs::write", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "install_linearity_hook", + "call_text": "std::fs::metadata", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "uninstall_linearity_hook", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "uninstall_linearity_hook", + "call_text": "std::fs::remove_file", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "uninstall_linearity_hook", + "call_text": "std::fs::write", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "test_install_new_hooks", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "test_install_appends_to_existing", + "call_text": "std::fs::create_dir_all", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "test_install_appends_to_existing", + "call_text": "std::fs::write", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "test_install_appends_to_existing", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "test_install_idempotent", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "test_install_linearity_hook_new", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "test_install_linearity_hook_idempotent", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "test_install_linearity_hook_appends_to_existing", + "call_text": "std::fs::create_dir_all", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "test_install_linearity_hook_appends_to_existing", + "call_text": "std::fs::write", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "test_install_linearity_hook_appends_to_existing", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "test_uninstall_linearity_hook_preserves_other_content", + "call_text": "std::fs::create_dir_all", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "test_uninstall_linearity_hook_preserves_other_content", + "call_text": "std::fs::write", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/storage/registry/hooks.rs", + "function": "test_uninstall_linearity_hook_preserves_other_content", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/agent_identity.rs", + "function": "ensure_git_repo", + "call_text": "std::fs::create_dir_all", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/agent_identity.rs", + "function": "write_agent_toml", + "call_text": "std::fs::write", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/freeze.rs", + "function": "load_active_freeze", + "call_text": "std::fs::read_to_string", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/freeze.rs", + "function": "load_active_freeze", + "call_text": "std::fs::remove_file", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/freeze.rs", + "function": "store_freeze", + "call_text": "std::fs::write", + "category": "FS" + }, + { + "crate_name": "auths-id", + "crate_version": "0.0.1-rc.9", + "file": "crates/auths-id/src/freeze.rs", + "function": "remove_freeze", + "call_text": "std::fs::remove_file", + "category": "FS" + } +] diff --git a/.capsec.toml b/.capsec.toml new file mode 100644 index 00000000..8f6950d5 --- /dev/null +++ b/.capsec.toml @@ -0,0 +1,14 @@ +# capsec audit configuration for auths workspace +# +# Two-tier enforcement: +# - Clean crates (crypto, verifier, policy, keri): --fail-on low (zero findings expected) +# - Dirty crates (core, id): --diff --fail-on high (baseline + regression detection) + +[analysis] +exclude = ["tests/**", "benches/**"] + +# auths-verifier's WASM extern block is expected — it's a minimal FFI binding +# for console.log in wasm32 targets, not a security concern. +[[allow]] +crate = "auths-verifier" +function = "extern \"C\"" diff --git a/.cargo/audit.toml b/.cargo/audit.toml index 34285902..e15f2dae 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -12,4 +12,9 @@ ignore = [ # Only affects the optional witness-server feature, not core signing or # verification paths. "RUSTSEC-2025-0134", + + # rustls-webpki 0.101.7 CRL matching logic error (via aws-smithy-http-client + # -> hyper-rustls 0.24 -> rustls 0.21). Pinned by AWS SDK's legacy TLS stack. + # No update available until AWS SDK drops rustls 0.21 support. + "RUSTSEC-2026-0049", ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3be385c7..ce5612c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -138,6 +138,34 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} + capsec-audit: + name: Capability Audit + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: dtolnay/rust-toolchain@stable + - name: Audit clean crates (zero I/O expected) + uses: bordumb/capsec-github-action@v1 + with: + only: auths-crypto,auths-verifier,auths-policy,auths-keri + fail-on: low + upload-sarif: false + comment-on-pr: false + - name: Audit dirty crates (no new high-risk I/O) + uses: bordumb/capsec-github-action@v1 + with: + only: auths-core,auths-id + fail-on: high + upload-sarif: true + sarif-category: capsec-audit-dirty + comment-on-pr: true + msrv: name: MSRV check (1.93) runs-on: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1bb1cc64..4e5ae314 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -56,6 +56,13 @@ repos: files: (Cargo\.(toml|lock)|deny\.toml)$ pass_filenames: false + - id: capsec-audit + name: capsec audit (I/O boundaries) + entry: bash -c 'command -v cargo-capsec >/dev/null 2>&1 || { echo "Skipping capsec audit — not installed."; exit 0; }; cargo capsec audit --only auths-crypto,auths-verifier,auths-policy,auths-keri --fail-on low --quiet && cargo capsec audit --only auths-core,auths-id --diff --fail-on high --quiet' + language: system + files: \.(rs|toml)$|Cargo\.lock$ + pass_filenames: false + # ── Slow gates (push only) ────────────────────────────────────────── # These run on `git push`. They require linking binaries, compiling # alternative targets, or cross-compilation. diff --git a/Cargo.lock b/Cargo.lock index 59a98e85..ac611358 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -878,9 +878,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.16.1" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" dependencies = [ "aws-lc-sys", "zeroize", @@ -888,9 +888,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" dependencies = [ "cc", "cmake", @@ -1800,7 +1800,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -5394,7 +5394,7 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.9", + "rustls-webpki 0.103.10", "subtle", "zeroize", ] @@ -5444,7 +5444,7 @@ dependencies = [ "rustls 0.23.37", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.9", + "rustls-webpki 0.103.10", "security-framework 3.7.0", "security-framework-sys", "webpki-root-certs", @@ -5469,9 +5469,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "aws-lc-rs", "ring", @@ -7280,7 +7280,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] diff --git a/docs/architecture/ADRs/001-capsec-capabilities-on-adapters.md b/docs/architecture/ADRs/001-capsec-capabilities-on-adapters.md new file mode 100644 index 00000000..f639cf25 --- /dev/null +++ b/docs/architecture/ADRs/001-capsec-capabilities-on-adapters.md @@ -0,0 +1,119 @@ +# ADR-001: capsec Capabilities on Adapters, Not Port Traits + +**Status:** Accepted +**Date:** 2026-03-21 +**Epic:** fn-82 (capsec Type System Adoption) + +## Context + +We are adopting [capsec](https://github.com/bordumb/capsec) to enforce I/O boundaries at compile time. capsec provides zero-sized capability tokens (`Cap
`, `SendCap
`) and a `Has
` trait bound that functions use to declare what I/O they require.
+
+The auths workspace uses a ports-and-adapters architecture. Port traits (e.g., `BlobReader`, `RegistryClient`, `KeyStorage`) are defined in domain crates (`auths-core`, `auths-id`) and stored as `Arc ` tokens to adapter constructors:
+
+```rust
+let root = capsec::root();
+let fs_cap = root.grant:: ` bounds on port trait definitions
+
+```rust
+pub trait BlobReader: Send + Sync + Has ` bounds to traits breaks object safety. `Arc ` has a generic parameter.
+- The entire `AuthsContext` is built on `Arc ` bounds on individual trait methods
+
+```rust
+pub trait BlobReader: Send + Sync {
+ fn read_blob(&self, key: &str, cap: &impl Has `) |
+| Domain crates see | `DateTime