Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fbaeddb
Fix race conditions and potential panics in JS API endpoint meta hand…
neilalexander Oct 1, 2025
dd4d534
[FIXED] EraseMsg lost tombstones
MauriceVanVeen Oct 1, 2025
5f5e9d1
[FIXED] Preserve last seq tombstone
MauriceVanVeen Oct 1, 2025
ad77d37
[FIXED] Clean up empty blocks when selecting next first
MauriceVanVeen Oct 1, 2025
5c51e52
Use optional sync when writing TTL and scheduling state in filestore
neilalexander Oct 2, 2025
1978dbd
Always use temporary staging for filestore meta file writes
neilalexander Oct 2, 2025
f14e1ee
[FIXED] Mirror consumer data race
MauriceVanVeen Oct 6, 2025
5596da3
[FIXED] Stream desync after out-of-order SkipMsg
MauriceVanVeen Oct 6, 2025
d2b36b2
[FIXED] Filestore detect delete gap with last SkipMsg
MauriceVanVeen Oct 6, 2025
b1c5612
[FIXED] Filestore don't add deletes for empty block with SkipMsgs
MauriceVanVeen Oct 7, 2025
824f669
[IMPROVED] NRG: Peer activity tracking
MauriceVanVeen Oct 7, 2025
a5a6181
FIPS 140-3 mode support
neilalexander Oct 9, 2025
0507dbb
Update dependencies
neilalexander Oct 13, 2025
5fd8ff1
NRG: Parallel catchups can truncate committed
MauriceVanVeen Oct 13, 2025
eea380a
feat: add riscv64 support
rodneyosodo Oct 9, 2025
c098cac
Update to Go 1.25.3/1.24.9
neilalexander Oct 14, 2025
8de5892
[IMPROVED] JSZ Raft leader stats
MauriceVanVeen Oct 14, 2025
02fc0ce
[FIXED] Filestore unlock when message erase fails
MauriceVanVeen Oct 14, 2025
f5389c7
Tweak cache expiry in `firstMatching` or `firstMatchingMulti`
neilalexander Oct 14, 2025
b0c3be1
Improved warning for Observer mode
roeschter Oct 14, 2025
4659478
[FIXED] Message Tracing: Hop header set properly per gateway
kozlovic Oct 18, 2025
a3d6618
[IMPROVED] Filestore MaxBytes/Msgs update performance
MauriceVanVeen Oct 21, 2025
df98c27
[FIXED] Header search without alloc & bug fixes
MauriceVanVeen Oct 23, 2025
9cdd19f
[FIXED] Consumer send 404 No Messages on EOS
MauriceVanVeen Oct 23, 2025
3970d31
(2.14) [FIXED] Consumer send 404 No Messages on EOS after delivering …
MauriceVanVeen Oct 23, 2025
3e89a09
Update Go dependencies
neilalexander Oct 27, 2025
865a4cf
Update GHA dependencies
neilalexander Oct 27, 2025
e6fe3ce
add expvarz
alexbozhenko Oct 24, 2025
cc123e4
[FIXED] NRG: Always only report leader after noop-entry
MauriceVanVeen Oct 22, 2025
37f1e0e
Add `meta_compact` option to control JetStream meta group compaction/…
neilalexander Oct 28, 2025
36fddba
[FIXED] Default to allowing binary stream snapshots
MauriceVanVeen Oct 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ jobs:

- name: Install cosign
# Use commit hash here to avoid a re-tagging attack, as this is a third-party action
# Commit d7543c93d881b35a8faa02e8e3605f69b7a1ce62 = tag v3.10.0
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62
# Commit faadad0cce49287aee09b3a48701e75088a2c6ad = tag v4.0.0
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad

- name: Install syft
# Use commit hash here to avoid a re-tagging attack, as this is a third-party action
# Commit f8bdd1d8ac5e901a77a92f111440fdb1b593736b = tag v0.20.6
uses: anchore/sbom-action/download-syft@f8bdd1d8ac5e901a77a92f111440fdb1b593736b
# Commit 8e94d75ddd33f69f691467e42275782e4bfefe84 = tag v0.20.9
uses: anchore/sbom-action/download-syft@8e94d75ddd33f69f691467e42275782e4bfefe84
with:
syft-version: "v1.27.1"

