Skip to content

Navigation Menu

Sign in
Sign up

Latest commit

History

129 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

secmem

Go Reference CI Go Report Card

Harden secrets in memory — keep private keys, tokens, and passwords off the Go garbage-collected heap, in OS-locked pages excluded from swap and, where the platform allows, from core dumps and from other processes. Bytes are wiped on release by an architecture-specific routine and reached only through a borrowing closure, so the plaintext never outlives its use.

Pure Go (CGO_ENABLED=0), depending only on golang.org/x/sys.

Built as internal tooling for a set of the author's own projects, then extracted and generalized. Governance is BDFL: bug fixes, hardening, and speedups-without-regression are all welcome.

Honesty first

Every guarantee is stated per platform, together with what it does not protect against. A security library that overstates its guarantees is worse than none. So:

  • A protection that cannot be provided on a platform is reported through Capabilities, never silently skipped. Call Probe once at startup to see what is in force.
  • A platform with no lockable off-heap memory fails loudly (ErrNoSecureMemory) rather than degrading to unprotected heap — unless you opt in explicitly with WithInsecureFallback().
  • Every below is backed by a test, or is named in TESTING.md's "Deliberately not proven" list. The guard pages actually fault; the lock, dump, fork, THP and KSM flags are read back from the kernel's own /proc/self/smaps record on both allocation tiers; the memfd_secret isolation is checked against /proc/<pid>/mem and process_vm_readv from inside the process, from a separate process, and as root; the region wipe (truncate, emergency, slot release), redaction, and no-panic promises are fuzzed. A test that skips in CI fails the job unless the skip is on a per-runner allowlist, so a proof cannot stop running unnoticed. See KERNELS.md for the kernels the suite has been executed on.
  • secmem has not had an independent third-party security audit. Every claim here is self-verified by the suite that runs in CI, and self-verification is not an audit — TESTING.md lists what is measured and, in its "Deliberately not proven" section, what is not. See SECURITY.md.

Install

go get github.com/deadpoets/secmem

Quick start

buf, err := secmem.NewBuffer(rawKey) // rawKey is wiped after the copy
if err != nil {
 return err
}
defer buf.Destroy() // always defer immediately
err = buf.WithBytesErr(func(borrowed []byte) error {
 // borrowed is valid ONLY inside this closure — never store it.
 return sign(borrowed, msg)
})

For values you hold and might log, wrap them in a Secret: it renders as [REDACTED] through fmt, encoding/json, and log/slog. For scrubbing free-form log text, the redact subpackage provides a slog.Handler wrapper.

The platform guarantee matrix

enforced · best-effort (failure is reported, not fatal) · not provided · LOUD opt-in only. This table is the threat model's spine; see THREAT-MODEL.md for what none of it protects against.

Protection linux/amd64·arm64 (≥5.14, secretmem live †) linux (older / 32-bit / secretmem inert) darwin windows other (compile-checked, not executed)
Off the Go heap ✓ memfd_secret ✓ mmap ✓ mmap ✓ VirtualAlloc LOUD heap only
No swap (locked) ✓ mlock ✓ mlock ⚠ VirtualLock ‡
Kernel isolation (defeats passive reads — ptrace, /proc/<pid>/mem, crash dumps — including by root: the in-tree proof runs unprivileged, and CI repeats it as root) ✓ memfd_secret ✗ (falls to mlock)
Excluded from crash dumps ⚠ MADV_DONTDUMP ⚠ MADV_DONTDUMP ⚠ WER exclusion (reported by the registration call, not verified by a dump)
Not inherited across fork ⚠ MADV_DONTFORK ⚠ MADV_DONTFORK n/a
No THP/KSM secret copies ✓ madvise ✓ madvise n/a n/a
Guaranteed wipe on destroy ✓ asm + cache flush (the zeros are read back; the flush is structural, not measured) ✓ (amd64/arm64 asm; else ⚠ barriered store loop) ✓ asm ✓ asm (amd64/arm64) ⚠ barriered store loop, no flush
Guard pages + overflow canary ✗ (heap fallback)
Stack-frame scrub inside Scrub ✓ asm ✓ asm on amd64/arm64; ✗ stub elsewhere ✓ asm ✓ asm (amd64/arm64) ✗ stub
No async register dump into the window (preemption signal blocked) ✓ SIGURG+SIGPROF ✓ SIGURG+SIGPROF ✗ no pthread_sigmask binding ✗ unmaskable (SetThreadContext)
Vector registers cleared after the window, on the working thread ✓ asm ✓ asm on amd64/arm64; ✗ elsewhere ✓ asm ✓ asm (amd64/arm64)
Register + heap scrub (Scrub) ✓ with GOEXPERIMENT=runtimesecret ✓ if set (amd64/arm64)
Encrypted while sealed (Seal) ✓ CryptProtectMemory
Process hardening (HardenProcess) ✓ dumpable=0, no-new-privs ✓ ACG + strict handles
Fails loudly, never silently degrades ✓ (LOUD opt-in)

The suite has been executed on real linux/amd64 and linux/arm64 hardware, spanning kernels 5.10 through 7.x (see KERNELS.md). On arm64 (Ampere Altra), the memfd_secret L4 path, the guard-page fault, the /proc/self/mem isolation proof, and the architecture-specific wipe assembly all pass.

