Go Proposal: Secret Mode
119 points
by enz
4 days ago
| 9 comments
| antonz.org
| HN
hamburglar
11 minutes ago
[-]
Personally, I’m more interested in what a process can do to protect a small amount of secret material longer-term, such as using wired memory and trust zones. I was hoping this would be an abstraction for that.
reply
raggi
21 minutes ago
[-]
This seems like it might be expensive (though plausibly complete), so I wonder if it’ll actually benchmark with a low enough overhead to be practical. We already struggle with a lack of optimization in some of the named target use cases - that said this also means there’s space to make up.
reply
voodooEntity
3 hours ago
[-]
Ok, i kinda get the idea, and with some modification it might be quite handy - but i wonder why its deemed like an "unsolvable" issue right now.

It may sound naive, but packages which include data like said session related or any other that should not persist (until the next Global GC) - why don't you just scramble their value before ending your current action?

And dont get me wrong - yes that implies extra computation yada yada - but until a a solution is practical and builtin - i'd just recommend to scramble such variables with new data so no matter how long it will persist, a dump would just return your "random" scramble and nothing actually relevant.

reply
raggi
33 minutes ago
[-]
It is fundamentally not possible to be in complete control of where the data you are working with is stored in go. The compiler is free to put things on the heap or on the stack however it wants. Relatedly it may make whatever copies it likes in between actions defined in the memory model which could leak arbitrary temporaries.
reply
willahmad
3 hours ago
[-]
without language level support, it makes code look like a mess.

Imagine, 3 level nesting calls where each calls another 3 methods, we are talking about 28 functions each with couple of variables, of course you can still clean them up, but imagine how clean code will look if you don't have to.

Just like garbage collection, you can free up memory yourself, but someone forgot something and we have either memory leak or security issues.

reply
HendrikHensen
2 hours ago
[-]
With good helpers, it could become something as simple as

    key := make([]byte, 32)
    defer scramble(&key)
    // do all the secret stuff

Unless I don't understand the problem correctly.
reply
kbolino
1 hour ago
[-]
There are two main reasons why this approach isn't sufficient at a technical level, which are brought up by comments on the original proposal: https://github.com/golang/go/issues/21865

1) You are almost certainly going to be passing that key material to some other functions, and those functions may allocate and copy your data around; while core crypto operations could probably be identified and given special protection in their own right, this still creates a hole for "helper" functions that sit in the middle

2) The compiler can always keep some data in registers, and most Go code can be interrupted at any time, with the registers of the running goroutine copied to somewhere in memory temporarily; this is beyond your control and cannot be patched up after the fact by you even once control returns to your goroutine

So, even with your approach, (2) is a pretty serious and fundamental issue, and (1) is a pretty serious but mostly ergonomic issue. The two APIs also illustrate a basic difference in posture: secret.Do wipes everything except what you intentionally preserve beyond its scope, while scramble wipes only what you think it is important to wipe.

reply
voodooEntity
22 minutes ago
[-]
Thanks, you brought up good points.

While in my case i had a program in which i created an instance of such a secret , "used it" and than scrambled the variable it never left so it worked.

Tho i didn't think of (2) which is especially problematic.

Prolly still would scramble on places its viable to implement, trying to reduce the surface even if i cannot fully remove it.

reply
voodooEntity
2 hours ago
[-]
Yep thats what i had in mind
reply
ok123456
1 hour ago
[-]
This proposal is worse because all the valuable regions of code will be clearly annotated for static analysis, either explicitly via a library/function call, or heuristically using the same boilerplate or fences.
reply
voodooEntity
19 minutes ago
[-]
Makes sense basically creating an easy to point out pattern for static analysis to find everything security related.

As another response pointed out, its also possible that said secret data is still in the register, which no matter what we do to the curr value could exist.

Thanks for pointing it out!

reply
compsciphd
3 hours ago
[-]
I could imagine code that did something like this for primatives

  secretStash := NewSecretStash()
  pString := secretStash.NewString()
  ....
  ....
  secretStash.Thrash()
yes, you now have to deal in pointers, but that's not too ugly, and everything is stored in secretStash so can iterate over all the types it supports and thrash them to make them unusable, even without the gc running.
reply
mbreese
1 hour ago
[-]
I used to see this is bash scripts all the time. It’s somewhat gone out of favor (along with using long bash scripts).

If you had to prompt a user for a password, you’d read it in, use it, then thrash the value.

    read -p “Password: “ PASSWD
    # do something with $PASSWD
    PASSWD=“XXXXXXXXXXXXXXXXXX”
It’s not pretty, but a similar concept. (I also don't know how helpful it actually is, but that's another question...)
reply
voodooEntity
2 hours ago
[-]
Thats even better than what i had in mind but agree also a good way to just scrumble stuff unusable ++
reply
compsciphd
27 minutes ago
[-]
I'm now wondering with a bit of unsafe, reflection and generics magic one could make it work with any struct as well (use reflection to instantiate a generic type and use unsafe to just overwrite the bytes)
reply
skywhopper
1 hour ago
[-]
Hard to understand what you’re asking. This is the solution that will practical and built-in. This is a summary of a new feature coming to Go’s runtime in 1.26.
reply
dpifke
3 days ago
[-]
Related: https://pkg.go.dev/crypto/subtle#WithDataIndependentTiming (added in 1.25)