Expand Down
10 changes: 9 additions & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ builds:
env:
# This is the toolchain version we use for releases. To override, set the env var, e.g.:
# GORELEASER_TOOLCHAIN="go1.22.8" TARGET='linux_amd64' goreleaser build --snapshot --clean --single-target
- GOTOOLCHAIN={{ envOrDefault "GORELEASER_TOOLCHAIN" "go1.24.7" }}
- GOTOOLCHAIN={{ envOrDefault "GORELEASER_TOOLCHAIN" "go1.25.3" }}
- GO111MODULE=on
- CGO_ENABLED=0
goos:
Expand All @@ -38,6 +38,8 @@ builds:
- mips64le
- s390x
- ppc64le
# RISC-V currently only supported on Linux
- riscv64
goarm:
- 6
- 7
Expand All @@ -52,6 +54,12 @@ builds:
goarch: arm64
- goos: freebsd
goarch: 386
- goos: darwin
goarch: riscv64
- goos: windows
goarch: riscv64
- goos: freebsd
goarch: riscv64
mod_timestamp: "{{ .CommitTimestamp }}"

nfpms:
Expand Down
12 changes: 7 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ module github.com/nats-io/nats-server/v2

go 1.24.0

toolchain go1.24.9

require (
github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op
github.com/google/go-tpm v0.9.6
github.com/klauspost/compress v1.18.0
github.com/klauspost/compress v1.18.1
github.com/minio/highwayhash v1.0.3
github.com/nats-io/jwt/v2 v2.8.0
github.com/nats-io/nats.go v1.46.1
github.com/nats-io/nats.go v1.47.0
github.com/nats-io/nkeys v0.4.11
github.com/nats-io/nuid v1.0.1
go.uber.org/automaxprocs v1.6.0
golang.org/x/crypto v0.42.0
golang.org/x/sys v0.36.0
golang.org/x/time v0.13.0
golang.org/x/crypto v0.43.0
golang.org/x/sys v0.37.0
golang.org/x/time v0.14.0
)
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-tpm v0.9.6 h1:Ku42PT4LmjDu1H5C5ISWLlpI1mj+Zq7sPGKoRw2XROA=
github.com/google/go-tpm v0.9.6/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q=
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g=
github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=
github.com/nats-io/nats.go v1.46.1 h1:bqQ2ZcxVd2lpYI97xYASeRTY3I5boe/IVmuUDPitHfo=
github.com/nats-io/nats.go v1.46.1/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
Expand All @@ -24,12 +24,12 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Binary file added server/.tmp
Binary file not shown.
11 changes: 11 additions & 0 deletions server/ciphersuites.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package server

import (
"crypto/fips140"
"crypto/tls"
)

