Skip to content

Latest commit

 

History

History
684 lines (498 loc) · 54.5 KB

File metadata and controls

684 lines (498 loc) · 54.5 KB

Security in Gears

Gears take a defense-in-depth approach to security, combining Rust's compile-time safety guarantees with layered static analysis, runtime enforcement, continuous scanning, and structured development processes. This document summarizes the security measures in place across the project.


Table of Contents


1. Rust Language Safety

Rust eliminates entire categories of vulnerabilities at compile time:

Vulnerability Class How Rust Prevents It
Null pointer dereference No null — Option<T> forces explicit handling
Use-after-free / double-free Ownership system with borrow checker
Data races Send/Sync traits enforced at compile time
Buffer overflows Bounds-checked indexing; slices carry length
Uninitialized memory All variables must be initialized before use
Integer overflow Checked in debug builds; explicit wrapping/saturating in release

Additional Rust-specific project practices:

  • #[deny(warnings)] — all compiler warnings are treated as errors in CI (RUSTFLAGS="-D warnings")
  • #[deny(clippy::unwrap_used)] / #[deny(clippy::expect_used)] — panicking on None/Err is forbidden in production code
  • No unsafe without justification — Clippy pedantic rules surface unnecessary unsafe usage

2. Compile-Time Tenant Scoping (Secure ORM)

Source: libs/toolkit-db-macros · guidelines/SECURITY.md · docs/toolkit_unified_system/06_authn_authz_secure_orm.md

Gears provide a compile-time enforced secure ORM layer over SeaORM. The #[derive(Scopable)] macro ensures every database entity explicitly declares its scoping dimensions:

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Scopable)]
#[sea_orm(table_name = "users")]
#[secure(
    tenant_col = "tenant_id",
    resource_col = "id",
    no_owner,
    no_type
)]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: Uuid,
    pub tenant_id: Uuid,
    pub email: String,
}

Key compile-time guarantees:

  • Explicit scoping required — every entity must declare all four dimensions (tenant, resource, owner, type). Missing declarations cause a compile error.
  • No accidental bypassclippy.toml configures disallowed-methods to block direct sea_orm::Select::all(), ::one(), ::count(), UpdateMany::exec(), and DeleteMany::exec(). All queries must go through SecureSelect/SecureUpdateMany/SecureDeleteMany.
  • Deny-by-default — empty AccessScope (no tenant IDs, no resource IDs) produces WHERE 1=0, denying all rows.
  • Immutable tenant ownership — updates cannot change tenant_id (enforced in secure_insert).
  • No SQL injection — all queries use SeaORM's parameterized query builder.

3. Authentication & Authorization Architecture

Source: docs/arch/authorization/ · gears/system/authn-resolver/ · gears/system/authz-resolver/ · gears/system/tenant-resolver/

Gears implement a PDP/PEP authorization model per NIST SP 800-162, extended with OpenID AuthZEN 1.0 constraint semantics (see ADR-0001):

Client → AuthN Middleware → AuthN Resolver (token validation)
       → Gear Handler (PEP) → AuthZ Resolver (PDP, policy evaluation)
       → Constraints compiled to AccessScope
       → Database (query with WHERE clauses from constraints)

SecurityContext

Every authenticated request produces a SecurityContext:

pub struct SecurityContext {
    subject_id: Uuid,
    subject_type: Option<String>,
    subject_tenant_id: Uuid,           // every subject belongs to a tenant
    token_scopes: Vec<String>,         // capability ceiling (["*"] = unrestricted)
    bearer_token: Option<SecretString>, // redacted in Debug, never serialized
}

bearer_token is stored as Secret<String> — redacted in Debug/Display, never serialized or logged. Introspection caches key by sha256(token), not the raw token.

AuthN Resolver

Source: OIDC AuthN Plugin DESIGN.md · ADR-0002 · ADR-0003

Validates bearer JWTs via OIDC discovery and JWKS, extracts claims, and constructs the SecurityContext. AuthN and AuthZ are split into independent resolver gears (ADR-0002) with pluggable vendor-specific implementations.

The current OIDC AuthN plugin supports:

  • JWT tokens — local validation via OIDC discovery → JWKS → signature verification (kid, exp, optional aud), with configurable claim mapping to SecurityContext fields.
  • Opaque tokens — out of scope for this plugin; non-JWT bearer tokens are rejected.
  • S2S identityexchange_client_credentials (OAuth2 client credentials grant) for service-to-service calls, producing the same SecurityContext pipeline.
  • Caching — JWKS cached with refresh and bounded stale serving; S2S tokens cached with TTL bounded by min(token_exp - now, configured_ttl).

AuthZ Resolver (PDP) — AuthZEN with Constraint Extensions

Source: DESIGN.md · AUTHZ_USAGE_SCENARIOS.md

Plain AuthZEN point-in-time true/false decisions are insufficient for LIST queries with pagination. The design extends AuthZEN with context.constraints — a predicate DSL so the PEP receives SQL-friendly filters in O(1) PDP calls per query instead of per-row evaluation.

Constraint predicates:

Predicate Purpose
eq(property, value) Exact match (e.g., owner_id)
in(property, values) Set membership
in_tenant_subtree(root_id, barrier_mode, status) Hierarchical tenant scoping via tenant_closure
in_group(group_ids) Resource group membership
in_group_subtree(group_ids) Hierarchical group membership

Constraints OR across alternatives, AND predicates within each constraint. PEP compiles constraints into AccessScope → SecureORM translates to SQL WHERE clauses.

