[HN Gopher] Go Proposal: Secret Mode
       ___________________________________________________________________
        
       Go Proposal: Secret Mode
        
       Author : enz
       Score  : 133 points
       Date   : 2025-12-09 21:10 UTC (4 days ago)
        
 (HTM) web link (antonz.org)
 (TXT) w3m dump (antonz.org)
        
       | fsmv wrote:
       | 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
        
         | samdoesnothing wrote:
         | Does it? I'm not disputing you, I'm curious why you think so.
        
           | pants2 wrote:
           | 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
        
             | adastra22 wrote:
             | 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.
        
         | fastest963 wrote:
         | 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.
        
           | awithrow wrote:
           | Why not just panic and make it obvious?
        
             | kbolino wrote:
             | 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.
        
           | kbolino wrote:
           | 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.
        
         | oncallthrow wrote:
         | 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.
        
           | ctoth wrote:
           | _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.)
        
           | hypeatei wrote:
           | > 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.
        
             | kbolino wrote:
             | 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.
        
       | dpifke wrote:
       | 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.)
        
         | kmeisthax wrote:
         | 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.
        
       | maxloh wrote:
       | > 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.
        
         | er4hn wrote:
         | 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.
        
         | kittywantsbacon wrote:
         | 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.
        
         | jerf wrote:
         | 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.
        
         | kbolino wrote:
         | 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.
        
         | vlovich123 wrote:
         | 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.
        
           | tptacek wrote:
           | 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/
        
             | vlovich123 wrote:
             | 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
        
               | tptacek wrote:
               | Another canard, unfortunately. "Segfault" is simply Go's
               | reporting convention for things like nil pointer hits.
               | "Segfaults" are not, in fact, part of the definition for
               | memory safety or a threshold condition for it. All due
               | respect to Ralf's Ramblings, but I'm going to rest my
               | case with the Prossimo page on memorysafety.org that I
               | just posted. This isn't a real debate.
        
               | vlovich123 wrote:
               | > Segfault" is simply Go's reporting convention for
               | things like nil pointer hits.
               | 
               | Blatantly false. From Ralf's post:
               | 
               | > panic: runtime error: invalid memory address or nil
               | pointer dereference [signal SIGSEGV: segmentation
               | violation code=0x1 addr=0x2a pc=0x468863]
               | 
               | The panic address is 42, a value being mutated, not a nil
               | pointer. You could easily imagine this address pointing
               | to a legal but unintended memory address resulting in a
               | read or write of unintended memory.
        
               | tptacek wrote:
               | No, you can't, and the reason you know you can't is that
               | _it 's never happened_. That looks like a struct offset
               | dereference from a nil pointer, for what it's worth.
        
               | afdbcreid wrote:
               | Rust is susceptible to segfaults when overflowing the
               | stack. Is Rust not memory safe then?
               | 
               | Of course, Go allows more than that, with data races it's
               | possible to reach use after free or other kinds of memory
               | unsafety, but just segfaults don't mark a language memory
               | unsafe.
        
               | cyberax wrote:
               | Go is most emphatically NOT memory-safe. It's trivially
               | easy to corrupt memory in Go when using gorotuines. You
               | don't even have to try hard.
               | 
               | This stems from the fact that Go uses fat pointers for
               | interfaces, so they can't be atomically assigned. Built-
               | in maps and slices are also not corruption-safe.
               | 
               | In contrast, Java does provide this guarantee. You can
               | mutate structures across threads, and you will NOT get
               | data corruption. It can result in null pointer
               | exceptions, infinite loops, but not in corruption.
        
               | tptacek wrote:
               | This is just wrong. Not that you can't blow up from a
               | data race; you certainly can. Simply that any of these
               | properties admit to exploitable vulnerabilities, which is
               | the point of the term as it is used today. When you
               | expand the definition the way you are here, you impair
               | the utility of the term.
               | 
               | Serious systems built in memory-unsafe languages yield
               | continual streams of exploitable vulnerabilities; that
               | remains true even when those systems are maintained by
               | the best-resourced security teams in the world.
               | Functionally _no_ Go projects have this property. The
               | empirics are hard to get around.
        
         | hamburglar wrote:
         | The Go runtime may not be the only thing reading your process'
         | memory.
        
         | gethly wrote:
         | Yeah, I can hardly disagree with that sentiment myself.
        
       | voodooEntity wrote:
       | 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.
        
         | willahmad wrote:
         | 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.
        
           | compsciphd wrote:
           | 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.
        
             | voodooEntity wrote:
             | Thats even better than what i had in mind but agree also a
             | good way to just scrumble stuff unusable ++
        
               | compsciphd wrote:
               | 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)
        
             | mbreese wrote:
             | 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...)
        
           | HendrikHensen wrote:
           | 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.
        
             | voodooEntity wrote:
             | Yep thats what i had in mind
        
               | ok123456 wrote:
               | 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.
        
               | voodooEntity wrote:
               | 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!
        
               | ok123456 wrote:
               | > Makes sense basically creating an easy to point out
               | pattern for static analysis to find everything security
               | related.
               | 
               | This is essentially already the case whenever you use
               | encryption, because there are tell-tale signs you can
               | detect (e.g., RSA S-Box). But this will make it even
               | easier and also tip you off to critical sections that are
               | sensitive yet don't involve encryption (e.g., secure
               | strings).
        
             | kbolino wrote:
             | 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.
        
               | voodooEntity wrote:
               | 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.
        
         | skywhopper wrote:
         | 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.
        
         | raggi wrote:
         | 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.
        
           | to11mtm wrote:
           | Yeah, .NET tried to provide a specific type related to this
           | concept (SecureString) in the past and AFAIK there were were
           | two main problems that have caused it to fall into disuse;
           | 
           | First one being, it was -very- tricky to use properly for
           | most cases, APIs to the outside world typically would give a
           | byte[] or string or char[] and then you fall into the problem
           | space you mention. That is, if you used a byte[] or char[]
           | array, and GC does a relocation of the data, it may still be
           | present in the old spot.
           | 
           | (Worth noting, the type _itself_ doesn 't do that, whatever
           | you pass in gets copied to a non-gc buffer.)
           | 
           | The second issue is that there's not a unified unix memory
           | protection system like in windows; The windows implementation
           | is able to use Crypt32 such that only the current process can
           | read the memory it used for the buffeer.
        
             | evntdrvn wrote:
             | In case you're interested in a potential successor:
             | https://github.com/dotnet/designs/pull/147
        
       | leoh wrote:
       | 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.
        
         | IshKebab wrote:
         | Rust doesn't have anything like this either. I think you
         | misunderstood what it is.
        
           | raggi wrote:
           | 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.
        
             | purplesyringa wrote:
             | You can try to write it in Rust, doesn't mean you'll
             | succeed. Rust targets the abstract machine, i.e. the
             | wonderful land of optimizing compilers, which can copy your
             | data anywhere they want and optimize out any attempts to
             | scramble the bytes. What we'd need for this in Rust would
             | be an integration with LLVM, and likely a number of
             | modifications to LLVM passes, so that temporarily moved
             | data can be tracked and erased. The only reason Go can even
             | begin to do this is they have their own compiler suite.
        
       | teeray wrote:
       | I wonder if people will start using this as magic security sauce.
        
       | jeffrallen wrote:
       | Wow, this is so neat. I spent some time thinking about this
       | problem years ago, and never thought of such an elegant solution.
        
       | raggi wrote:
       | 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.
        
       | hamburglar wrote:
       | 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.
        
       | burnt-resistor wrote:
       | Consumer-grade hardware generally lacks real confidentiality
       | assurance features. Such a software feature implemented in user-
       | space is moot without the ability to control context switching,
       | rendering it mostly security theater. Security critical bits
       | should be done in a dedicated crypto processor that has tamper
       | self-zeroing and self-contained RAM, or at the very least, in the
       | kernel away outside the reach of user-space processes. No matter
       | how much marketing or blog hype is offered, it's lipstick on a
       | pig. They've essentially implemented a soft, insecure HSM.
       | 
       | Big thumbs down from me.
        
       | Someone wrote:
       | FTA: _"Heap allocations made by the function are erased as soon
       | as the garbage collector decides they are no longer reachable"_
       | 
       | I think that means this proposal adds a very specific form of
       | finalisers to go.
       | 
       | How is that implemented efficiently? I can think of doing
       | something akin to NSAutoReleasePool (https://developer.apple.com/
       | documentation/foundation/nsautor...), with all allocations inside
       | a `secret.Do` block going into a separate section of the heap
       | (like a new generation), and, on exit of the block, the runtime
       | doing a GC cycle, collecting and clearing every now inaccessible
       | object in that section of the heap.
       | 
       | It can't do that, though, because the article also says:
       | 
       |  _"Heap allocations are only erased if the program drops all
       | references to them, and then the garbage collector notices that
       | those references are gone. The program controls the first part,
       | but the second part depends on when the runtime decides to act"_
       | 
       | and I think what I am thinking of will guarantee that the garbage
       | collector will eagerly erase any heap allocations that can be
       | freed.
       | 
       | Also, the requirement " the program drops all references to them"
       | means this is not a 100% free lunch. You can't simply wrap your
       | code in a `secret.Do` and expect your code to be free of leaking
       | secrets.
        
       | robmccoll wrote:
       | This is interesting, but how do you bootstrap it? How does this
       | little software enclave get key material in that doesn't transit
       | untrusted memory? From a file? I guess the attacker this is
       | guarding against can read parts of memory remotely but doesn't
       | have RCE. Seems like a better approach would be an explicitly
       | separate allocator and message passing boundaries. Maybe a new
       | way to launch an isolated go routine with limited copying
       | channels.
        
         | cyberax wrote:
         | > How does this little software enclave get key material in
         | that doesn't transit untrusted memory?
         | 
         | Linux has memfd_secret ( https://man7.org/linux/man-
         | pages/man2/memfd_secret.2.html ), that allow you to create a
         | secure memory region that can't be directly mapped into regular
         | RAM.
        
       | _1tan wrote:
       | Seems neat, anything similar in Java?
        
       ___________________________________________________________________
       (page generated 2025-12-13 23:00 UTC)