{"record":{"id":"d46cfd3f01c34088","repo":"slackhq/nebula","slug":"no-cipher-state-available-to-encrypt","errorCode":null,"errorMessage":"no cipher state available to encrypt","messagePattern":"no cipher state available to encrypt","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"noiseutil/aesgcm.go","lineNumber":26,"sourceCode":"\t\"github.com/flynn/noise\"\n)\n\n// CipherStateAESGCM is the data-plane wrapper for the AES-GCM AEAD cipher.\n// AES-GCM uses big-endian nonce encoding per the Noise spec.\ntype CipherStateAESGCM struct {\n\tc cipher.AEAD\n}\n\n// NewCipherStateAESGCM extracts the underlying AEAD from the post-handshake noise.CipherState.\n// The caller is responsible for ensuring the noise cipher is actually AES-GCM,\n// otherwise the type assertion still succeeds but the nonce endianness will be wrong on the wire.\nfunc NewCipherStateAESGCM(s *noise.CipherState) *CipherStateAESGCM {\n\treturn &CipherStateAESGCM{c: s.Cipher().(cipher.AEAD)}\n}\n\nfunc (s *CipherStateAESGCM) EncryptDanger(out, ad, plaintext []byte, n uint64, nb []byte) ([]byte, error) {\n\tif s == nil {\n\t\treturn nil, errors.New(\"no cipher state available to encrypt\")\n\t}\n\tif n >= RejectAfterMessages {\n\t\treturn nil, ErrMessageCounterExhausted\n\t}\n\tnb[0] = 0\n\tnb[1] = 0\n\tnb[2] = 0\n\tnb[3] = 0\n\tbinary.BigEndian.PutUint64(nb[4:], n)\n\treturn s.c.Seal(out, nb, plaintext, ad), nil\n}\n\nfunc (s *CipherStateAESGCM) DecryptDanger(out, ad, ciphertext []byte, n uint64, nb []byte) ([]byte, error) {\n\tif s == nil {\n\t\treturn []byte{}, nil\n\t}\n\tnb[0] = 0\n\tnb[1] = 0","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/noiseutil/aesgcm.go#L8-L44","documentation":"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.","triggerScenarios":"Calling NewCipherStateAESGCM(nil) or constructing CipherStateAESGCM without a valid *noise.CipherState, then calling EncryptDanger; the test TestCipherStateNilSafety exercises exactly this.","commonSituations":"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.","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."],"exampleFix":"// before\nout, err := cs.EncryptDanger(out, ad, plaintext, n, nb) // cs may be nil\n// after\nif cs == nil {\n    return errors.New(\"cipher state not initialized; handshake incomplete\")\n}\nout, err := cs.EncryptDanger(out, ad, plaintext, n, nb)","handlingStrategy":"type-guard","validationCode":"if cs == nil {\n    return errors.New(\"cipher state not initialized\")\n}","typeGuard":"func hasCipherState(cs *noiseutil.CipherStateAESGCM) bool { return cs != nil }","tryCatchPattern":"out, err := cs.EncryptDanger(out, ad, plaintext, n, nb)\nif err != nil && err.Error() == \"no cipher state available to encrypt\" {\n    // trigger re-handshake\n}","preventionTips":["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."],"tags":["noise","aesgcm","nil-state","encryption"],"backgroundTag":"uninitialized-cipher-state","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}