Fail-closed PEP enforcement — PEP denies access on:

  • Missing or false decision
  • Unreachable PDP
  • Missing constraints when require_constraints: true
  • Unknown predicates or property names
  • Empty predicate lists, empty in/group_ids values, or empty constraints: []

Capability negotiation — PEP sends capabilities, supported_properties, and require_constraints with each evaluation request. The PDP must not emit unsupported property names and must degrade gracefully (expand groups to explicit in lists, or deny).

Token scopes as capability ceilingeffective_access = min(token_scopes, user_permissions). First-party apps typically carry ["*"] (unrestricted).

Deny contractdecision: false must include a deny_reason with a GTS error_code. Details are logged for audit but never exposed to clients (generic 403 only, no policy leakage).

404 vs 403 for point reads — Constrained queries returning 0 rows yield 404, preventing existence leakage.

TOCTOU mitigation — For UPDATE/DELETE, PEP prefetches the target attribute and uses eq predicates in the WHERE clause so the mutation is atomic with the authorization check.

Multi-Tenancy — Tenant Resolver

Source: TENANT_MODEL.md · gears/system/tenant-resolver/

Hierarchical multi-tenancy with a single-root tree topology (exactly one tenant with no parent; all others descend from it):

  • Isolation by default — every resource carries owner_tenant_id as the primary partition key; tenants cannot access each other's data.
  • Hierarchical access — parent tenants may access child data. Subject tenant (home identity) and context tenant (operational scope) are distinguished, enabling scoped admin patterns.
  • Barriers — child tenants set self_managed = true to create a privacy barrier. Parents cannot see that subtree's business data by default; BarrierMode controls per-resource-type relaxation (e.g., billing ignores barriers while tasks respect them).
  • tenant_closure table — materialized (ancestor_id, descendant_id, barrier, descendant_status) enables efficient in_tenant_subtree predicate compilation to SQL subqueries.

Tenant Resolver is a plugin-based system gear providing tenant graph operations (get, ancestors, descendants, is_ancestor) via TenantResolverClient:

  • Plugin architecture — gateway discovers a backend plugin by GTS vendor string; routes all calls through TenantResolverPluginClient. Built-in plugins: static-tr-plugin (in-memory tree from config), single-tenant-tr-plugin (enforces ctx.subject_tenant_id() as the only tenant).
  • Barrier-aware traversal — ancestor/descendant walks respect self_managed barriers and use visited sets for cycle safety.
  • Status filtering — queries filter by TenantStatus; filtering a suspended parent excludes its entire subtree.
  • PIP role — Tenant Resolver serves as a Policy Information Point (PIP): the AuthZ plugin queries it for hierarchy data when building tenant constraints.

Resource Groups

Source: RESOURCE_GROUP_MODEL.md

Optional M:N, tenant-scoped resource grouping that acts as a PIP alongside the tenant hierarchy:

  • Groups enable attribute-based grouping of resources for authorization (e.g., project groups, organizational units).
  • The AuthZ plugin queries group membership/hierarchy when building in_group / in_group_subtree predicates.
  • ResourceGroupReadHierarchy supports hierarchical group traversal.
  • Group constraints are always paired with tenant predicates — defense in depth prevents cross-tenant leakage through group membership alone.

GTS-Based Attribute Access Control (ABAC)

Source: gts-spec · dylint_lints/de09_gts_layer/ · gears/system/types-registry/

Gears use the Global Type System (GTS) as the foundation for attribute-based access control. GTS defines a hierarchical identifier scheme for data types and instances:

gts.<vendor>.<package>.<namespace>.<type>.v<MAJOR>[.<MINOR>]~

How GTS enables ABAC:

  • Token claims — authenticated user tokens carry GTS type patterns in token_scopes, defining the capability ceiling for the subject (e.g., ["gts.cf.core.srr.resource.v1~*"] grants access to all SRR resource types under that schema).
  • Wildcard matching — GTS supports segment-wise wildcard patterns (*), chain-aware evaluation, and attribute predicates for fine-grained policy expressions.
  • Authorization resources — PDP evaluations reference GTS-typed resources (e.g., gts.cf.core.oagw.proxy.v1~:invoke for outbound gateway proxy access).
  • Secure ORM integration (under development) — the ScopableEntity trait supports a type_col dimension. The planned flow: AuthZ Resolver (PDP) evaluates GTS type constraints → compiles them into AccessScope → Secure ORM translates to SQL WHERE clauses, automatically filtering rows by type at the database level.

Current implementation status:

Component Status
GTS identifier parsing & validation Implemented
GTS type patterns in token scopes Implemented
Wildcard pattern matching (GtsWildcard) Implemented
GTS → UUID resolution (Types Registry) Implemented
Domain-level type filtering (e.g., SRR) Implemented
GTS-typed authorization resources Implemented
Secure ORM type_col auto-injection via PDP Under development

Custom dylint rules (DE0901, DE0902) validate GTS identifier correctness at compile time, preventing malformed type strings from entering the codebase.

4. Credentials Storage Architecture

Source: gears/credstore/ · gears/credstore/docs/DESIGN.md

Gears provide a plugin-based credential storage gateway for managing secrets across the platform. The architecture separates the gateway (routing, authorization) from storage backends (plugin implementations).

Consumer → CredStoreClientV1 → Gateway Service → GTS Plugin Discovery
         → CredStorePluginClientV1 (vendor backend)

Secret Material Handling

The SDK enforces secure handling of secret material at the type level:

