hush/vendor/google.golang.org/protobuf/internal/protolazy/bufferreader.go
jx12n 4d9a26498e hush: one-time secret links the server cannot read
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.
2026-09-03 00:08:38 -06:00

365 lines
6.2 KiB
Go

// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Helper code for parsing a protocol buffer
package protolazy
import (
"errors"
"fmt"
"io"
"google.golang.org/protobuf/encoding/protowire"
)
// BufferReader is a structure encapsulating a protobuf and a current position
type BufferReader struct {
Buf []byte
Pos int
}
// NewBufferReader creates a new BufferRead from a protobuf
func NewBufferReader(buf []byte) BufferReader {
return BufferReader{Buf: buf, Pos: 0}
}
var errOutOfBounds = errors.New("protobuf decoding: out of bounds")
var errOverflow = errors.New("proto: integer overflow")
func (b *BufferReader) DecodeVarintSlow() (x uint64, err error) {
i := b.Pos
l := len(b.Buf)
for shift := uint(0); shift < 64; shift += 7 {
if i >= l {
err = io.ErrUnexpectedEOF
return
}
v := b.Buf[i]
i++
x |= (uint64(v) & 0x7F) << shift
if v < 0x80 {
b.Pos = i
return
}
}
// The number is too large to represent in a 64-bit value.
err = errOverflow
return
}
// decodeVarint decodes a varint at the current position
func (b *BufferReader) DecodeVarint() (x uint64, err error) {
i := b.Pos
buf := b.Buf
if i >= len(buf) {
return 0, io.ErrUnexpectedEOF
} else if buf[i] < 0x80 {
b.Pos++
return uint64(buf[i]), nil
} else if len(buf)-i < 10 {
return b.DecodeVarintSlow()
}
var v uint64
// we already checked the first byte
x = uint64(buf[i]) & 127
i++
v = uint64(buf[i])
i++
x |= (v & 127) << 7
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 14
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 21
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 28
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 35
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 42
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 49
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 56
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 63
if v < 128 {
goto done
}
return 0, errOverflow
done:
b.Pos = i
return
}
// decodeVarint32 decodes a varint32 at the current position
func (b *BufferReader) DecodeVarint32() (x uint32, err error) {
i := b.Pos
buf := b.Buf
if i >= len(buf) {
return 0, io.ErrUnexpectedEOF
} else if buf[i] < 0x80 {
b.Pos++
return uint32(buf[i]), nil
} else if len(buf)-i < 5 {
v, err := b.DecodeVarintSlow()
return uint32(v), err
}
var v uint32
// we already checked the first byte
x = uint32(buf[i]) & 127
i++
v = uint32(buf[i])
i++
x |= (v & 127) << 7
if v < 128 {
goto done
}
v = uint32(buf[i])
i++
x |= (v & 127) << 14
if v < 128 {
goto done
}
v = uint32(buf[i])
i++
x |= (v & 127) << 21
if v < 128 {
goto done
}
v = uint32(buf[i])
i++
x |= (v & 127) << 28
if v < 128 {
goto done
}
return 0, errOverflow
done:
b.Pos = i
return
}
// skipValue skips a value in the protobuf, based on the specified tag
func (b *BufferReader) SkipValue(tag uint32) (err error) {
wireType := tag & 0x7
switch protowire.Type(wireType) {
case protowire.VarintType:
err = b.SkipVarint()
case protowire.Fixed64Type:
err = b.SkipFixed64()
case protowire.BytesType:
var n uint32
n, err = b.DecodeVarint32()
if err == nil {
err = b.Skip(int(n))
}
case protowire.StartGroupType:
err = b.SkipGroup(tag)
case protowire.Fixed32Type:
err = b.SkipFixed32()
default:
err = fmt.Errorf("Unexpected wire type (%d)", wireType)
}
return
}
// skipGroup skips a group with the specified tag. It executes efficiently using a tag stack
func (b *BufferReader) SkipGroup(tag uint32) (err error) {
tagStack := make([]uint32, 0, 16)
tagStack = append(tagStack, tag)
var n uint32
for len(tagStack) > 0 {
tag, err = b.DecodeVarint32()
if err != nil {
return err
}
switch protowire.Type(tag & 0x7) {
case protowire.VarintType:
err = b.SkipVarint()
case protowire.Fixed64Type:
err = b.Skip(8)
case protowire.BytesType:
n, err = b.DecodeVarint32()
if err == nil {
err = b.Skip(int(n))
}
case protowire.StartGroupType:
tagStack = append(tagStack, tag)
case protowire.Fixed32Type:
err = b.SkipFixed32()
case protowire.EndGroupType:
if protoFieldNumber(tagStack[len(tagStack)-1]) == protoFieldNumber(tag) {
tagStack = tagStack[:len(tagStack)-1]
} else {
err = fmt.Errorf("end group tag %d does not match begin group tag %d at pos %d",
protoFieldNumber(tag), protoFieldNumber(tagStack[len(tagStack)-1]), b.Pos)
}
}
if err != nil {
return err
}
}
return nil
}
// skipVarint effiently skips a varint
func (b *BufferReader) SkipVarint() (err error) {
i := b.Pos
if len(b.Buf)-i < 10 {
// Use DecodeVarintSlow() to check for buffer overflow, but ignore result
if _, err := b.DecodeVarintSlow(); err != nil {
return err
}
return nil
}
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
return errOverflow
out:
b.Pos = i + 1
return nil
}
// skip skips the specified number of bytes
func (b *BufferReader) Skip(n int) (err error) {
if len(b.Buf) < b.Pos+n {
return io.ErrUnexpectedEOF
}
b.Pos += n
return
}
// skipFixed64 skips a fixed64
func (b *BufferReader) SkipFixed64() (err error) {
return b.Skip(8)
}
// skipFixed32 skips a fixed32
func (b *BufferReader) SkipFixed32() (err error) {
return b.Skip(4)
}
// skipBytes skips a set of bytes
func (b *BufferReader) SkipBytes() (err error) {
n, err := b.DecodeVarint32()
if err != nil {
return err
}
return b.Skip(int(n))
}
// Done returns whether we are at the end of the protobuf
func (b *BufferReader) Done() bool {
return b.Pos == len(b.Buf)
}
// Remaining returns how many bytes remain
func (b *BufferReader) Remaining() int {
return len(b.Buf) - b.Pos
}