# memguard: a Go software enclave for keeping secrets out of swap and core dumps

> A pure Go package that encrypts sensitive buffers in memory, bypasses the Go runtime for allocation, guards pages with canaries, and asks the kernel to keep the pages resident.

**awnumar/memguard** — Software sandbox for storage of sensitive information in memory.

- Repository: https://github.com/awnumar/memguard
- Stars: 2,763 · Forks: 137
- Language: Go
- License: Apache-2.0
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/awnumar-memguard

## What the package actually protects against

The README frames this as reducing the likelihood of sensitive data being exposed while it is in memory, which is a deliberately narrow claim. It is not a claim that the data cannot be read, and the difference matters for how you evaluate it.

The list of protections is concrete and mostly about the operating system rather than about cryptography. Memory is locked to prevent swapping, so a secret does not end up in a hibernation file or a page file on disk. Core dumps are handled. Kernel-level immutability is implemented, so an attempted write to a protected region produces an access violation rather than a silent modification. Buffers holding plaintext are backed by guard pages and canary values, so an out-of-bounds read or write hits something that faults instead of returning adjacent heap data.

The cryptography is standard and named: XSalsa20Poly1305, from `golang.org/x/crypto`. The README links a scheme write-up that also claims protection against cold-boot attacks, and it credits libsodium as an inspiration for several of these features. Note the constant-time requirement too: copying and comparison of sensitive data is done in constant time, which closes a timing side channel that a naive comparison would open.

## Bypassing the garbage collector for allocation

The most interesting design decision is not the encryption. It is that memguard does not allocate memory through the Go runtime at all. The README says it queries the kernel directly using system calls, through a separate package called `memcall`, so that the garbage collector cannot interfere.

That matters because of how Go manages memory. A normal byte slice lives in a heap the garbage collector owns, and the collector moves nothing, but it does decide when a span is reclaimed and returned to the operating system. Anything the collector reclaims can then be reused by unrelated allocations, which means a buffer holding plaintext can end up backing a string that gets logged. Working below the runtime means the lifetime of the buffer is yours to decide, not the collector's.

The cost is that this is operating-system work, not portable library work. The package depends on `golang.org/x/sys` for the system calls and on `memcall` for the allocation layer, so the protections vary by platform and some do not exist on all of them. The `go.mod` also requires Go 1.25.0, which is a real constraint for anyone on an older toolchain.

## Enclaves, buffers and streams as three levels of abstraction

The repository files make the API surface easy to read. `enclave.go` and `enclave_test.go` hold the container, `buffer.go` and `buffer_test.go` hold the memory regions, `stream.go` and `stream_test.go` hold the streaming helpers, `signals.go` handles signal handling, and `memguard.go` plus `docs.go` hold the package documentation. The `core/` directory is where the platform-specific pieces live.

An enclave owns buffers. A buffer is the encrypted memory region that can be locked, unlocked, wiped and read. A stream lets data move in and out of a buffer without the caller handling plaintext lifetimes. Signals are handled by the package itself so that a terminating process can purge its sessions rather than leaving remnants behind, and the README lists session purging and safe termination as separate endpoints.

The examples directory is worth more attention than most projects give theirs. There are `casting/`, `deadlock/`, `socketkey/`, `stdin/`, `stream/` and `streams/` subdirectories, each with its own README. A subpackage named `deadlock` is a good sign: it means the authors thought about the case where a program blocks while holding an enclave and documented it rather than leaving it as a trap.

## Adding the dependency, and pinning it

Installation is a module fetch:

```bash
go get github.com/awnumar/memguard
```

The dependency set is small and worth reading before you add it. Beyond `golang.org/x/crypto` and `golang.org/x/sys`, there is `lukechampine.com/frand`, a fast random number generator, which is what you would expect from a package whose job involves generating keys and nonces:

```text
+require (
+	github.com/awnumar/memcall v0.5.0
+	golang.org/x/crypto v0.50.0
+	golang.org/x/sys v0.44.0
+	lukechampine.com/frand v1.5.1
+)
+```