Protection Mechanism
Memory safety SecretValue wraps Vec<u8> with zeroize on Drop — secret bytes are wiped from memory when no longer needed
Log safety Debug and Display implementations on SecretValue emit [REDACTED] — secrets cannot leak through logging
Serialization safety SecretValue does not implement Serialize/Deserialize — secrets cannot be accidentally persisted or transmitted
Key validation SecretRef validates keys as [a-zA-Z0-9_-]+ (max 255 chars, no colons) — prevents injection via key names
Anti-enumeration Failed lookups return Ok(None), not a distinct "forbidden" error — prevents existence probing

Scoping Model

Credentials are scoped along three visibility levels:

  • Private(tenant, owner, key): only the owning subject within a tenant can access.
  • Tenant(tenant, key): any subject within the tenant can access.
  • Shared — tenant-scoped with descendant visibility via gateway hierarchy walk-up.

Plugin Isolation

The gateway enforces authorization via SecurityContext; plugins are single-tenant-level adapters that handle storage only. Built-in reference plugin: static-credstore-plugin (YAML-defined, in-memory — development/test use).

Planned Encryption at Rest

The credential storage design specifies AES-256-GCM encryption with per-tenant keys and a KeyProvider abstraction supporting both DatabaseKeyProvider (co-located keys) and ExternalKeyProvider (Vault/KMS integration) for key–data separation.

5. Outbound API Gateway (OAGW)

Source: gears/system/oagw/ · gears/system/oagw/docs/DESIGN.md

OAGW is a centralized outbound API gateway built on Pingora. All platform traffic to external HTTP services is routed through OAGW, enforcing security and observability policies via a Control Plane / Data Plane architecture.

Authorization (Platform PEP)

Every proxy request is authorized via PolicyEnforcer before routing:

self.policy_enforcer
    .access_scope_with(
        &ctx,
        &resources::PROXY,                      // gts.cf.core.oagw.proxy.v1~
        actions::INVOKE,
        None,
        &AccessRequest::new()
            .require_constraints(false)
            .context_tenant_id(ctx.subject_tenant_id()),
    )
    .await?;

Ancestor bind flows (descendant reusing parent upstream aliases) have separate authorization actions: bind, override_auth, override_rate, add_plugins.

Credential Isolation

Outbound authentication credentials are never stored in OAGW configuration. Auth configs reference secrets via cred:// URIs, resolved through CredStoreClientV1 under the caller's SecurityContext. OAuth2 client-credentials tokens are cached with keys scoped by (tenant, subject, auth_method), preventing cross-tenant token reuse.

Auth Plugins

Per-upstream/route authentication plugins modify outbound requests:

Plugin Mechanism
noop No authentication (pass-through)
api-key Injects API key from credential store
oauth2-client-credentials Client credentials grant (form/basic), token caching with tenant isolation

Request Hardening

Control Protection
Path traversal Alias extracted from first path segment; only suffix is normalized
Body size Configurable cap (default 100 MB)
Content validation Content-Type and Transfer-Encoding validated before forwarding
Hop-by-hop headers Stripped per HTTP spec; internal headers controlled
CORS OPTIONS preflight returns 204 without upstream resolution; real requests validate Origin/method against config
Rate limiting Per-upstream/route limits enforced in the data plane
Error isolation X-OAGW-Error-Source header distinguishes gateway errors from upstream errors; RFC 9457 problem responses

6. Compile-Time Linting — Clippy

Source: Cargo.toml (workspace.lints.clippy) · clippy.toml

The project enforces 90+ Clippy rules at deny level, including the full pedantic group. Security-relevant highlights:

Rule Why It Matters
unwrap_used, expect_used Prevents panics in production (denial-of-service)
await_holding_lock, await_holding_refcell_ref Prevents deadlocks in async code
cast_possible_truncation, cast_sign_loss, cast_precision_loss Prevents silent data corruption
integer_division Prevents silent truncation
float_cmp, float_cmp_const Prevents incorrect equality checks
large_stack_arrays, large_types_passed_by_value Prevents stack overflows
rc_mutex Prevents common concurrency anti-patterns
regex_creation_in_loops Prevents ReDoS-adjacent performance issues
cognitive_complexity (threshold: 20) Keeps code reviewable and auditable

clippy.toml additionally enforces:

  • disallowed-methods blocking direct SeaORM execution methods (must use Secure wrappers)
  • disallowed-types blocking LinkedList (poor cache locality, potential DoS amplification)
  • Stack size threshold of 512 KB
  • Max 2 boolean fields per struct (prevents boolean blindness)

7. Compile-Time Linting — Custom Dylint Rules

Source: dylint_lints/

Project-specific architectural lints run on every CI build via cargo dylint. These enforce design boundaries that generic linters cannot:

ID Lint Security Relevance
DE0706 no_direct_sqlx Prohibits direct sqlx usage — forces all DB access through SeaORM/SecORM
DE0708 no_non_fips_hasher Prohibits sha2/sha1/md5 imports outside allow-list — prevents unreviewed non-FIPS crypto usage
DE0103 no_http_types_in_contract Prevents HTTP types leaking into contract layer
DE0301 no_infra_in_domain Prevents domain layer from importing sea_orm, sqlx, axum, hyper, http
DE0308 no_http_in_domain Prevents HTTP types in domain logic
DE0801 api_endpoint_version Enforces versioned API paths (/{service}/v{N}/{resource})
DE1301 no_print_macros Forbids println!/dbg! in production code (prevents info leakage)

The architectural lints in the DE03xx series enforce strict layering (contract → domain → infrastructure), preventing accidental coupling that could undermine security boundaries.

8. Dependency Security — cargo-deny

Source: deny.toml · CI job: .github/workflows/ci.yml (security job)

