Paste a secret, get a link, send it. The first person to open it and press
Reveal sees the secret; the link dies at that moment. The recipient needs a
browser and nothing else — no account, no client, no installed tooling.
The server cannot read what it stores. AES-256-GCM happens in the browser and
the key lives in the URL fragment, which browsers never transmit, so hushd
holds ciphertext and no key material. That is a property of where the key sits
rather than a promise about our conduct, which is why there is deliberately no
endpoint accepting a plaintext secret and no server-side-encryption fallback:
two guarantees behind one URL would be worse than one honest guarantee.
Three decisions carry the design:
* GET /s/{id} touches NO storage, not even to check existence. Slack, Teams,
WhatsApp, iMessage and Outlook Safe Links all fetch a URL before a human
sees it, so destroying on GET would destroy most secrets in transit and the
recipient's "already used" would be indistinguishable from interception.
Only POST /reveal consumes. Bot user-agent detection is an arms race;
removing the side effect from GET is not. Pinned by
TestGettingTheRevealPageNeverConsumesTheSecret.
* Destruction is one Redis GETDEL, which is atomic. GET-then-DEL has a window
where two simultaneous readers both win, and for a one-time secret that
window is the product. The store contract demands atomicity and the same
concurrency test runs against both implementations.
* Missing, already-revealed, expired and evicted are ONE indistinguishable
410. Separating them would confirm to a prober that a given link was real.
The secret id IS the capability, so secret.ID is a struct whose every
accidental path — %v, %s, String(), slog, json.Marshal — emits a redacted
handle or refuses, and the raw value needs an explicit Value(). The first
version tried to prevent leaks by implementing no String() at all; its own test
caught that Go's fmt prints unexported fields anyway, so forbidding the method
had removed the control rather than the leak.
Operationally: structured JSON on stdout in the fleet's wire format, which
Vector already collects with no annotation; six hush_* metrics on the chassis
registry with no id, IP or path in any label; five alert rules wired into
vmalert. The public Ingress enumerates /, /s/ and /api/ so /metrics, /healthz
and /readyz share the port but are unreachable from the internet — no
basic-auth middleware to maintain and get wrong.
Dependencies are vendored because go-chassis is private: the Woodpecker test
step and the in-cluster Kaniko build both run -mod=vendor with GOPROXY=off and
hold no git credential.
cmd/hush-mcp is a stdio MCP server doing the same client-side crypto locally,
so using hush from an agent preserves the same guarantee as using it from a
browser.
64 lines
2.0 KiB
Markdown
64 lines
2.0 KiB
Markdown
# atomic [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![Go Report Card][reportcard-img]][reportcard]
|
|
|
|
Simple wrappers for primitive types to enforce atomic access.
|
|
|
|
## Installation
|
|
|
|
```shell
|
|
$ go get -u go.uber.org/atomic@v1
|
|
```
|
|
|
|
### Legacy Import Path
|
|
|
|
As of v1.5.0, the import path `go.uber.org/atomic` is the only supported way
|
|
of using this package. If you are using Go modules, this package will fail to
|
|
compile with the legacy import path path `github.com/uber-go/atomic`.
|
|
|
|
We recommend migrating your code to the new import path but if you're unable
|
|
to do so, or if your dependencies are still using the old import path, you
|
|
will have to add a `replace` directive to your `go.mod` file downgrading the
|
|
legacy import path to an older version.
|
|
|
|
```
|
|
replace github.com/uber-go/atomic => github.com/uber-go/atomic v1.4.0
|
|
```
|
|
|
|
You can do so automatically by running the following command.
|
|
|
|
```shell
|
|
$ go mod edit -replace github.com/uber-go/atomic=github.com/uber-go/atomic@v1.4.0
|
|
```
|
|
|
|
## Usage
|
|
|
|
The standard library's `sync/atomic` is powerful, but it's easy to forget which
|
|
variables must be accessed atomically. `go.uber.org/atomic` preserves all the
|
|
functionality of the standard library, but wraps the primitive types to
|
|
provide a safer, more convenient API.
|
|
|
|
```go
|
|
var atom atomic.Uint32
|
|
atom.Store(42)
|
|
atom.Sub(2)
|
|
atom.CAS(40, 11)
|
|
```
|
|
|
|
See the [documentation][doc] for a complete API specification.
|
|
|
|
## Development Status
|
|
|
|
Stable.
|
|
|
|
---
|
|
|
|
Released under the [MIT License](LICENSE.txt).
|
|
|
|
[doc-img]: https://godoc.org/github.com/uber-go/atomic?status.svg
|
|
[doc]: https://godoc.org/go.uber.org/atomic
|
|
[ci-img]: https://github.com/uber-go/atomic/actions/workflows/go.yml/badge.svg
|
|
[ci]: https://github.com/uber-go/atomic/actions/workflows/go.yml
|
|
[cov-img]: https://codecov.io/gh/uber-go/atomic/branch/master/graph/badge.svg
|
|
[cov]: https://codecov.io/gh/uber-go/atomic
|
|
[reportcard-img]: https://goreportcard.com/badge/go.uber.org/atomic
|
|
[reportcard]: https://goreportcard.com/report/go.uber.org/atomic
|