slackhq/nebula · error
no cipher state available to encrypt
Error message
no cipher state available to encrypt
What it means
The AES-GCM CipherState wrapper in noiseutil encrypts data-plane packets. If the wrapper itself is a nil pointer, there is no underlying cipher.AEAD to seal with, so EncryptDanger fails fast instead of panicking on a nil dereference.
Source
Thrown at noiseutil/aesgcm.go:26
"github.com/flynn/noise"
)
// CipherStateAESGCM is the data-plane wrapper for the AES-GCM AEAD cipher.
// AES-GCM uses big-endian nonce encoding per the Noise spec.
type CipherStateAESGCM struct {
c cipher.AEAD
}
// NewCipherStateAESGCM extracts the underlying AEAD from the post-handshake noise.CipherState.
// The caller is responsible for ensuring the noise cipher is actually AES-GCM,
// otherwise the type assertion still succeeds but the nonce endianness will be wrong on the wire.
func NewCipherStateAESGCM(s *noise.CipherState) *CipherStateAESGCM {
return &CipherStateAESGCM{c: s.Cipher().(cipher.AEAD)}
}
func (s *CipherStateAESGCM) EncryptDanger(out, ad, plaintext []byte, n uint64, nb []byte) ([]byte, error) {
if s == nil {
return nil, errors.New("no cipher state available to encrypt")
}
if n >= RejectAfterMessages {
return nil, ErrMessageCounterExhausted
}
nb[0] = 0
nb[1] = 0
nb[2] = 0
nb[3] = 0
binary.BigEndian.PutUint64(nb[4:], n)
return s.c.Seal(out, nb, plaintext, ad), nil
}
func (s *CipherStateAESGCM) DecryptDanger(out, ad, ciphertext []byte, n uint64, nb []byte) ([]byte, error) {
if s == nil {
return []byte{}, nil
}
nb[0] = 0
nb[1] = 0View on GitHub (pinned to dd8f660c0a)
Solutions
- Check that NewCipherStateAESGCM receives a non-nil *noise.CipherState whose Cipher() yields a cipher.AEAD.
- Ensure the noise handshake completes and a cipher state is set before sending data-plane traffic.
- Add a nil check on the cipher state before calling EncryptDanger in the send path.
Example fix
// before
out, err := cs.EncryptDanger(out, ad, plaintext, n, nb) // cs may be nil
// after
if cs == nil {
return errors.New("cipher state not initialized; handshake incomplete")
}
out, err := cs.EncryptDanger(out, ad, plaintext, n, nb) Defensive patterns
Strategy: type-guard
Validate before calling
if cs == nil {
return errors.New("cipher state not initialized")
} Type guard
func hasCipherState(cs *noiseutil.CipherStateAESGCM) bool { return cs != nil } Try / catch
out, err := cs.EncryptDanger(out, ad, plaintext, n, nb)
if err != nil && err.Error() == "no cipher state available to encrypt" {
// trigger re-handshake
} Prevention
- Never call data-plane encrypt before the handshake completes.
- Centralize cipher-state construction so nil states are not propagated.
- Add unit tests mirroring TestCipherStateNilSafety in custom wrappers.
When it happens
Trigger: Calling NewCipherStateAESGCM(nil) or constructing CipherStateAESGCM without a valid *noise.CipherState, then calling EncryptDanger; the test TestCipherStateNilSafety exercises exactly this.
Common situations: Handshake completed without establishing a cipher key (e.g. skipped or failed handshake), caching a nil cipher state and using it before rekey, or wiring a custom noise CipherState that returns nil.
Related errors
- no cipher state available to encrypt
- no cipher state available to encrypt
- ErrIncompleteHandshake
- ErrMachineFailed
- ErrMissingContent
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/d46cfd3f01c34088.
Report an issue: GitHub.