cargo deny check runs in CI and enforces:

  • RustSec advisory database — known vulnerabilities are treated as hard errors
  • License allow-list — only approved OSS licenses (MIT, Apache-2.0, BSD, MPL-2.0, etc.)
  • Source restrictions — only crates.io allowed; unknown registries and git sources warned
  • Duplicate version detection — warns on multiple versions of the same crate in the dependency graph

9. Cryptographic Stack & FIPS-140-3

Source: FIPS PRD · ADRs in docs/security/fips/adrs/ · libs/toolkit/src/bootstrap/crypto.rs · libs/rustls-corecrypto-provider/ · deny-fips.toml

Default (non-FIPS) cryptographic stack

The project uses aws-lc-rs (via rustls) as its primary TLS cryptographic backend. JWT validation uses jsonwebtoken.

Layer Library Backend
TLS rustls + hyper-rustls aws-lc-rs
Certificate verification rustls-webpki aws-lc-rs, ring
JWT validation jsonwebtoken sha2, hmac, ring
Database TLS sqlx (tls-rustls-aws-lc-rs) aws-lc-rs

FIPS-140-3 build (--features fips)

Gears applications can be built with FIPS-validated cryptography by enabling the fips feature flag on cf-gears-toolkit, cf-gears-toolkit-http, and any binary that ships TLS. A single feature flag selects a per-target CMVP-validated backend behind one shared rustls 0.23 TLS state machine. The TLS data plane routes through a FIPS 140-3 validated module on Linux and macOS; on Windows it is currently FIPS 140-2 (140-3 in CMVP processing — see the per-target table and What this does NOT claim):

Target Validated module How it routes
Linux (x86_64, aarch64) AWS-LC FIPS Provider v2 — CMVP cert #4816 (FIPS 140-3) rustls/fips + aws-lc-fips-sys; activated via target-gated shim cf-gears-rustls-fips-shim
macOS (any arch) Apple corecrypto User-Space Module — per-macOS-major CMVP cert (search by "Apple corecrypto Module" on the CMVP database) In-tree cf-gears-rustls-corecrypto-provider over Security.framework + CommonCrypto
Windows (x86_64) Microsoft Windows CNG — Cryptographic Primitives Library (bcryptprimitives.dll, loaded via bcrypt.dll); per-Windows-build CMVP cert, currently FIPS 140-2 validated (FIPS 140-3 in CMVP processing — see What this does NOT claim) Community rustls-cng-crypto (caret-pinned 0.1.x); requires OS-level FIPS-mode via HKLM\System\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy = 1
cargo build -p cf-gears-server --features fips

toolkit::bootstrap::init_crypto_provider is invoked automatically as the first step of init_procedure (used by run_server and run_migrate) — no explicit setup in main(). The function dispatches per cfg(target_os, feature = "fips") and installs the per-OS provider once via OnceLock. Subsequent calls return the cached first-call result.

Build prerequisites:

Target Toolchain
Linux + fips C toolchain, cmake, perl, go (required by aws-lc-fips-sys build script — module integrity checks)
macOS + fips Xcode Command Line Tools + Rust toolchain. No cmake / perl / go. The per-target shim excludes aws-lc-fips-sys from the macOS build graph entirely.
Windows + fips (native) MSVC + Windows SDK. No cmake / perl / go. CNG is loaded at runtime from bcrypt.dll.
Windows + fips (cross-compile from Linux/macOS) cargo install cargo-xwin plus ninja. See make check-windows-fips.

Algorithm scope on the wire

Under --features fips, the ClientHello and ServerHello offer only FIPS-Approved algorithms. No ChaCha20-Poly1305, X25519, X25519MLKEM768 / post-quantum hybrids, Ed25519, MD5, or SHA-1 (outside the SP 800-131A legacy signature-verify allowance).

Category Algorithms
TLS versions TLS 1.2, TLS 1.3 (no TLS 1.0/1.1). macOS is TLS 1.3-only under FIPS — see TLS 1.2 PRF note below.
TLS 1.3 cipher suites TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384
TLS 1.2 cipher suites ECDHE_{ECDSA,RSA}_WITH_AES_{128,256}_GCM_SHA{256,384} (×4 — Linux + Windows only)
Key exchange NIST P-256, P-384 ECDHE
Signature verify ECDSA P-256/P-384/P-521, RSA-PSS, RSA PKCS#1 v1.5 (SHA-256/384/512)
Signature sign (server-side / mTLS) Same scope as verify; routed through the validated module (SecKeyCreateSignature on macOS, BCryptSignHash on Windows, EVP_PKEY_sign on Linux)
Hash / HMAC / HKDF SHA-256, SHA-384 (HKDF is an Approved KDF per NIST SP 800-56C)
TLS 1.2 Extended Master Secret (RFC 7627) required (require_ems = true) per NIST SP 800-52 Rev. 2 §3.5
RSA modulus floor ≥ 2048 bits (NIST FIPS 186-5 §5.1) — enforced at server-side key load on macOS

Build-time dependency-graph policy

A FIPS build must not pull in a non-FIPS-validated crypto crate, even transitively. We enforce this at dependency-resolution time via cargo-deny: under --features fips, the build fails if any banned crypto crate appears in cargo tree. This is the Rust analogue of Go 1.25's fips140=only runtime gate — Rust has no language-level hook for "refuse this crypto call", so we cut it off one step earlier, before the offending code ever links. Configured via deny-fips.toml; rationale in ADR 0005.

make fips-policy        # Standalone — runs cargo deny check bans --config deny-fips.toml
make security           # Runs both `deny` (license/advisory) and `fips-policy`