The instruction that follows the install command is the one to not skip: the API is experimental and may have unstable changes, so you should pin a version. At version 0.23.0 that is not ceremony. A library whose purpose is defensive must be able to change its behaviour when a weakness is found, and a major-version-zero dependency gives the author room to do that without a deprecation cycle.

## What the release history says about the maintenance reality

The version numbers tell the story. v0.22.5 came in March 2024 and is the most interesting release on the list: it removed a finalizer that was causing a bug, contributed by someone outside the maintainer, and fixed the resulting CI failures on Windows and FreeBSD. A finalizer bug in a memory package is the kind of thing that should worry you in a good way, because it means an outside researcher used the package hard enough to find one.

v0.22.6 and v0.23.0 both landed on 2025-08-27, ninety minutes apart. Together they added GitHub Actions pipelines, addressed concurrency issues with an added test, updated dependencies, and bumped the minor version because `go.mod` had moved to a newer Go version. Two releases in the same afternoon with a minor bump for a toolchain change is a package being tidied rather than developed.

The repository is not archived and the last push was on 2026-05-08, roughly thirteen months after that release pair. That is a slow cadence for a security library, and it is the main practical caveat: the design is strong and the code is careful, but the pace of new platform support will be whatever one maintainer can manage.

## Where memguard is the wrong choice

The clearest case against is a program that has to decrypt into ordinary memory anyway. If your secret is used to derive a key, sign a request, or open a socket, the plaintext exists briefly in a buffer you did not control, and the guard pages protecting the enclave do not extend to it. The `socketkey/` example is instructive precisely because it deals with a case where key material has to exist outside the enclave for a moment.

It is also the wrong tool if your threat model is an attacker with code execution inside your process. A process that can call your functions, read your arguments and dump your memory can ask the enclave to hand over its contents, because the enclave has no idea who is asking. The package is about defence in depth against memory disclosure, core dumps, swap and overflow, not against a compromised process.

And the platform caveat is real. Guard pages, locked memory and kernel-level immutability are operating system features with different names and different semantics on Linux, macOS and Windows. The README says the package aims to support all major operating systems, which is an aim rather than a promise, so before designing around it check what your deployment target actually provides.

## Conclusion

memguard is for Go programs that hold key material, passphrases or tokens for longer than a single function call and want more than a byte slice with a zeroing loop. It is not a replacement for keeping secrets out of the process, and it is not protection against an attacker who already owns your process, because any code that can read your memory can ask the enclave to decrypt. The README states plainly that the API is experimental and may have unstable changes, and it tells you to pin a version, which is the correct way to adopt a package at version 0.23.0. Start with the examples subpackage, since the package documentation is the part the README defers entirely, and check whether your target platform is covered before designing around guard pages and locked memory.

## FAQ

### What is memguard used for in Go?

It holds sensitive values such as keys, passphrases and tokens in encrypted memory regions with guard pages, canary values and paging locked so the plaintext does not reach swap files or core dumps. Values are held inside an enclave, read through buffers, and purged when the process shuts down.

### How does memguard encrypt data in memory?

Sensitive data is encrypted and authenticated with XSalsa20Poly1305, from the golang.org/x/crypto package, which also provides an authenticated cipher covering both confidentiality and integrity. Memory is allocated through system calls rather than the Go runtime so the garbage collector cannot reclaim or reuse a buffer that still holds plaintext.

### Is the memguard Go API stable?

The README states that the API is experimental and may have unstable changes, and instructs you to pin a version. The most recent release is v0.23.0, and the major version is still zero, so the author reserves the right to change the surface without a long deprecation cycle. Pin it and read the change notes before upgrading.

## Sources

- [awnumar/memguard on GitHub](https://github.com/awnumar/memguard)
- [Issues](https://github.com/awnumar/memguard/issues)
- [License: Apache-2.0](https://github.com/awnumar/memguard/blob/master/LICENSE)
- [README](https://github.com/awnumar/memguard/blob/master/README.md)
- [Releases](https://github.com/awnumar/memguard/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/awnumar-memguard
