Mediumruntime/secret: Go 1.26’s Answer to Secrets Leaking in Core Dumps | by Cheikh seck | Jul, 2026 | Towards DevSitemapOpen in appSign up<br>Sign in
Medium Logo
Get app<br>Write
Search
Sign up<br>Sign in
Towards Dev
A publication for sharing projects, ideas, codes, and new theories.
runtime/secret: Go 1.26’s Answer to Secrets Leaking in Core Dumps
Your crash file is a confession you never wrote.
Cheikh seck
7 min read·<br>1 hour ago
Listen
Share
There is a file on your server right now that contains every secret your program has ever touched. It was written without your permission. It has no access controls. And when your process died — at 2am, on a Tuesday, in a container you forgot was running — the operating system photographed its entire mind and saved it to disk.<br>We call that photograph a core dump.<br>On Linux, with GOTRACEBACK=crash, Go doesn't ask. A SIGSEGV or SIGABRT triggers a full snapshot of the process: stack, heap, every span, every buffer, every plaintext token your service decoded at startup and "forgot" to wipe. The file lands in /tmp or the working directory. A supervisor catches it. Ships it to a post-mortem bucket. Some junior engineer downloads it three months later to debug a flaky test, and your production API key is now in their laptop's search index.<br>You never logged it. You never meant to. The kernel did it for you.<br>For years the accepted answer was to manually zero your buffers after use — the hand-written defer that wipes a slice byte by byte before the function returns. A spell to keep the demons out. But here's what nobody tells you: you don't own most of the copies of your secret. The runtime makes them behind your back. A stack that grew and abandoned its old frame. An interface boxing that copied your bytes onto the heap. A GC cycle that evacuated your buffer to a new span and left the old bytes sitting there, readable, until some later sweep bothers to reclaim them. Those copies were never yours to zero. And the compiler? It optimized your cleanup away the moment it proved the variable dead. The secret is gone from your code and alive in memory.<br>Go 1.26 answers this for the first time at the only layer that can actually answer it: the runtime itself. runtime/secret.<br>(This article was co-authored with https://liteagent.cloud/ . Try it today!)<br>Press enter or click to view image in full size
The entire surface is two functions<br>package secret // import "runtime/secret"<br>func Do(f func()) // execute f with secret-mode enabled for this goroutine<br>func Enabled() bool // true only inside Do, and only on supported platformsNo Init. No global registry. No config file. You wrap the dangerous region of execution and the runtime takes custody of the memory it allocates there. Enabled() exists so code inside the closure can ask am I actually protected right now — and the answer is false on macOS, on Windows, on anything that isn't linux/amd64 or linux/arm64. Everywhere else, Do is a silent pass-through. Your secret leaks and the code looks correct. That is the trap.<br>The baseline: this is you<br>// canary/control/main.go<br>hexStr := os.Getenv("CANARY_HEX")<br>c, _ := hex.DecodeString(hexStr) // heap allocation, no special handling<br>runtime.KeepAlive(c) // pin it live past its last use<br>time.Sleep(60 * time.Second) // hold the process open for the signalhex.DecodeString allocates c on the heap — a fresh, unmarked, fully-recoverable span. KeepAlive(c) is the only thing standing between this buffer and the optimizer; without it the compiler would prove c is dead after the decode and the whole experiment would be meaningless. The time.Sleep just keeps the process alive so the test can fire SIGSEGV into a living target.<br>We send the signal. The kernel writes the core. We open the 42-megabyte file and search it for the canary bytes.<br>Found: true.<br>The secret is in the crash. This is not a hypothetical — this is what nearly every Go service in production does today, including presumably yours.<br>The protected path — and the line that decides whether you’re safe or lying to yourself<br>// canary/secret/main.go<br>secret.Do(func() {<br>c, _ := hex.DecodeString(hexStr) // allocated inside secret mode<br>runtime.KeepAlive(c) // keep it live until Do returns<br>}) // ← allocations marked for erasure here
runtime.GC() // ← the line that separates safety from theater<br>time.Sleep(60 * time.Second)Now read the closure carefully, because the mechanics are subtle and unforgiving.<br>Inside secret.Do, the goroutine enters secret mode. Every heap allocation the function makes — including the one hex.DecodeString returns — is tracked by the runtime and tagged for erasure on return. KeepAlive(c) ensures c stays live until the closure exits, so the runtime's erase logic actually observes it rather than finding it already collected.<br>Then Do returns. And here is where people get burned: secret.Do does not wipe your memory when the function returns. It marks the allocations. The bytes are physically purged only on the next garbage-collection...