Phase A (shipped) bans crates not currently in the graph — zero-pain regression gate: future PRs adding md2/md4/ripemd, chacha20poly1305/salsa20, the Curve25519 family (x25519-dalek, ed25519-dalek, …), alternative TLS frameworks (openssl, boring, native-tls), or alternative rustls CryptoProviders (rustls-symcrypt, rustls-mbedcrypto-provider, rustls-openssl, rustls-rustcrypto, rustls-graviola, rustls-wolfcrypto-provider, boring-rustls-provider) all fail the gate.

Non-FIPS hasher guard — Dylint lint DE0708 (no_non_fips_hasher) rejects new sha2/sha1/md5 imports outside an explicit allow-list (one entry: file-storage content hashing — see Non-cryptographic sha2 and rand usage), preventing unreviewed non-FIPS crypto usage from creeping in. All previous direct use sites have been replaced with inline FNV-1a (a deterministic, non-cryptographic fingerprint): libs/toolkit-odata/src/pagination.rs (cursor consistency) and oidc-authn-plugin/src/infra/token_client.rs (credential cache key). sha2 remains in the dependency graph as a Phase B transitive (via sqlx-core, sqlx-postgres, lopdf, rust-embed-utils) and will be promoted to Phase A once those pull-throughs are eliminated.