Expand Down Expand Up @@ -94,6 +95,16 @@ var curvePreferenceMap = map[string]tls.CurveID{
// reorder to default to the highest level of security. See:
// https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
func defaultCurvePreferences() []tls.CurveID {
if fips140.Enabled() {
// X25519 is not FIPS-approved by itself, but it is when
// combined with MLKEM768.
return []tls.CurveID{
tls.X25519MLKEM768, // post-quantum
tls.CurveP256,
tls.CurveP384,
tls.CurveP521,
}
}
return []tls.CurveID{
tls.X25519, // faster than P256, arguably more secure
tls.CurveP256,
Expand Down
79 changes: 65 additions & 14 deletions server/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"net/url"
"regexp"
"runtime"
"slices"
"strconv"
"strings"
"sync"
Expand Down Expand Up @@ -4257,7 +4258,7 @@ func (c *client) setupResponseServiceImport(acc *Account, si *serviceImport, tra

// Will remove a header if present.
func removeHeaderIfPresent(hdr []byte, key string) []byte {
start := bytes.Index(hdr, []byte(key))
start := getHeaderKeyIndex(key, hdr)
// key can't be first and we want to check that it is preceded by a '\n'
if start < 1 || hdr[start-1] != '\n' {
return hdr
Expand Down Expand Up @@ -4375,22 +4376,13 @@ func sliceHeader(key string, hdr []byte) []byte {
if len(hdr) == 0 {
return nil
}
index := bytes.Index(hdr, stringToBytes(key+":"))
hdrLen := len(hdr)
// Check that we have enough characters, this will handle the -1 case of the key not
// being found and will also handle not having enough characters for trailing CRLF.
if index < 2 {
return nil
}
// There should be a terminating CRLF.
if index >= hdrLen-1 || hdr[index-1] != '\n' || hdr[index-2] != '\r' {
index := getHeaderKeyIndex(key, hdr)
if index == -1 {
return nil
}
// The key should be immediately followed by a : separator.
// Skip over the key and the : separator.
index += len(key) + 1
if index >= hdrLen || hdr[index-1] != ':' {
return nil
}
hdrLen := len(hdr)
// Skip over whitespace before the value.
for index < hdrLen && hdr[index] == ' ' {
index++
Expand All @@ -4406,6 +4398,65 @@ func sliceHeader(key string, hdr []byte) []byte {
return hdr[start:index:index]
}

// getHeaderKeyIndex returns an index into the header slice for the given key.
// Returns -1 if not found.
func getHeaderKeyIndex(key string, hdr []byte) int {
if len(hdr) == 0 {
return -1
}
bkey := stringToBytes(key)
keyLen, hdrLen := len(key), len(hdr)
var offset int
for {
index := bytes.Index(hdr[offset:], bkey)
// Check that we have enough characters, this will handle the -1 case of the key not
// being found and will also handle not having enough characters for trailing CRLF.
if index < 2 {
return -1
}
index += offset
// There should be a terminating CRLF.
if index >= hdrLen-1 || hdr[index-1] != '\n' || hdr[index-2] != '\r' {
offset = index + keyLen
continue
}
// The key should be immediately followed by a : separator.
if index+keyLen >= hdrLen {
return -1
}
if hdr[index+keyLen] != ':' {
offset = index + keyLen
continue
}
return index
}
}

func setHeader(key, val string, hdr []byte) []byte {
start := getHeaderKeyIndex(key, hdr)
if start >= 0 {
valStart := start + len(key) + 1
// Preserve single whitespace if used.
hdrLen := len(hdr)
if valStart < hdrLen && hdr[valStart] == ' ' {
valStart++
}
valEnd := bytes.Index(hdr[valStart:], []byte("\r"))
if valEnd < 0 {
return hdr // malformed headers
}
valEnd += valStart
suffix := slices.Clone(hdr[valEnd:])
newHdr := append(hdr[:valStart], val...)
return append(newHdr, suffix...)
}
if len(hdr) > 0 && bytes.HasSuffix(hdr, []byte("\r\n")) {
hdr = hdr[:len(hdr)-2]
val += "\r\n"
}
return fmt.Appendf(hdr, "%s: %s\r\n", key, val)
}

// For bytes.HasPrefix below.
var (
jsRequestNextPreB = []byte(jsRequestNextPre)
Expand Down
101 changes: 100 additions & 1 deletion server/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3040,7 +3040,7 @@ func TestSliceHeader(t *testing.T) {
require_True(t, bytes.Equal(sliced, copied))
}

func TestSliceHeaderOrdering(t *testing.T) {
func TestSliceHeaderOrderingPrefix(t *testing.T) {
hdr := []byte("NATS/1.0\r\n\r\n")

// These headers share the same prefix, the longer subject
Expand All @@ -3060,6 +3060,105 @@ func TestSliceHeaderOrdering(t *testing.T) {
require_True(t, bytes.Equal(sliced, copied))
}

func TestSliceHeaderOrderingSuffix(t *testing.T) {
hdr := []byte("NATS/1.0\r\n\r\n")

// These headers share the same suffix, the longer subject
// must not invalidate the existence of the shorter one.
hdr = genHeader(hdr, "Previous-Nats-Msg-Id", "user")
hdr = genHeader(hdr, "Nats-Msg-Id", "control")

sliced := sliceHeader("Nats-Msg-Id", hdr)
copied := getHeader("Nats-Msg-Id", hdr)

require_NotNil(t, sliced)
require_NotNil(t, copied)
require_True(t, bytes.Equal(sliced, copied))
require_Equal(t, string(copied), "control")
}

func TestRemoveHeaderIfPresentOrderingPrefix(t *testing.T) {
hdr := []byte("NATS/1.0\r\n\r\n")

// These headers share the same prefix, the longer subject
// must not invalidate the existence of the shorter one.
hdr = genHeader(hdr, JSExpectedLastSubjSeqSubj, "foo")
hdr = genHeader(hdr, JSExpectedLastSubjSeq, "24")

hdr = removeHeaderIfPresent(hdr, JSExpectedLastSubjSeq)
ehdr := genHeader(nil, JSExpectedLastSubjSeqSubj, "foo")
require_True(t, bytes.Equal(hdr, ehdr))
}

func TestRemoveHeaderIfPresentOrderingSuffix(t *testing.T) {
hdr := []byte("NATS/1.0\r\n\r\n")

// These headers share the same suffix, the longer subject
// must not invalidate the existence of the shorter one.
hdr = genHeader(hdr, "Previous-Nats-Msg-Id", "user")
hdr = genHeader(hdr, "Nats-Msg-Id", "control")

hdr = removeHeaderIfPresent(hdr, "Nats-Msg-Id")
ehdr := genHeader(nil, "Previous-Nats-Msg-Id", "user")
require_True(t, bytes.Equal(hdr, ehdr))
}

func TestSetHeaderOrderingPrefix(t *testing.T) {
for _, space := range []bool{true, false} {
title := "Normal"
if !space {
title = "Trimmed"
}
t.Run(title, func(t *testing.T) {
hdr := []byte("NATS/1.0\r\n\r\n")

// These headers share the same prefix, the longer subject
// must not invalidate the existence of the shorter one.
hdr = genHeader(hdr, JSExpectedLastSubjSeqSubj, "foo")
hdr = genHeader(hdr, JSExpectedLastSubjSeq, "24")
if !space {
hdr = bytes.ReplaceAll(hdr, []byte(" "), nil)
}

hdr = setHeader(JSExpectedLastSubjSeq, "12", hdr)
ehdr := genHeader(nil, JSExpectedLastSubjSeqSubj, "foo")
ehdr = genHeader(ehdr, JSExpectedLastSubjSeq, "12")
if !space {
ehdr = bytes.ReplaceAll(ehdr, []byte(" "), nil)
}
require_True(t, bytes.Equal(hdr, ehdr))
})
}
}

func TestSetHeaderOrderingSuffix(t *testing.T) {
for _, space := range []bool{true, false} {
title := "Normal"
if !space {
title = "Trimmed"
}
t.Run(title, func(t *testing.T) {
hdr := []byte("NATS/1.0\r\n\r\n")

// These headers share the same suffix, the longer subject
// must not invalidate the existence of the shorter one.
hdr = genHeader(hdr, "Previous-Nats-Msg-Id", "user")
hdr = genHeader(hdr, "Nats-Msg-Id", "control")
if !space {
hdr = bytes.ReplaceAll(hdr, []byte(" "), nil)
}

hdr = setHeader("Nats-Msg-Id", "other", hdr)
ehdr := genHeader(nil, "Previous-Nats-Msg-Id", "user")
ehdr = genHeader(ehdr, "Nats-Msg-Id", "other")
if !space {
ehdr = bytes.ReplaceAll(ehdr, []byte(" "), nil)
}
require_True(t, bytes.Equal(hdr, ehdr))
})
}
}

func TestInProcessAllowedConnectionType(t *testing.T) {
tmpl := `
listen: "127.0.0.1:-1"
Expand Down
Loading
Loading