Prompt and context
A service handles short-lived keys on Linux amd64 and arm64. The team wants Go 1.26 experimental runtime/secret to erase registers, stack storage, and temporary heap allocations, reducing residual-key risk. Design adoption criteria, the secret.Do boundary, build and fallback policy, and explain why it does not replace KMS, permissions, rotation, process isolation, or memory-forensics controls. The core skill is reasoning about cryptographic-code boundaries, so this is a coding question.
What the interviewer evaluates
First, whether you accurately state that the experimental package exists only with GOEXPERIMENT=runtimesecret and is outside the Go compatibility promise.
Second, whether you understand that secret.Do controls cleanup timing for temporary storage in its call tree, not every external reference or persistent copy.
Third, whether you put the boundary around a small cryptographic operation instead of networking, logging, or long-lived caches.
Fourth, whether architecture, build, performance, and fallback behavior are explicit rather than silently assuming unsupported platforms.
Fifth, whether KMS/HSM, rotation, least privilege, core-dump controls, and tests form a defense in depth.
Questions to clarify first
- Are Go version, platform, and build flags pinned?
- Do keys come from KMS/HSM, process environment, files, or a database?
- Are you protecting short-lived intermediates or a long-resident private key?
- Are core dumps, debuggers, or memory-analysis tools enabled?
- Can the crypto library copy secrets to other goroutines, buffers, or logs?
- Can the service disable the experiment and use a compatibility implementation?
A 30-second answer
“runtime/secret is an experimental Go 1.26 package available only with GOEXPERIMENT=runtimesecret on supported platforms; it is not a stable API. secret.Do can wrap short key derivation or decapsulation work and help erase registers, stack storage, and new heap temporaries in its call tree, but it does not erase copies in external buffers, logs, caches, or swap. Production still needs KMS/HSM, rotation, least privilege, core-dump controls, and process isolation. I verify enabled and disabled builds, leakage boundaries, performance, and a fast fallback or key-rotation response.”
Detailed solution
Step 1: Pin the API and experiment
Go 1.26 runtime/secret exposes Do(func()) and Enabled(), but only when GOEXPERIMENT=runtimesecret is set. It currently targets Linux amd64 and arm64, and the experimental package is outside the Go 1 compatibility promise. Build scripts must record toolchain, platform, and flag.
Step 2: Choose a narrow erasure boundary
Put the smallest cryptographic temporary region in secret.Do, such as decapsulation, derivation, or one-time MAC computation. Do not wrap HTTP, database calls, or an uncontrolled dependency: a larger call tree increases cleanup cost, blocking time, and audit complexity.
func deriveKey(input []byte) ([]byte, error) {
var out []byte
secret.Do(func() {
out = deriveTemporaryKey(input)
})
return out, nil // out still needs explicit ownership and erasure rules
}The example expresses a boundary; it does not mean out is automatically zeroed. The caller still owns lifetime, copying, and erasure decisions.
Step 3: Find copies that are not automatic
An input slice may be copied, the return value leaves Do, logging and serialization allocate new buffers, and the garbage collector does not give user code a precise erasure moment. Reduce copies, never stringify secrets, and explicitly clear buffers still owned by the application at critical boundaries.
Step 4: Connect forward secrecy to key management
Erasing temporary memory narrows a residual window, but forward secrecy also needs short-lived session keys, rotation, old-key destruction, and controlled recovery. Root keys belong in KMS/HSM; the process receives only the minimum derived material. runtime/secret supplies no access control, revocation, or audit trail.
Step 5: Handle builds and fallback
Compile with and without the experiment. Enabled() can inform diagnostics or an implementation choice, but a disabled path must not be labeled equally protected. On unsupported platforms, use a compatibility function, an upgrade gate, or refuse to start; make the choice explicit in the threat model and availability plan.
Step 6: Measure performance and observability
Cleaning a call tree can affect latency, especially with large temporary allocations. Compare p50/p95 latency, allocations, and throughput with identical inputs. Log only feature-path and flag state, never secrets. “Zeroing succeeded” is not a direct business metric.
Step 7: Build tests and incident response
Test the build matrix, Enabled() behavior, crypto outputs, errors, copy boundaries, and concurrent calls. Combine disabled core dumps, memory-analysis tooling, and controlled fault drills for defense in depth. If the experiment fails, have a path to disable it, rotate affected keys, and shorten session TTLs.
A high-quality sample answer
“I treat runtime/secret as an experimental residual-window mitigation, not key management. I pin Go 1.26, Linux amd64/arm64, and the build flag, then put only key derivation or decapsulation inside secret.Do. I audit inputs, return values, logs, caches, and goroutine copies outside the call tree because they do not disappear automatically. Production keys still come from KMS/HSM with rotation, revocation, least privilege, core-dump controls, and process isolation. I test enabled and disabled builds, performance, and leak boundaries; if the experiment fails, I can disable it, rotate keys, and shorten session lifetimes.”
Common mistakes
- Treating the experiment as a stable API → upgrades or platform builds fail → pin version, flag, and support matrix.
- Assuming
Doerases every secret → external buffers, logs, and return values can remain → audit copies and ownership. - Wrapping an entire request → the boundary is too large and latency is unpredictable → wrap only the small cryptographic region.
- Using
Enabledas a security guarantee → a flag is not a threat defense → document experiment and defense in depth. - Ignoring KMS/HSM → the process retains root keys too long → separate root keys from short-lived derivations.
- Testing functionality only → residual, performance, and build differences remain → add matrix, leakage, and benchmark tests.
- Stringifying secrets for logs → more uncontrolled copies appear → avoid strings and redact.
- No emergency switch-off → a failure leaves only outage or risk → predefine flag, rotation, and session-shrink actions.
Follow-up questions
Follow-up 1: Does secret.Do guarantee the stack is zeroed on return?
The documentation describes clearing registers and stack temporaries used by the call before return, but that is not every copy held by user code. Slices, return values, logs, and caches outside the boundary remain the application’s responsibility.
Follow-up 2: Why do supported platforms matter?
Registers, stacks, and runtime implementation differ by architecture. Production builds must verify the platform matrix instead of assuming identical cleanup semantics.
Follow-up 3: Can a network request run inside Do?
It is discouraged. Network I/O enlarges the call tree and blocking window, and downstream libraries may copy secrets. Complete the small cryptographic operation first, then perform network work outside.
Follow-up 4: How do you handle a returned secret?
Define ownership, use duration, and erasure responsibility; minimize copies, clear the owning buffer when finished, and never convert the value to a string or long-lived cache entry.
Follow-up 5: Does it replace a forward-secret protocol?
No. It reduces residual-memory exposure. Forward secrecy depends on ephemeral session keys, rotation, destruction, and protocol design, plus KMS/HSM and access control.
Follow-up 6: What if the experiment crashes in production?
Disable the experimental path or roll back to a compatibility implementation, rotate potentially exposed keys, shorten affected session TTLs, and preserve build, performance, and error evidence for diagnosis.