Phase B (pending transitive cleanup) is documented inline in deny-fips.tomlring, non-FIPS aws-lc-rs, chacha20, md-5, sha1, blake2/blake3, aes, hmac, hkdf, etc. — currently pulled by upstream deps (pingora-rustls/ureq, rustls's default features, rand). Each moves to Phase A as its upstream pull-through is replaced. Tracking: ADR 0005 §"Phasing" and FIPS PRD §13 TODO-7 — promotion to Phase A is the unit of work; no per-crate sub-tickets today.

Runtime Operational Environment validation

A FIPS 140-3 claim is only valid when the running OS version lies inside the Operational Environment (OE) listed on the active CMVP certificate. The OE check has different shapes per OS:

Target Runtime gate Behaviour on mismatch
macOS cf_gears_rustls_corecrypto_provider::oe::validate_oe() reads kern.osproductversion via sysctlbyname and matches against SUPPORTED_OE_MACOS_MAJOR. Fires as a side-effect of the first fips_provider() call. Under --features fips: panic (deliberate fail-closed — cannot be silently caught by an intermediate Result-handling caller). Override via CF_GEARS_FIPS_OE_OVERRIDE=1 for CI on pre-release macOS only.
Linux Not yet implemented at runtime. OE coverage verified manually per release (CMVP cert search for cert #4816). Tracked as TODO-8 in FIPS PRD §13.
Windows OS-level FIPS-mode flag check (via rustls-cng-crypto's empty-provider gate). Build-number-vs-CMVP-OE check not yet implemented at runtime. If FipsAlgorithmPolicy = 0: bootstrap refuses to install the provider. Build-number OE check tracked as TODO-8.

Runtime failure modes

What a --features fips process does when its environment cannot satisfy the FIPS claim. Each row is fail-closed — the gear never serves traffic under a degraded or non-validated provider.

OS (--features fips) Failure mode Process behaviour
macOS OE mismatch — running macOS major ∉ SUPPORTED_OE_MACOS_MAJOR, with CF_GEARS_FIPS_OE_OVERRIDE unset Fail-closed panic. oe::validate_oe() returns Errfips_witness_ok() caches false ⇒ every primitive's fips() returns falseCryptoProvider::fips() is false ⇒ the post-install assert!(provider.fips()) in init_crypto_provider panics. Defense-in-depth: even if that assert were bypassed, tls::apply_fips_hardening returns Err(TlsConfigError::FipsHardeningFailed), so any TLS-config build also fails recoverably. CI on pre-release macOS sets CF_GEARS_FIPS_OE_OVERRIDE=1.
Windows OS FIPS-mode off (FipsAlgorithmPolicy != 1) Bootstrap fails closed (no panic). rustls_cng_crypto::fips_provider() yields a provider with empty cipher_suites; init_crypto_provider detects the empty provider and returns CryptoProviderError::SystemFipsModeNotEnabled; the binary refuses to start.
Linux AWS-LC POST (power-on self-test) failure Panic at provider construction. rustls::crypto::default_fips_provider() (AWS-LC FIPS module) aborts on a POST failure; init_crypto_provider returns Err for the install-conflict case it can observe, but an underlying POST failure propagates as a panic/abort. main.rs / init_procedure handle the Err path from init_crypto_provider but cannot catch the POST abort.

TLS configuration knobs

The HTTP client (cf-gears-toolkit-http) exposes the following transport knobs (see libs/toolkit-http/src/config.rs). None of them relax FIPS hardening — under --features fips, tls::apply_fips_hardening still asserts ClientConfig::fips(), so any setting incompatible with the active FIPS provider surfaces as an error at build time.

Knob Type Effect
Transport security TransportSecurity (TlsOnly | AllowInsecureHttp); HttpClientBuilder::deny_insecure_http() Whether cleartext http:// is permitted. Defaults to TlsOnly under --features fips, AllowInsecureHttp otherwise. Selecting AllowInsecureHttp under FIPS makes build() return HttpError::InsecureTransport.
Minimum TLS version TlsVersion (Tls12 | Tls13) Tls12 advertises TLS 1.2 + 1.3 (rustls safe default); Tls13 is TLS 1.3-only. Does not relax FIPS hardening.
Mutual TLS identity ClientAuthConfig PEM cert-chain + private-key file paths (PKCS#8 / PKCS#1 / SEC1). Files are read lazily at build; no key bytes are held in the cloneable config.
Trust roots TlsRootConfig Selects the certificate trust anchors (e.g. the native OS root store).
Extended Master Secret require_ems (set true by apply_fips_hardening) Enforces RFC 7627 EMS under FIPS per NIST SP 800-52 Rev. 2 §3.5.

Non-cryptographic sha2 and rand usage

Any residual sha2 / rand usage in the tree is non-cryptographic and is not part of the FIPS claim. Non-cryptographic fingerprints use inline FNV-1a (OData pagination cursor consistency in libs/toolkit-odata/src/pagination.rs; the OIDC token-cache key in oidc-authn-plugin), and the rand ecosystem is pulled in transitively rather than used for key material on the TLS data plane. New sha2/sha1/md5 imports are rejected at compile time by Dylint DE0708 (no_non_fips_hasher) outside an explicit allow-list. See the Non-FIPS hasher guard note above and What this does NOT claim below for the transitive-dependency posture.

DE0708 allow-list entry — file-storage content hashing. gears/file-storage/file-storage/src/infra/content/hash.rs is the single SHA-256 call site in the file-storage gear and is on the DE0708 allow-list. It is used for content addressing/integrity — the expected_hash upload constraint and version-identity check (SHA-256 is mandated by file-storage ADR-0002) — and to derive the opaque content ETag. It is not used for signatures, key derivation, or password storage: the signed-URL signing primitive runs behind a replaceable SignatureProvider abstraction (file-storage ADR-0004), so a FIPS deployment swaps the signing module without touching this hasher. All sha2 usage in the gear is confined to this one reviewable module.

File-storage content hashing is out of FIPS scope

File-storage's content hash is excluded from the FIPS claim — but the exclusion turns on the hash's purpose, not on its algorithm. Both content-hash modes are SHA-256 throughout (a FIPS-Approved algorithm), so there is no non-Approved-algorithm question to carve out in the first place: single-part uploads use sha256(whole object); multipart uploads use a bespoke SHA-256 composite — a one-level Merkle over per-part SHA-256 digests (root = sha256 of a "v1,{offset}:{sha256(part)},…" manifest). Neither mode is a FIPS-scoped security function: the job of this hash is to verify that a stored file was not corrupted and was split into parts and uploaded/reassembled correctly (content-addressed identity/dedup), not to defend a security boundary against deliberate tampering. That non-adversarial purpose — not an algorithm-approval exception — is what places it outside the FIPS-Approved-algorithm list in Algorithm scope on the wire. This sits on top of the existing decoupling described above: content hashing already runs through a plain sha2 crate call site, outside the FIPS-validated TLS module.

The one exception is the expected_hash upload-verification path, which is security-relevant: it defends against a client falsely claiming a different object than it actually uploaded, so it remains SHA-256 and is not covered by this exclusion.

Because both modes are already 100% SHA-256, there is no build-time Cargo feature gating and no config-time rejection to enforce on FIPS builds — the exclusion is a scoping clarification, not a runtime guardrail.

Recorded in file-storage ADR-0006 (cpt-cf-file-storage-adr-content-hash-modes), which supersedes ADR-0002 for the content-hash-modes decision and records the two-mode, SHA-256-only design. This is an additive clarification of scope, not a change to the TLS/signing FIPS-module claims described elsewhere in this section.

Approved-only deployment checklist

Before relying on the FIPS claim in a deployment:

  • OS FIPS mode — confirm the OS is in an approved state: Windows HKLM\System\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy = 1; macOS major version inside SUPPORTED_OE_MACOS_MAJOR; Linux OE inside CMVP cert #4816.
  • Static-linkage check — run the linkage smoke (otool -L on macOS, dumpbin /imports on Windows) and confirm only the OS-supplied / validated crypto module is loaded — see How to verify a build is FIPS-conformant.
  • No-sccache FIPS build job — build the FIPS provider in a dedicated CI job with sccache disabled, so AWS-LC FIPS integrity / self-test artifacts are produced fresh rather than served from a compilation cache.

Enabling OS FIPS mode (Windows)

The Windows CNG FIPS provider only enforces its FIPS-Approved algorithm subset when the operating system itself is in FIPS mode; otherwise Gears bootstrap fails closed with CryptoProviderError::SystemFipsModeNotEnabled. Enable system-wide FIPS mode via Group Policy: Computer Configuration → Windows Settings → Security Settings → Local Policies → Security Options → "System cryptography: Use FIPS compliant algorithms for encryption, hashing, and signing" → Enabled. Or via the registry:

reg add HKLM\System\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy /v Enabled /t REG_DWORD /d 1 /f

A reboot is required after either change. See Microsoft's FIPS 140 validation reference for the authoritative posture documentation.

How to verify a build is FIPS-conformant

# 1. Wire-level — what the ClientHello actually offers:
cargo run -p cf-gears-fips-probe --features fips -- --url https://www.howsmyssl.com/a/check
# Expected: given_cipher_suites = AES-GCM only, given_named_groups = secp256r1/secp384r1,
#           post_quantum_key_agreement: false, [OK] No ChaCha20 in ClientHello.

# 2. Dep-graph regression (cheap, no compile step):
cargo tree --target aarch64-apple-darwin -p cf-gears-example-server --features fips \
  -e features | grep -E 'corecrypto|aws-lc-fips'
# Expected on macOS: cf-gears-rustls-corecrypto-provider present, aws-lc-fips-sys ABSENT.

cargo tree --target x86_64-unknown-linux-gnu -p cf-gears-example-server --features fips \
  -e features | grep 'aws-lc-fips'
# Expected on Linux: aws-lc-fips-sys present.

cargo tree --target x86_64-pc-windows-msvc -p cf-gears-example-server --features fips \
  -e features | grep -E 'cng-crypto|aws-lc-fips'
# Expected on Windows: rustls-cng-crypto present, aws-lc-fips-sys ABSENT.

# 3. Linkage smoke — confirm only OS-supplied crypto framework is loaded:
otool -L target/release/cf-gears-server | grep -E 'aws|crypto|ssl|ring'      # macOS — expect only Security.framework
dumpbin /imports target\release\cf-gears-server.exe | findstr /i "bcrypt aws"  # Windows — expect only bcrypt.dll
vmmap <cf-gears-server-pid> | grep -E 'corecrypto|Security\.framework'        # macOS runtime — confirm corecrypto is mapped into the live process

# 4. Dep-graph policy regression (rejects non-FIPS crypto crates):
make fips-policy

# 5. Wire-shape regression for our macOS provider:
cargo test -p cf-gears-rustls-corecrypto-provider --features fips --test fips_provider_invariants

See examples/cf-gears-fips-probe/README.md for the full four-layer verification chain (linkage, runtime, wire-level, cert-validation).

What this does NOT claim

  • Gears itself is not on the CMVP Validated Modules list. The validated modules are Apple corecrypto, AWS-LC FIPS Provider, and Microsoft Windows CNG. Gears are consumers of those modules.
  • The Windows path is FIPS 140-2 today, not 140-3. The Windows CNG module that Gears routes through — the Cryptographic Primitives Library (bcryptprimitives.dll) — holds a CMVP FIPS 140-2 certificate per Windows build; Microsoft's FIPS 140-3 validation of the Windows cryptographic modules is still in CMVP processing. A --features fips Windows binary therefore carries a 140-2 claim on the TLS data plane until the 140-3 certificate is issued. Linux (AWS-LC, cert #4816) and macOS (Apple corecrypto) carry 140-3 claims.
  • CMVP OE-coverage is the deployment's responsibility. A FIPS claim is void if the running OS version is not inside the cert's OE. The macOS runtime gate is fail-closed; Linux + Windows OE coverage is verified manually per release.
  • CryptoProvider::fips() = true is a runtime witness, not just design intent. On macOS it reflects the OE check (oe::fips_witness_ok); on Windows, the OS FIPS-mode flag. On Linux, runtime OE-validation is not yet implemented; OE coverage is verified manually per release via the §release-checklist CMVP-cert search.
  • TLS 1.2 PRF on macOS is not CAVS-listed. Apple corecrypto exposes generic HMAC primitives but not a CAVS-listed dedicated TLS PRF (unlike aws-lc-fips's tls_prf::Algorithm). Consequence: fips_provider() on macOS is TLS-1.3-only; customers requiring TLS 1.2 on macOS+FIPS must accept that those connections do not carry a FIPS claim.
  • JWT signature validation does not go through the FIPS path. jsonwebtoken uses ring / non-FIPS aws-lc-rs for RSA / ECDSA verification on bearer tokens. Treat tokens as authentication context, not as data covered by the cryptographic claim. Out of scope today; tracked as TODO-7 in FIPS PRD §13. Cleanup is gated by deny-fips.toml Phase B promotion.
  • Non-FIPS crypto crates remain in the final binary on macOS+fips. ring is pulled in transitively by pingora-rustls, pingora-pool, and ureq; non-FIPS aws-lc-rs is pulled in by rustls's default feature set; chacha20 is pulled in by the rand ecosystem. These are not invoked on the TLS data plane (the installed CryptoProvider routes every TLS primitive through the validated module) but the symbols are linked into the binary. Linkage smoke (above) confirms no non-validated shared libraries appear at runtime.
  • Server-side TLS keys load from PEM/DER bytes by default. The bytes transit user-space memory before reaching SecKeyCreateWithData / BCryptImportKeyPair / EVP_PKEY_new. Strict-FIPS auditors operating under "no plaintext CSPs outside the boundary" require a Keychain / NCrypt / HSM flow; tracked as TODO-1.
  • Server-side TLS termination (inbound HTTPS) is out of scope. The FIPS scope here covers the toolkit's outbound TLS data path; inbound HTTPS termination is delegated to the reverse proxy in front of the gear and is the deployment's responsibility.
  • The wrapper crates are not themselves CMVP-listed. Neither rustls-cng-crypto nor cf-gears-rustls-corecrypto-provider is a CMVP-validated module — each is a thin wrapper over the validated system module it consumes (Windows CNG and Apple corecrypto respectively). The chain-of-trust comes from the underlying validated module, not the wrapper crate.

Deep references

10. Continuous Fuzzing

Source: fuzz/ · CI workflow: .github/workflows/clusterfuzzlite.yml

Gears use cargo-fuzz with ClusterFuzzLite for continuous fuzzing. Fuzzing discovers panics, logic bugs, and algorithmic complexity attacks in parsers and validators.

Fuzz targets:

Target Priority Component Status
fuzz_odata_filter HIGH OData $filter query parser Implemented
fuzz_odata_cursor HIGH Pagination cursor decoder (base64+JSON) Implemented
fuzz_odata_orderby MEDIUM OData $orderby token parser Implemented
fuzz_yaml_config HIGH YAML configuration parser Planned
fuzz_html_parser MEDIUM HTML document parser Planned
fuzz_pdf_parser MEDIUM PDF document parser Planned

CI integration:

  • On pull requests: ClusterFuzzLite runs with address sanitizer for 10 minutes per target
  • On main branch / nightly: Extended 1-hour runs per target
  • Crash artifacts and SARIF results uploaded for triage

Local usage:

make fuzz          # Smoke test all targets (30s each)
make fuzz-run FUZZ_TARGET=fuzz_odata_filter FUZZ_SECONDS=300
make fuzz-list     # List available targets

11. Security Scanners in CI

Multiple automated scanners run on every pull request and/or on schedule:

Scanner What It Checks Trigger
CodeQL Static analysis for security vulnerabilities (Actions, Python, Rust) PRs to main + weekly schedule
OpenSSF Scorecard Supply-chain security posture (branch protection, dependency pinning, CI/CD hardness) Weekly + branch protection changes
cargo-deny RustSec advisories, license compliance, source restrictions Every CI run
ClusterFuzzLite Crash/panic/complexity bugs via fuzzing with address sanitizer PRs to main/develop
Dependabot Dependency alerts (including malware), security updates, version updates Continuous (repository-level)
Snyk Dependency vulnerability scanning Configured at repository/organization level
Aikido Application security posture management Configured at repository/organization level

The OpenSSF Scorecard badge is displayed in the project README: OpenSSF Scorecard

12. PR Review Bots

Every pull request is reviewed by automated bots before human review:

Bot Mode Purpose
CodeRabbit Automatic on every PR AI-powered code review with security awareness
Graphite Manual trigger Stacked PR management and review automation
Claude Code Manual trigger LLM-powered deep code review

13. Specification Templates & SDLC

Source: docs/spec-templates/ · docs/spec-templates/gears-sdlc/

Gears follow a spec-driven development lifecycle where PRD and DESIGN documents are written before implementation. Security is addressed at multiple points:

  • PRD template — Non-Functional Requirements section references project-wide security baselines and automated security scans
  • DESIGN template — dependency rules mandate SecurityContext propagation across all in-process calls
  • ISO 29148 alignment — global guidelines reference guidelines/SECURITY.md for security policies and threat models
  • Testing strategy — 90%+ code coverage target with explicit security testing category (unit, integration, e2e, security, performance)
  • Git/PR record — all changes flow through PRs with review and immutable merge/audit trail

14. Repository Scaffolding — Gears CLI

Gears provide a CLI tool for scaffolding new repositories that automatically inherit the platform's security posture:

Inherited Configuration Description
Compiler configuration rust-toolchain.toml, workspace lint rules (#[deny(warnings)], 90+ Clippy rules at deny level), unsafe_code = "forbid"
Custom dylint rules Architectural boundary enforcement (DE01xx–DE13xx series), GTS validation (DE09xx)
Makefile targets make deny (cargo-deny), make fuzz (continuous fuzzing), make dylint (custom lints), make safety (full suite)
cargo-deny configuration deny.toml with RustSec advisory checks, license allow-lists, source restrictions

This ensures every new service or gear repository starts with the same defense-in-depth baseline described in this document, eliminating configuration drift across the platform.

15. Opportunities for Improvement

The following areas have been identified for future hardening:

  1. FIPS-140-3 — non-TLS crypto cleanup — the --features fips build routes the TLS data plane through a CMVP-validated module on Linux, macOS, and Windows (see §9). Open items, tracked in the FIPS PRD §13:
    • TODO-7 — JWT signature validation (jsonwebtoken) currently uses ring / non-FIPS aws-lc-rs. Audit the surface and either replace upstream, fork, or restrict JWT to symmetric HMAC. Build-time floor enforced via deny-fips.toml Phase B promotion.
    • TODO-8 — Runtime Operational Environment validation on Linux + Windows (macOS already has a sysctl-based fail-closed gate). Today OE coverage on Linux + Windows is verified manually per release via CMVP cert search.
    • TODO-1 — Keychain / NCrypt / HSM-stored private keys for server-side TLS (today's PEM/DER load is acceptable for development and most production deployments where filesystem permissions guard the key).
  2. Secure ORM type-column auto-injection — the ScopableEntity trait supports a type_col dimension, but automatic GTS type constraint injection from PDP → AccessScope → SQL WHERE is under development
  3. Tenant Resolver access-control plugins — the Unauthorized error variant is reserved in the SDK, but no production plugin enforces caller-vs-target authorization (the static plugin allows any caller to query any configured tenant; the single-tenant plugin uses identity matching only). A policy-backed plugin would enforce fine-grained tenant visibility
  4. Security guidelines in spec templates — add explicit security checklist sections to PRD and DESIGN templates (threat modeling, data classification, authentication requirements per feature)
  5. Security-focused dylint lints — extend the DE07xx series with additional rules:
    • Detecting hardcoded secrets or API keys
    • Enforcing SecretString / SecretValue usage for sensitive fields
    • Flagging raw SQL string construction
    • Validating SecurityContext propagation in gear handlers
  6. Fuzz target expansion — current implemented targets cover OData parsers (fuzz_odata_filter, fuzz_odata_cursor, fuzz_odata_orderby). Planned targets: fuzz_yaml_config, fuzz_html_parser, fuzz_pdf_parser, fuzz_json_config, fuzz_markdown_parser
  7. Kani formal verification — expand use of the Kani Rust Verifier for proving safety properties on critical code paths (make kani)
  8. SBOM generation — add Software Bill of Materials generation to CI for supply-chain transparency

This document is maintained alongside the codebase. For implementation-level security guidelines, see guidelines/SECURITY.md. For the authorization architecture, see docs/arch/authorization/DESIGN.md.