Skip to content

FIPS 140-3

flagd is released in two variants.

The standard build is the default and uses the ordinary Go cryptography. The FIPS build is compiled against the Go Cryptographic Module v1.0.0, which holds NIST CMVP validation certificate #5247 and CAVP certificate A6650, and runs it in the FIPS 140-3 approved mode. Both variants are published for flagd and flagd-proxy on every release.

The validation covers the Go Cryptographic Module. flagd consumes the module and has not been submitted to CMVP. A FIPS-mode deployment may still need further controls to satisfy FedRAMP, DoD SRG, CMMC or a similar programme; those determinations depend on the whole system and its assessment.

The scope is cryptography performed inside the flagd process. It excludes TLS terminated by a proxy or service mesh in front of flagd, the platform and container runtime, and operator-supplied key material. flagd performs no authentication or authorization on any endpoint and expects to run inside a trust boundary.

Getting the FIPS build

Container images carry a -fips tag suffix:

docker pull ghcr.io/open-feature/flagd:latest-fips

Release archives carry it in the product position, so globs that match the standard artifacts do not pick up the FIPS ones:

flagd_0.16.2_Linux_x86_64.tar.gz        # standard
flagd-fips_0.16.2_Linux_x86_64.tar.gz   # FIPS

To build one:

GOFIPS140=v1.0.0 CGO_ENABLED=0 go build -tags fips140 -o ./bin/flagd-fips ./flagd

GOFIPS140=v1.0.0 selects the frozen, certified module snapshot and makes the binary default to GODEBUG=fips140=on. Use the literal version: the certified and inprocess aliases resolve differently per Go toolchain, and on Go 1.26 inprocess points at module v1.26.0, which is still pending CMVP review.

The fips140 build tag is what makes the binary require that module rather than merely report on it. The two belong together, and a binary built with the tag but without GOFIPS140 refuses to start rather than presenting itself as a FIPS build.

Verifying

The build settings are recorded in the binary's runtime/debug.BuildInfo and survive -trimpath and -ldflags "-s -w".

go version -m ./bin/flagd-fips | grep -E 'GOFIPS140|DefaultGODEBUG|tags'
build   -tags=fips140,fips140v1.0
build   DefaultGODEBUG=fips140=on
build   GOFIPS140=v1.0.0-c2097c7c

v1.0.0-c2097c7c uniquely identifies the module snapshot. fips140 is flagd's own tag; fips140v1.0 is added by the Go toolchain.

The running process reports the same state, both from the version command and in its startup log.

flagd version
flagd: v0.16.2 (a1b2c3d), built at: 2026-08-28
build variant: fips
FIPS 140-3 mode: enabled (Go Cryptographic Module v1.0.0, GOFIPS140=v1.0.0-c2097c7c)

The standard build reports build variant: standard and FIPS 140-3 mode: disabled.

Enforcement

FIPS mode is fixed at process start and is turned off with GODEBUG=fips140=off. A FIPS build started that way refuses to run, so a stray GODEBUG cannot quietly drop a deployment out of approved mode. There is no flag to override this: a binary that carries the fips140 tag either runs the certified module or does not run.

The standard build performs no such check and is unaffected by GODEBUG=fips140=off.

TLS restrictions

These apply to the FIPS build only. In FIPS mode crypto/tls will not negotiate a protocol version, cipher suite, curve or signature algorithm outside the approved set.

Parameter Permitted
Protocol versions TLS 1.2, TLS 1.3
Key exchange X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, P-256, P-384, P-521
TLS 1.3 cipher suites TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384
TLS 1.2 cipher suites TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
Peer certificates RSA 2048 bits or larger, ECDSA P-256/P-384/P-521, Ed25519

Excluded: SHA-1, 3DES, RC4, ChaCha20-Poly1305, plain X25519, RSA PKCS #1 v1.5 key transport, TLS 1.0 and TLS 1.1.

Moving from the standard build to the FIPS build

Clients that only offer ChaCha20-Poly1305 or plain X25519 will fail the handshake. Server certificates with RSA keys under 2048 bits are rejected. Check your clients and certificates before switching.

