nats-io/nats-server · error

invalid null character in %s %q

Error message

invalid null character in %s %q

What it means

MQTT forbids the NUL character (U+0000) inside topics ([MQTT-4.7.3-2]); the server rejects any topic containing a 0x00 byte even if the bytes are otherwise valid UTF-8. This prevents ambiguous or injection-prone topic names in the subject tree.

Source

Thrown at server/mqtt.go:4332

	pp.sz = payloadSize
	if pp.sz > 0 {
		start = r.pos
		r.pos += pp.sz
		pp.msg = r.buf[start:r.pos]
	} else if pp.sz == 0 {
		pp.msg = nil
	} else {
		return errMQTTInvalidPublishLength
	}
	return nil
}

func mqttValidateTopic(topic []byte, field string) error {
	if !utf8.Valid(topic) {
		return fmt.Errorf("invalid utf8 for %s %q", field, topic)
	}
	if bytes.IndexByte(topic, 0) >= 0 {
		return fmt.Errorf("invalid null character in %s %q", field, topic)
	}
	return nil
}

func mqttValidateString(value string, field string) error {
	if !utf8.ValidString(value) {
		return fmt.Errorf("invalid utf8 for %s %q", field, value)
	}
	if strings.IndexByte(value, 0) >= 0 {
		return fmt.Errorf("invalid null character in %s %q", field, value)
	}
	return nil
}

func mqttPubTrace(pp *mqttPublish) string {
	dup := pp.flags&mqttPubFlagDup != 0
	qos := mqttGetQoS(pp.flags)
	retain := mqttIsRetained(pp.flags)

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Trim or reject NUL bytes from topic strings in the client before publishing/subscribing
  2. Replace binary data in topics with hex/base64 textual encoding
  3. Verify the buffer-length calculation doesn't include padding zeros beyond the string
  4. Escape/strip control characters from user input used to compose topics

Example fix

// before
topic := fmt.Sprintf("devices/%s/events", rawBuf[:cap])
// after
t := strings.TrimRight(string(rawBuf[:n]), "\x00")
if strings.ContainsRune(t, 0) { return errors.New("topic contains NUL") }
topic := fmt.Sprintf("devices/%s/events", t)
Defensive patterns

Strategy: validation

Validate before calling

func topicSafe(t string) bool { return !strings.ContainsRune(t, 0) && utf8.ValidString(t) && len(t) > 0 }

Type guard

func hasNoNul(b []byte) bool { return bytes.IndexByte(b, 0) < 0 }

Try / catch

if err := subscribe(topic); err != nil && strings.Contains(err.Error(), "null character") { topic = strings.ReplaceAll(topic, "\x00", ""); retry() }

Prevention

When it happens

Trigger: A client sends a SUBSCRIBE/PUBLISH/UNSUBSCRIBE whose topic contains an embedded 0x00 byte, typically from concatenating C strings, zero-padded buffers, or binary data into the topic.

Common situations: Buffer-based topic construction where leftover zero bytes are included in the length, embedding binary keys/hashes in topics, or templated topics joined with NUL separators by mistake.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/c0fd4b79a917e00c. Report an issue: GitHub.