The four Scrub rows are separate because they degrade separately. On Linux the window blocks Go's preemption signal for its duration, so runtime.asyncPreempt cannot spill the whole register file onto the stack partway through a cipher round; on amd64 and arm64 it then zeroes the vector register file — where vectorised crypto keeps its working state, and which nothing in the Go runtime clears — before the window closes. What each reaches — and the residue that is a constraint of the Go runtime or the OS rather than something this library can fix — is set out in THREAT-MODEL.md. The stack-frame scrub is asserted by planting markers down the stack and reading the abandoned frames back as zero, on linux/amd64 and linux/arm64; the signal block is asserted against the kernel's own SigBlk for the calling thread, on linux/arm64 (Tegra 234, kernel 6.8.12; RK3328, kernel 6.18.35) and linux/amd64 (kernel 7.0.0); the vector clear is asserted by planting a pattern in the registers inside a window and reading them back zero after it, with a control that must show the pattern surviving when the clear is left out.

† Whether memfd_secret is live is not decided by the kernel version, and not even by CONFIG_SECRETMEM alone. It needs the kernel to be able to split the linear map at page granularity; on arm64 that means rodata=full (or DEBUG_PAGEALLOC, or KFENCE). A measured counter-example: an NVIDIA Jetson Orin Nano on kernel 6.8.12 ships CONFIG_SECRETMEM=y and still returns ENOSYS, because CONFIG_RODATA_FULL_DEFAULT_ENABLED is off — see KERNELS.md. Where it is inert, secmem reports "fallback" and uses mmap+mlock, honestly, per allocation. Read Capabilities at runtime; do not infer the tier from a config symbol or a uname.

VirtualLock is weaker than mlock: it pins pages into the process working set, not into physical memory. While the process is running they stay resident and are exempt from working-set trimming, but when the memory manager outswaps an idle process's working set as a whole, locked pages go to the pagefile with it (Microsoft's VirtualLock remarks; Raymond Chen, "VirtualLock only locks your memory into the working set"). No user-mode setting closes that; Capabilities.Warnings says so on Windows, and WINDOWS.md covers the working-set budget.

On a unified-memory SoC (Tegra, Apple Silicon, AMD APUs, most ARM SBCs) note also that locking a page constrains the CPU's view of it, not an on-die GPU/NPU sharing the same DRAM — see THREAT-MODEL.md.

Guard pages and the canary are a memory-safety bug-catcher, not a confidentiality control — they trap an accidental over/under-flow, and do nothing against a privileged reader of process memory (that is memfd_secret's job). The Windows sealed-state cipher raises the bar against memory dumps of a dormant secret; it is not cold-boot protection. Both are detailed in the godoc and the threat model.

Modules

  • secmem (this module) — SecureBuffer, SecureArena, Secret, Capabilities/Probe, Scrub, and the process-hardening helpers. Depends only on golang.org/x/sys.
  • secmem/redactSanitizer and an slog.Handler for boundary-level log scrubbing. Standard library only.
  • secmem/httpauth — an http.RoundTripper that injects a credential header per request from a SecureBuffer, so an HTTP client never holds the token as a long-lived string. Standard library only.

Two further modules live in this repository and are versioned and tagged independently, so neither adds anything to the core's dependency graph:

  • secmem-crypto — signing, AEAD, key agreement and KDFs that keep their key material inside a SecureBuffer for the whole operation rather than copying it out first. Includes an in-place RFC 8032 Ed25519 signer, because crypto/ed25519's FIPS-140 cache panics on mmap'd memory — the reasoning is set out in that module's README, up front, since "rolled their own Ed25519" is a claim that deserves scrutiny. Carries a wiping fork of golang.org/x/crypto/argon2 for the same kind of reason. Adds filippo.io/edwards25519, golang.org/x/crypto and golang.org/x/sys.
  • secmem-lint — a go/analysis analyzer (and go vet -vettool binary) that checks the borrowing-closure discipline at compile time: the slice handed to WithBytes must not escape the closure. It detects a documented set of escape shapes, not every possible one; its README lists exactly what it resolves. Depends only on golang.org/x/tools.

Documentation

Full API docs, per-symbol runnable Examples, and per-symbol guarantees are on pkg.go.dev. For end-to-end programs, examples/ holds a password register/login flow and a working, hardened SSH agent — each composing the library under real I/O, concurrency, and shutdown. Start with the package overview, then THREAT-MODEL.md for the limits, ADOPTION.md for putting it into an existing service (the secret inventory, the boundary map, and sizing the lock budget), PITFALLS.md for the mistakes that quietly defeat it, TESTING.md for how each claim is proven (or why it can't be), ENVIRONMENTS.md for behavior under root / non-root / containers, KERNELS.md for the Linux kernels the suite has run on, and WINDOWS.md for Windows editions/builds.

Contributing

Bug fixes, hardening, and speedups-without-regression are welcome — see CONTRIBUTING.md for the workflow. Contributed PRs need an approving review from the maintainer; every PR, the maintainer's included, has to pass the full required-check matrix before it can land. Found a vulnerability? See SECURITY.md — please don't file it as a public issue. Participation is governed by the Code of Conduct.

License

Apache-2.0. See LICENSE.

About

Pure-Go off-heap secret memory: mmap/mlock, memfd_secret L4 isolation, guard pages, fail-closed by default

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /