runtime/secret Zeroization
runtime/secret Zeroization
Thesis
runtime/secret (Go experiment) runs a function under a contract that temporary storage — registers, stack, and soon-unreachable heap — is erased for forward secrecy. It is not a replacement for careful key handling, and it is not covered by the Go 1 compatibility promise.
Mental model
GOEXPERIMENT=runtimesecret
secret.Do(func() {
// derive / use key material
})
// registers + stack used by the call tree cleared before return
// heap objects erased once GC proves them unreachable
Enable only when you build with GOEXPERIMENT=runtimesecret. Without that experiment, the package is not part of the build (build constraints exclude all Go files).
Worked example
On Go 1.27.2 this box ships the sources under $GOROOT/src/runtime/secret/ but the default toolchain has the experiment off. Documented API surface (from doc.go / secret.go):
// Build with: GOEXPERIMENT=runtimesecret
package main
import "runtime/secret"
func useKey(key []byte) {
secret.Do(func() {
// use key; do not let copies escape the Do call tree
_ = key
})
}From the package doc on this GOROOT:
Do ensures that any temporary storage used by
fis erased in a timely manner… registers and stack beforeDoreturns; heap as soon as GC sees values unreachable. Works even iffpanics or callsruntime.Goexit.
Lab note from this box:
go doc runtime/secret → package excluded without GOEXPERIMENT=runtimesecret
We did not rebuild the entire toolchain with the experiment here (no system package installs / long bootstrap). Treat the snippets as API-correct against the 1.27.2 sources; enable the experiment in a dedicated build when you need runtime verification.
Trap
Shipping secret.Do in production without the experiment (or without understanding that copies you deliberately escape still live). Anything reachable after Do returns is not erased. Logging the key, storing it in a global, or sending it on a channel defeats the point.
Boring rule
Keep key lifetimes short, avoid copies, and use secret.Do only when you have enabled the experiment and measured that your call tree does not escape sensitive buffers. Prefer boring crypto APIs (crypto/tls, crypto/mlkem, …) for protocol work.
Try this
- Read
$GOROOT/src/runtime/secret/doc.goon your Go 1.27+ install. - Bootstrap a long test with
GOEXPERIMENT=runtimesecretin a throwaway module when you can afford a toolchain rebuild. - Pair with the eMudhra incident write-up: rebuild binaries with patched toolchains — zeroization does not help a compromised build key.
Sources
- Go sources:
src/runtime/secret/doc.go(Go 1.27) - Go experiments:
internal/goexperiment(RuntimeSecret) - Forward secrecy (concept)
- Go Security: response to the eMudhra key disclosure (rebuild with patched 1.27.x / 1.26.x)