And an in-progress proposal to make these various "bubble" functions have consistent semantics: https://github.com/golang/go/issues/76477

(As an aside, the linked blog series is great, but if you're interested in new Go features, I've found it really helpful to also subscribe to https://go.dev/issue/33502 to get the weekly proposal updates straight from the source. Reading the debates on some of these proposals provides a huge level of insight into the evolution of Go.)

reply
kmeisthax
7 minutes ago
[-]
I have to wonder if we need, say, a special "secret data" type (or modifier) that has the semantics of both crypto/subtle and runtime/secret. That is to say, comparison operators are always constant-time, functions holding the data zero it out immediately, GC immediately zeroes and deallocs secret heap allocations, etc.

I mean, if you're worried about ensuring data gets zeroed out, you probably also don't want to leak it via side channels, either.

reply
fsmv
3 days ago
[-]
One thing that makes me unsure about this proposal is the silent downgrading on unsupported platforms. People might think they're safe when they're not.

Go has the best support for cryptography of any language

reply
fastest963
3 days ago
[-]
I'm not sure there's a realistic alternative. If you need to generate a key then it has to happen somehow on unsupported platforms. You can check Enabled() if you need to know and intend to do something different but I assume most of the time you run the same function either way you'd just prefer to opt into secret mode if it's available.
reply
kbolino
2 hours ago
[-]
This is not what secret.Enabled() means. But it probably illustrates that the function needs to be renamed already. Here's what the doc comment says:

  // Enabled reports whether Do appears anywhere on the call stack.
In other words, it is just a way of checking that you are indeed running inside the context of some secret.Do call; it doesn't guarantee that secret.Do is actually offering the protection you may desire.
reply
awithrow
3 days ago
[-]
Why not just panic and make it obvious?
reply
kbolino
2 hours ago
[-]
One of the goals here is to make it easy to identify existing code which would benefit from this protection and separate that code from the rest. That code is going to run anyway, it already does so today.
reply
samdoesnothing
3 days ago
[-]
Does it? I'm not disputing you, I'm curious why you think so.
reply
pants2
3 days ago
[-]
Not OP, but Go has some major advantages in cryptography:

1. Well-supported standard libraries generally written by Google

2. Major projects like Vault and K8s that use those implementations and publish new stuff

3. Primary client language for many blockchains, bringing cryptography contributions from the likes of Ethereum Foundation, Tendermint, Algorand, ZK rollups, etc

reply
adastra22
1 hour ago
[-]
Do you mean “best support for cryptography in the standard library”?

Because there is tremendous support for cryptography in, say, the C/C++ ecosystem, which has traditionally been the default language of cryptographers.

reply
oncallthrow
4 hours ago
[-]
Meh, this is a defence in depth measure anyway

Edit: also, the supported platforms are ARM and x86. If your code isn’t running on one of those platforms, you probably know what you’re doing.

reply
ctoth
3 hours ago
[-]
Linux

Windows and MacOS?

Go is supposed to be cross-platform. I guess it's cross-platform until it isn't, and will silently change the semantics of security-critical operations (yes, every library builder will definitely remember to check if it's enabled.)

reply
hypeatei
3 hours ago
[-]
> Meh, this is a defence in depth measure

Which is exactly why it should fail explicitly on unsupported platforms unless the developer says otherwise. I'm not sure how Go developers make things obvious, but presumably you have an ugly method or configuration option like:

  dangerousAllowSecretsToLeak()
...for when a developer understands the risk and doesn't want to panic.
reply
kbolino
2 hours ago
[-]
This is a sharp-edged tool guarded behind an experimental flag. You are not meant to use it unless you want to participate in the experiment. Objections like this and the other one ("check if it's enabled" -- you can't, that's not what secret.Enabled() means) illustrate that this API may still need further evolution, which it won't get if it's never available to experiment with.
reply
teeray
39 minutes ago
[-]
I wonder if people will start using this as magic security sauce.
reply
jeffrallen
25 minutes ago
[-]
Wow, this is so neat. I spent some time thinking about this problem years ago, and never thought of such an elegant solution.
reply
maxloh
4 hours ago
[-]
> The new runtime/secret package lets you run a function in secret mode. After the function finishes, it immediately erases (zeroes out) the registers and stack it used.

I don't understand. Why do you need it in a garbage-collected language?

My impression was that you are not able to access any register in these language. It is handled by the compiler instead.

reply
jerf
3 hours ago
[-]
This is about minimizing attack surface. Not only could secrets be leaked by hacking the OS process somehow to perform arbitrary reads on the memory space and send keys somewhere, they could also be leaked with root access to the machine running the process, root access to the virtualization layer, via other things like rowhammering potentially from an untrusted process in an entirely different virtual context running on the same machine, and at the really high end, attacks where the government agents siezing your machine physically freeze your RAM (that is, reduce the physical temperature of your RAM to very low temperatures) when they confiscate your machine and read it out later. (I don't know if that is still possible with modern RAM, but even if it isn't I wouldn't care to bet much on the proposition that they don't have some other way to read RAM contents out if they really, really want to.) This isn't even intended as a complete list of the possibilities, just more than enough to justify the idea that in very high security environments there's a variety of threats that come from leaving things in RAM longer than you absolutely need to. You can't avoid having things in RAM to operate on them but you can ensure they are as transient as possible to minimize the attack window.

If you are concerned about secrets being zeroed out in almost any language, you need some sort of support for it. Non-GC'd languages are prone to optimize away zeroing out of memory before deallocation, because under normal circumstances a write to a value just before deallocation that is never effectfully read can be dropped without visible consequence to the rest of the program. And as compilers get smarter it can be harder to fool them with code, like, simply reading afterwards with no further visible effect might have been enough to fool 20th century compilers but nowadays I wouldn't count on my compiler being that stupid.

There are also plenty of languages where you may want to use values that are immutable within the context of the language, so there isn't even a way to express "let's zero out this RAM".

Basically, if you don't build this in as a language feature, you have a whole lot of pressures constantly pushing you in the other direction, because why wouldn't you want to avoid the cost of zeroing memory if you can? All kinds of reasons to try to avoid that.

reply
er4hn
4 hours ago
[-]
In theory it prevents failures of the allocator that would allow reading uninitialized memory, which isn't really a thing in Go.

In practice it provides a straightforward path to complying with government crypto certification requirements like FIPS 140 that were written with languages in mind where this is an issue.

reply
hamburglar
37 minutes ago
[-]
The Go runtime may not be the only thing reading your process’ memory.
reply
kbolino
2 hours ago
[-]
Go has both assembly language and unsafe pointer operations available. While any uses of these more advanced techniques should be vetted before going to production, they are obviously able to break out of any sandboxing that you might otherwise think a garbage collector provides.

And any language which can call C code that is resident in the same virtual memory space can have its own restrictions bypassed by said C code. This even applies to more restrictive runtimes like the JVM or Python.

reply
gethly
28 minutes ago
[-]
Yeah, I can hardly disagree with that sentiment myself.
reply
vlovich123
1 hour ago
[-]
Go is not a memory safe language. Even in memory safe languages, memory safety vulnerabilities can exist. Such vulnerabilities can be used to hijack your process into running untrusted code. Or as others point out sibling processes could attack yours. This underlying principle is defense in depth - you make add another layer of protection that has to be bypassed to achieve an exploit. All the chains combined raise the expense of hacking a system.
reply
tptacek
1 hour ago
[-]
Respectfully, this has become a message board canard. Go is absolutely a memory safe language. The problem is that "memory safe", in its most common usage, is a term of art, meaning "resilient against memory corruption exploits stemming from bounds checking, pointer provenance, uninitialized variables, type confusion and memory lifecycle issues". To say that Go isn't memory safe under that definition is a "big if true" claim, as it implies that many other mainstream languages commonly regarded as memory safe aren't.

Since "safety" is an encompassing term, it's easy to find more rigorous definitions of the term that Go would flunk; for instance, it relies on explicit synchronization for shared memory variables. People aren't wrong for calling out that other languages have stronger correctness stories, especially regarding concurrency. But they are wrong for extending those claims to "Go isn't memory safe".

https://www.memorysafety.org/docs/memory-safety/

reply
vlovich123
39 minutes ago
[-]
I’m not aware of any definition of memory safety that allows for segfaults- by definition those are an indication of not being memory safe.

It is true that go is only memory unsafe in a specific scenario, but such things aren’t possible in true memory safe languages like c# or Java. That it only occurs in multithreaded scenarios matters little especially since concurrency is a huge selling point of the language and baked in.

Java can have data races, but those data races cannot be directly exploited into memory safety issues like you can with Go. I’m tired of Go fans treating memory safety as some continuum just because there are many specific classes of how memory safety can be violated and Go protecting against most is somehow the same as protecting against all (which is what being a memory safe language means whether you like it or not).

I’m not aware of any other major language claiming memory safety that is susceptible to segfaults.

https://www.ralfj.de/blog/2025/07/24/memory-safety.html

reply
kittywantsbacon
4 hours ago
[-]
This would potentially protect against other process reading memory via some system compromise - they would be able to get new secrets but not old ones.
reply
leoh
56 minutes ago
[-]
Kind of stupid it didn’t have something like this to begin with tbh. It really is an incredible oversight when one steps back. I am fully ready to be downvoted to hell for this, but rust ftw.
reply
IshKebab
47 minutes ago
[-]
Rust doesn't have anything like this either. I think you misunderstood what it is.
reply
raggi
30 minutes ago
[-]
It doesn’t but the problem space is more constrained as you are at least in control of heap vs stack storage. Register clearing is not natively available though. To put it more simply: yes but you can write this in rust- you can’t write it in go today.
reply