Skip to content

Security Model

dotsecenv uses a layered encryption model combining symmetric and asymmetric cryptography. For threat analysis and what dotsecenv does/doesn’t protect against, see the Threat Model.

See also Key Scope for org-wide vs repo-scoped keypairs.

┌─────────────────────────────────────────────────────────────┐
│ Secret Value │
│ "my-password" │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Symmetric Encryption (AES-256-GCM) │
│ │
│ Random session key (256-bit) + Nonce (96-bit) │
│ → Encrypted data + Authentication tag │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Asymmetric Encryption (GPG) │
│ │
│ Session key encrypted for each recipient's public key │
│ → One key blob per authorized identity │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Cryptographic Signature (GPG) │
│ │
│ SHA-256/SHA-512 hash signed with originator's key │
│ → Detached signature for integrity + non-repudiation │
└─────────────────────────────────────────────────────────────┘

The choice of SHA-256/SHA-512 depends on the originator’s key strength.

Asymmetric encryption (RSA/ECC) is slow for large data. Symmetric encryption (AES) is fast but requires secure key exchange.

Hybrid encryption combines the two:

  1. Generate a random session key (fast)
  2. Encrypt the data with AES-256-GCM under the session key (fast, authenticated)
  3. Encrypt the session key with each recipient’s public key (slow, but the payload is small)
  4. Sign the result with the originator’s private key (authenticity)

When sharing a secret with 5 people, the secret is encrypted once. Only the session key is encrypted 5 times:

Secret Value → AES-256-GCM → Encrypted Data (1 copy)
├─→ Session key encrypted for Alice
├─→ Session key encrypted for Bob
├─→ Session key encrypted for Carol
├─→ Session key encrypted for Dave
└─→ Session key encrypted for Eve

PropertyMechanismStrength
ConfidentialityAES-256-GCM256-bit symmetric
Key protectionGPG (RSA/ECC)2048-bit+ RSA, P-384+ ECC
IntegrityAEAD + HMAC128-bit authentication tag
AuthenticityDetached signaturesSHA-256/SHA-512
Non-repudiationGPG signaturesTied to specific key

dotsecenv cryptographic operations align with the following NIST and IETF standards:

RFC 9580 (OpenPGP, 2024 revision)

  • Mandates AEAD (Authenticated Encryption with Associated Data)
  • AES-256-GCM as the default symmetric cipher
  • Replaces RFC 4880’s CFB mode with modern authenticated encryption

NIST SP 800-38D (GCM Mode)

  • Specifies Galois/Counter Mode for AES
  • Provides both confidentiality and authenticity
  • 128-bit authentication tags prevent tampering

FIPS 186-5 (Digital Signature Standard, 2023)

  • Approves RSA, ECDSA, and EdDSA for digital signatures
  • Deprecates DSA (removed from dotsecenv’s approved algorithms)
  • EdDSA (Ed25519, Ed448) added as approved algorithms

Vault entries are signed using FIPS 186-5 compliant algorithms:

  • RSA: RSASSA-PKCS1-v1_5 or RSASSA-PSS (2048-bit minimum)
  • ECDSA: P-384 or P-521 curves
  • EdDSA: Ed25519 for performance, Ed448 for higher security

FIPS 140-3 (Security Requirements for Cryptographic Modules)

  • Default configuration enforces FIPS-compliant algorithm minimums
  • Actual compliance depends on underlying GPG/libgcrypt installation
  • Use FIPS-validated cryptographic libraries in regulated environments

Since v0.3.0, dotsecenv uses Go’s native FIPS 140-3 validated cryptographic modules, which provide certified implementations of AES, SHA, RSA, and ECC operations across all supported platforms.


dotsecenv uses FIPS 186-5 compliant defaults out of the box:

ComponentAlgorithm
SymmetricAES-256-GCM
HashSHA-256/SHA-512
AsymmetricRSA 2048+, ECC P-384/P-521, EdDSA Ed25519/Ed448

Every vault entry is signed. Verification happens on read:

Entry signature covers:
├── Secret key name
├── Available-to list (fingerprints)
├── Encrypted value blob
├── Timestamp
└── Originator fingerprint

If any field is modified, verification fails:

Terminal window
dotsecenv validate
# ✓ Vault header: valid
# ✓ Identity entries: 2 valid
# ✗ Secret entry 5: signature mismatch
# Error: vault validation failed

Hermetic E2E

A secrets management tool that “phones home” or makes unexpected network connections is a severe risk. dotsecenv’s CI pipeline includes hermetic e2e testing with two independent verifications:

  1. Vendored dependencies. Go modules are vendored, so the build needs no network.
  2. Dual verification. Two jobs check that zero network connections happen:
    • step-security/harden-runner blocks egress at the kernel level
    • A network namespace plus strace traces every network syscall in isolation
  3. Build and runtime are both hermetic. The two jobs cover different surfaces of the pipeline (see Hermetic guarantees across build and runtime).
  4. Auditable artifacts. Job summaries and downloadable syscall traces.
  5. Release gate. Releases are blocked if hermetic tests fail.
┌───────────────────────────────────────────────────────────────────────┐
│ GitHub Actions │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ Job 1: harden-runner Job 2: strace + namespace │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ egress-policy: block │ │ unshare --net (isolate) │ │
│ │ ──────────────────── │ │ strace -e network │ │
│ │ │ │ ────────────────────────│ │
│ │ make e2e │ │ │ │
│ │ (vendored, no net) │ │ make build e2e │ │
│ │ │ │ (namespace blocks) │ │
│ │ ↓ │ │ ↓ │ │
│ │ Job Summary: 0 conn │ │ Artifact: syscall.log │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ ✓ Both jobs must pass = zero network connections verified twice │
└───────────────────────────────────────────────────────────────────────┘

Hermetic guarantees across build and runtime

Section titled “Hermetic guarantees across build and runtime”

The two jobs cover different parts of the pipeline, not just the same part twice. The split exists because of an ordering constraint in harden-runner.

Surface under inspectionharden-runner jobstrace + netns job
go build (compile step)uses pre-built binaryrebuilds inside namespace
make e2e (runtime)yesyes
Enforcement layeruserspace egress interceptorkernel network namespace + syscall trace
Catches a bug in the other?yes (different mechanism)yes (different mechanism)

Why does only the strace job cover the build? harden-runner should be the first step of the job so a compromised later step cannot disable it. Once egress-policy: block is set, no later step has network access, including actions/setup-go, which has to fetch the Go toolchain. Vendoring removes the network requirement from go build itself but not from installing the compiler. So the harden-runner job consumes a pre-built bin/dotsecenv from the setup job and only exercises make e2e.

The strace job has no such constraint. It runs actions/setup-go before entering unshare --net, then builds and runs e2e inside the namespace. The kernel namespace is the isolation boundary, so the compile step is covered too. go build -mod=vendor is what makes the in-namespace build possible.

So every CI step has at least one job watching it, and the e2e phase has both.

The harden-runner tool is used by Microsoft, Google, CISA, DataDog, Intel, Kubernetes, Node.js, and 8,000+ open source projects. It detected the tj-actions/changed-files supply chain attack (CVE-2025-30066).

  1. Open any Hermetic E2E workflow run on GitHub Actions
  2. Check the Job Summary for harden-runner’s network activity report
  3. Download the network-syscall-trace artifact to see the strace log

You can also click the harden-runner dashboard link in the job summary for detailed analysis.