This applies to flagd's outbound sync connections as well as to the TLS it terminates. A flag source, or a Kubernetes API server, that negotiates outside the approved set will not be reachable from the FIPS build.

The policy filter lives in crypto/tls, which sits outside the validated module boundary and tracks the Go toolchain version. flagd pins its toolchain so the negotiable set stays fixed across releases.

Platform support

Go's FIPS 140-3 mode is unavailable on wasm, windows/386, openbsd and aix. A windows/386 build compiles but panics at startup, so that binary is no longer released. linux/386 is no longer released either. FIPS mode runs there, but 32-bit x86 Linux is not among the operating environments the module was validated on, so a build for it could not carry the same claim.

Cryptography used by flagd

These are security functions, all serviced by the validated module.

Algorithm Purpose Location
TLS 1.2/1.3 (AES-GCM, ECDHE, HKDF, HMAC) Evaluation and sync transport security flagd/pkg/service/flag-evaluation/connect_service.go, flagd/pkg/service/flag-sync/sync_service.go, core/pkg/sync/grpc/credentials/builder.go
ECDSA, RSA-PSS, RSA PKCS #1 v1.5, Ed25519 TLS certificate and signature verification crypto/tls, crypto/x509
CTR_DRBG Random number generation crypto/rand

The remaining uses are not security functions, so no approved-algorithm requirement applies to them.

Algorithm Purpose Location
SHA3-256 Detecting whether a fetched flag configuration changed core/pkg/utils/hash.go
SHA-256 OFREP SSE ETag fingerprint flagd/pkg/service/flag-evaluation/ofrep/sse/tracker.go
MurmurHash3-32 Fractional rollout bucketing core/pkg/evaluator/fractional.go
FNV-1a 32 Poll interval jitter offset core/pkg/sync/internal/polling/poller.go
UUIDv4 Default internal flag set identifier core/pkg/store/query.go

SHA3-256 and SHA-256 are approved algorithms and come from the validated module; flagd uses them for change detection and HTTP cache validation.

MurmurHash3 assigns an evaluation context to a bucket for percentage-based rollouts. The fractional operation specification mandates it so that every flagd provider, in every language, assigns the same unit to the same bucket. Predicting or colliding it reveals only which variant a context receives, which evaluating the flag already tells you.

FNV-1a spreads polling across the configured interval to avoid a thundering herd. UUIDv4 is an internal index key that is never exposed externally, and its entropy comes from crypto/rand.

Non-approved algorithms in the dependency tree

core/pkg/sync/blob/blob_sync.go links the gocloud.dev drivers for S3, GCS and Azure Blob so those sync sources work out of the box. Those SDKs pull non-approved primitives into the link closure:

  • golang.org/x/crypto/pkcs12 and its RC2 implementation, via azidentity
  • golang.org/x/crypto/chacha20poly1305, via google/s2a-go
  • crypto/md5 and crypto/sha1, via gocloud's Content-MD5 handling, the AWS checksum and SSO credential paths, and Azure AD MSAL

The standard library contributes crypto/des, crypto/rc4, crypto/sha1 and crypto/md5 on its own account, through crypto/tls and crypto/x509 legacy parsing, plus crypto/mldsa for post-quantum certificate signatures. The FIPS policy filter keeps that set out of negotiation.

flagd calls none of these for its own security functions. They run only on cloud provider credential and object integrity paths, where the SDKs need them for protocol compatibility, and the providers offer FIPS-validated and FedRAMP-authorized endpoints. Data in transit on those paths is protected by TLS from the validated module.

The full closure is recorded in fips-crypto-closure.txt, which CI regenerates and diffs, so any change to it has to be reviewed.

Keys and secrets

flagd generates, wraps, stores and zeroizes no cryptographic keys.

TLS private keys are read from operator-supplied PEM paths (--server-cert-path, --server-key-path) via tls.LoadX509KeyPair. OAuth2 client credentials and static Authorization headers for the HTTP sync source are read from configuration or mounted files and held in memory as Go strings, which cannot be zeroized. Protect them with file permissions, a secret manager and short credential lifetimes.