From f98634adfbb8e9dec7b4a75a5e47c643f885324d Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Thu, 30 Apr 2026 09:19:40 +0000 Subject: [PATCH 1/6] feat(config,run): generate session ID at startup Add SessionID field to config.AppConfig and generate a UUIDv4 in Run (run_linux.go) at process startup. The session ID is logged once and will be used by future work to group all audit events from a single boundary invocation into a session. No consumers yet; this is the first step of the session correlation RFC. --- config/config.go | 5 +++++ go.mod | 1 + run/run_linux.go | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/config/config.go b/config/config.go index d688834..229cf35 100644 --- a/config/config.go +++ b/config/config.go @@ -85,6 +85,11 @@ type AppConfig struct { UserInfo *UserInfo DisableAuditLogs bool LogProxySocketPath string + + // SessionID is a UUIDv4 generated at process startup. It groups + // all audit events produced by this boundary invocation into a + // single session. Set by Run, not by configuration. + SessionID string } func NewAppConfigFromCliConfig(cfg CliConfig, targetCMD []string) (AppConfig, error) { diff --git a/go.mod b/go.mod index 0188e84..58e7944 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/cenkalti/backoff/v5 v5.0.3 github.com/coder/coder/v2 v2.10.1-0.20260303212958-f758443f44ac github.com/coder/serpent v0.14.0 + github.com/google/uuid v1.6.0 github.com/landlock-lsm/go-landlock v0.0.0-20251103212306-430f8e5cd97c github.com/miekg/dns v1.1.72 github.com/spf13/pflag v1.0.10 diff --git a/run/run_linux.go b/run/run_linux.go index f67d82b..357f5c1 100644 --- a/run/run_linux.go +++ b/run/run_linux.go @@ -10,9 +10,13 @@ import ( "github.com/coder/boundary/config" "github.com/coder/boundary/landjail" "github.com/coder/boundary/nsjail_manager" + "github.com/google/uuid" ) func Run(ctx context.Context, logger *slog.Logger, cfg config.AppConfig) error { + cfg.SessionID = uuid.New().String() + logger.Info("boundary session started", "session_id", cfg.SessionID) + switch cfg.JailType { case config.NSJailType: return nsjail_manager.Run(ctx, logger, cfg) From 2bcedd240df7d909a86977b8f5ed99bf51c936d6 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Thu, 30 Apr 2026 09:21:36 +0000 Subject: [PATCH 2/6] feat(audit): add per-session sequence counter Introduce SequenceCounter, a shared atomic uint64 counter intended to be initialized once per boundary session and shared between the socket auditor and the proxy. The Next() method returns a zero-based, monotonically increasing value so the audit event and any injected header for the same request agree on the sequence number. No wiring yet; this commit only adds the type and its tests. --- audit/sequence.go | 20 ++++++++++ audit/sequence_test.go | 87 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 audit/sequence.go create mode 100644 audit/sequence_test.go diff --git a/audit/sequence.go b/audit/sequence.go new file mode 100644 index 0000000..1735d49 --- /dev/null +++ b/audit/sequence.go @@ -0,0 +1,20 @@ +package audit + +import "sync/atomic" + +// SequenceCounter is a monotonically increasing counter that assigns a +// unique sequence number to every audit event within a single boundary +// session. The counter starts at 0 and is safe for concurrent use by +// both the socket auditor and the proxy. +type SequenceCounter struct { + next atomic.Uint64 +} + +// Next returns the next sequence number. The first call returns 0, +// subsequent calls return 1, 2, 3, etc. It is safe for concurrent +// use. +func (c *SequenceCounter) Next() uint64 { + // Add returns the new value after incrementing, so subtract 1 + // to produce a zero-based sequence. + return c.next.Add(1) - 1 +} diff --git a/audit/sequence_test.go b/audit/sequence_test.go new file mode 100644 index 0000000..e0231e2 --- /dev/null +++ b/audit/sequence_test.go @@ -0,0 +1,87 @@ +package audit + +import ( + "sync" + "testing" +) + +func TestSequenceCounter_StartsAtZero(t *testing.T) { + t.Parallel() + + var c SequenceCounter + if got := c.Next(); got != 0 { + t.Fatalf("first call: got %d, want 0", got) + } +} + +func TestSequenceCounter_Increments(t *testing.T) { + t.Parallel() + + var c SequenceCounter + for i := range uint64(100) { + if got := c.Next(); got != i { + t.Fatalf("call %d: got %d, want %d", i, got, i) + } + } +} + +func TestSequenceCounter_ConcurrentAccess(t *testing.T) { + t.Parallel() + + var c SequenceCounter + + const goroutines = 8 + const callsPerGoroutine = 1000 + const total = goroutines * callsPerGoroutine + + seen := make([]bool, total) + var mu sync.Mutex + + var wg sync.WaitGroup + wg.Add(goroutines) + for range goroutines { + go func() { + defer wg.Done() + for range callsPerGoroutine { + n := c.Next() + mu.Lock() + if n >= total { + mu.Unlock() + t.Errorf("sequence number %d out of range [0, %d)", n, total) + return + } + if seen[n] { + mu.Unlock() + t.Errorf("duplicate sequence number %d", n) + return + } + seen[n] = true + mu.Unlock() + } + }() + } + + wg.Wait() + + for i, ok := range seen { + if !ok { + t.Errorf("sequence number %d was never produced", i) + } + } +} + +func TestSequenceCounter_IndependentInstances(t *testing.T) { + t.Parallel() + + var a, b SequenceCounter + + // Advance a a few times. + for range 5 { + a.Next() + } + + // b should still start at 0. + if got := b.Next(); got != 0 { + t.Fatalf("independent counter: got %d, want 0", got) + } +} From 57c9456efa0962f3de3146dea61635fe448a77c8 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Thu, 30 Apr 2026 09:36:21 +0000 Subject: [PATCH 3/6] feat(audit): include session_id, sequence_number, and time on emitted BoundaryLogs Wire the session ID (from PR 1) and SequenceCounter (from PR 2) into SocketAuditor so that every emitted BoundaryLog carries a sequence number and every flushed ReportBoundaryLogsRequest carries the session ID. Changes: - socket_auditor.go: Add sessionID and seq fields to SocketAuditor. AuditRequest now sets SequenceNumber via seq.Next(). flush() accepts and sets SessionId on ReportBoundaryLogsRequest. - multi_auditor.go: SetupAuditor accepts sessionID, creates a SequenceCounter, and passes both to NewSocketAuditor. - landjail/parent.go, nsjail_manager/parent.go: Pass config.SessionID to SetupAuditor. - go.mod: Bump coder/coder to the commit that adds SequenceNumber and SessionId to the generated proto types. - socket_auditor_test.go: Update test helpers to set the new fields. Add TestSocketAuditor_AuditRequest_SequenceNumberIncrements and TestSocketAuditor_Loop_FlushIncludesSessionID. --- audit/multi_auditor.go | 5 ++- audit/multi_auditor_test.go | 8 ++-- audit/socket_auditor.go | 22 ++++++--- audit/socket_auditor_test.go | 75 ++++++++++++++++++++++++++++++- go.mod | 24 +++++----- go.sum | 86 ++++++++++++++++++------------------ landjail/parent.go | 2 +- nsjail_manager/parent.go | 2 +- 8 files changed, 151 insertions(+), 73 deletions(-) diff --git a/audit/multi_auditor.go b/audit/multi_auditor.go index a9be017..0fff881 100644 --- a/audit/multi_auditor.go +++ b/audit/multi_auditor.go @@ -28,7 +28,7 @@ func (m *MultiAuditor) AuditRequest(req Request) { // provided configuration. It always includes a LogAuditor for stderr logging, // and conditionally adds a SocketAuditor if audit logs are enabled and the // workspace agent's log proxy socket exists. -func SetupAuditor(ctx context.Context, logger *slog.Logger, disableAuditLogs bool, logProxySocketPath string) (Auditor, error) { +func SetupAuditor(ctx context.Context, logger *slog.Logger, disableAuditLogs bool, logProxySocketPath string, sessionID string) (Auditor, error) { stderrAuditor := NewLogAuditor(logger) auditors := []Auditor{stderrAuditor} @@ -48,7 +48,8 @@ func SetupAuditor(ctx context.Context, logger *slog.Logger, disableAuditLogs boo } agentWillProxy := !os.IsNotExist(err) if agentWillProxy { - socketAuditor := NewSocketAuditor(logger, logProxySocketPath) + seq := &SequenceCounter{} + socketAuditor := NewSocketAuditor(logger, logProxySocketPath, sessionID, seq) go socketAuditor.Loop(ctx) auditors = append(auditors, socketAuditor) } else { diff --git a/audit/multi_auditor_test.go b/audit/multi_auditor_test.go index e7f8af9..1ba2a77 100644 --- a/audit/multi_auditor_test.go +++ b/audit/multi_auditor_test.go @@ -25,7 +25,7 @@ func TestSetupAuditor_DisabledAuditLogs(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) ctx := context.Background() - auditor, err := SetupAuditor(ctx, logger, true, "") + auditor, err := SetupAuditor(ctx, logger, true, "", "test-session") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -50,7 +50,7 @@ func TestSetupAuditor_EmptySocketPath(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) ctx := context.Background() - _, err := SetupAuditor(ctx, logger, false, "") + _, err := SetupAuditor(ctx, logger, false, "", "test-session") if err == nil { t.Fatal("expected error for empty socket path, got nil") } @@ -62,7 +62,7 @@ func TestSetupAuditor_SocketDoesNotExist(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) ctx := context.Background() - auditor, err := SetupAuditor(ctx, logger, false, "/nonexistent/socket/path") + auditor, err := SetupAuditor(ctx, logger, false, "/nonexistent/socket/path", "test-session") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -100,7 +100,7 @@ func TestSetupAuditor_SocketExists(t *testing.T) { t.Fatalf("failed to close temp file: %v", err) } - auditor, err := SetupAuditor(ctx, logger, false, socketPath) + auditor, err := SetupAuditor(ctx, logger, false, socketPath, "test-session") if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/audit/socket_auditor.go b/audit/socket_auditor.go index 4c8c5d8..9a613fc 100644 --- a/audit/socket_auditor.go +++ b/audit/socket_auditor.go @@ -34,6 +34,8 @@ type SocketAuditor struct { batchSize int batchTimerDuration time.Duration socketPath string + sessionID string + seq *SequenceCounter droppedChannelFull atomic.Int64 droppedBatchFull atomic.Int64 @@ -45,7 +47,7 @@ type SocketAuditor struct { // NewSocketAuditor creates a new SocketAuditor that sends logs to the agent's // boundary log proxy socket after SocketAuditor.Loop is called. The socket path // is read from EnvAuditSocketPath, falling back to defaultAuditSocketPath. -func NewSocketAuditor(logger *slog.Logger, socketPath string) *SocketAuditor { +func NewSocketAuditor(logger *slog.Logger, socketPath string, sessionID string, seq *SequenceCounter) *SocketAuditor { // This channel buffer size intends to allow enough buffering for bursty // AI agent network requests while a batch is being sent to the workspace // agent. @@ -60,6 +62,8 @@ func NewSocketAuditor(logger *slog.Logger, socketPath string) *SocketAuditor { batchSize: defaultBatchSize, batchTimerDuration: defaultBatchTimerDuration, socketPath: socketPath, + sessionID: sessionID, + seq: seq, } } @@ -77,9 +81,10 @@ func (s *SocketAuditor) AuditRequest(req Request) { } log := &agentproto.BoundaryLog{ - Allowed: req.Allowed, - Time: timestamppb.Now(), - Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: httpReq}, + Allowed: req.Allowed, + Time: timestamppb.Now(), + SequenceNumber: s.seq.Next(), + Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: httpReq}, } select { @@ -100,14 +105,17 @@ type flushErr struct { func (e *flushErr) Error() string { return e.err.Error() } // flush sends the current batch of logs to the given connection. -func flush(conn net.Conn, logs []*agentproto.BoundaryLog) *flushErr { +func flush(conn net.Conn, sessionID string, logs []*agentproto.BoundaryLog) *flushErr { if len(logs) == 0 { return nil } msg := &codec.BoundaryMessage{ Msg: &codec.BoundaryMessage_Logs{ - Logs: &agentproto.ReportBoundaryLogsRequest{Logs: logs}, + Logs: &agentproto.ReportBoundaryLogsRequest{ + Logs: logs, + SessionId: sessionID, + }, }, } if err := codec.WriteMessage(conn, codec.TagV2, msg); err != nil { @@ -188,7 +196,7 @@ func (s *SocketAuditor) Loop(ctx context.Context) { return } - if err := flush(conn, batch); err != nil { + if err := flush(conn, s.sessionID, batch); err != nil { if err.permanent { // Data error: discard batch to avoid infinite retries. s.logger.Warn("dropping batch due to data error on flush attempt", diff --git a/audit/socket_auditor_test.go b/audit/socket_auditor_test.go index 67ebbae..17435c2 100644 --- a/audit/socket_auditor_test.go +++ b/audit/socket_auditor_test.go @@ -33,6 +33,12 @@ func TestSocketAuditor_AuditRequest_QueuesLog(t *testing.T) { if log.Allowed != true { t.Errorf("expected Allowed=true, got %v", log.Allowed) } + if log.Time == nil { + t.Fatal("expected Time to be set") + } + if log.SequenceNumber != 0 { + t.Errorf("expected first SequenceNumber=0, got %d", log.SequenceNumber) + } httpReq := log.GetHttpRequest() if httpReq == nil { t.Fatal("expected HttpRequest, got nil") @@ -230,6 +236,8 @@ func TestSocketAuditor_Loop_RetriesOnConnectionFailure(t *testing.T) { logCh: make(chan *agentproto.BoundaryLog, 2*defaultBatchSize), batchSize: defaultBatchSize, batchTimerDuration: time.Hour, // Ensure timer doesn't interfere with the test + sessionID: "test-session-id", + seq: &SequenceCounter{}, } // Set up hook to detect flush attempts @@ -349,6 +357,8 @@ func TestSocketAuditor_Loop_ReportsBatchFullDrops(t *testing.T) { logCh: make(chan *agentproto.BoundaryLog, 2*defaultBatchSize), batchSize: defaultBatchSize, batchTimerDuration: time.Hour, + sessionID: "test-session-id", + seq: &SequenceCounter{}, } flushed := make(chan struct{}, 4) @@ -451,17 +461,74 @@ func TestSocketAuditor_Loop_ShutdownFlushIncludesDrops(t *testing.T) { func TestFlush_EmptyBatch(t *testing.T) { t.Parallel() - err := flush(nil, nil) + err := flush(nil, "", nil) if err != nil { t.Errorf("expected nil error for empty batch, got %v", err) } - err = flush(nil, []*agentproto.BoundaryLog{}) + err = flush(nil, "", []*agentproto.BoundaryLog{}) if err != nil { t.Errorf("expected nil error for empty slice, got %v", err) } } +func TestSocketAuditor_AuditRequest_SequenceNumberIncrements(t *testing.T) { + t.Parallel() + + auditor := setupSocketAuditor(t) + + for i := range 5 { + auditor.AuditRequest(Request{Method: "GET", URL: "https://example.com", Allowed: true}) + + select { + case log := <-auditor.logCh: + if log.SequenceNumber != uint64(i) { + t.Errorf("request %d: expected SequenceNumber=%d, got %d", i, i, log.SequenceNumber) + } + default: + t.Fatalf("request %d: expected log in channel, got none", i) + } + } +} + +func TestSocketAuditor_Loop_FlushIncludesSessionID(t *testing.T) { + t.Parallel() + + auditor, serverConn := setupTestAuditor(t) + auditor.batchTimerDuration = time.Hour + + cr := newConnReader() + go readFromConn(t, serverConn, cr) + + go auditor.Loop(t.Context()) + + // Fill a batch to trigger a flush. + for i := 0; i < auditor.batchSize; i++ { + auditor.AuditRequest(Request{Method: "GET", URL: "https://example.com", Allowed: true}) + } + + select { + case req := <-cr.logs: + if req.SessionId != "test-session-id" { + t.Errorf("expected SessionId=test-session-id, got %q", req.SessionId) + } + if len(req.Logs) != auditor.batchSize { + t.Errorf("expected %d logs, got %d", auditor.batchSize, len(req.Logs)) + } + // Verify sequence numbers are monotonically increasing. + for i, log := range req.Logs { + if log.SequenceNumber != uint64(i) { + t.Errorf("log %d: expected SequenceNumber=%d, got %d", i, i, log.SequenceNumber) + } + if log.Time == nil { + t.Errorf("log %d: expected Time to be set", i) + } + } + case <-time.After(5 * time.Second): + t.Fatal("timeout waiting for flush") + } +} + // setupSocketAuditor creates a SocketAuditor for tests that only exercise // the queueing behavior (no connection needed). func setupSocketAuditor(t *testing.T) *SocketAuditor { @@ -475,6 +542,8 @@ func setupSocketAuditor(t *testing.T) *SocketAuditor { logCh: make(chan *agentproto.BoundaryLog, 2*defaultBatchSize), batchSize: defaultBatchSize, batchTimerDuration: defaultBatchTimerDuration, + sessionID: "test-session-id", + seq: &SequenceCounter{}, } } @@ -504,6 +573,8 @@ func setupTestAuditor(t *testing.T) (*SocketAuditor, net.Conn) { logCh: make(chan *agentproto.BoundaryLog, 2*defaultBatchSize), batchSize: defaultBatchSize, batchTimerDuration: defaultBatchTimerDuration, + sessionID: "test-session-id", + seq: &SequenceCounter{}, } return auditor, serverConn diff --git a/go.mod b/go.mod index 58e7944..c45cc14 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,22 @@ module github.com/coder/boundary -go 1.25.7 +go 1.25.9 require ( github.com/cenkalti/backoff/v5 v5.0.3 - github.com/coder/coder/v2 v2.10.1-0.20260303212958-f758443f44ac + github.com/coder/coder/v2 v2.33.0-rc.3.0.20260429144328-b1bdb6704fde github.com/coder/serpent v0.14.0 github.com/google/uuid v1.6.0 github.com/landlock-lsm/go-landlock v0.0.0-20251103212306-430f8e5cd97c github.com/miekg/dns v1.1.72 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 - golang.org/x/sys v0.41.0 + golang.org/x/sys v0.43.0 google.golang.org/protobuf v1.36.11 ) require ( - cdr.dev/slog/v3 v3.0.0-rc1 // indirect + cdr.dev/slog/v3 v3.0.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -39,16 +39,16 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect github.com/zeebo/errs v1.4.0 // indirect - go.opentelemetry.io/otel v1.40.0 // indirect - go.opentelemetry.io/otel/trace v1.40.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/crypto v0.48.0 // indirect + golang.org/x/crypto v0.50.0 // indirect golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect - golang.org/x/mod v0.33.0 // indirect - golang.org/x/net v0.50.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/term v0.40.0 // indirect - golang.org/x/tools v0.42.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.53.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/term v0.42.0 // indirect + golang.org/x/tools v0.44.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gopkg.in/yaml.v3 v3.0.1 // indirect kernel.org/pub/linux/libs/security/libcap/psx v1.2.77 // indirect diff --git a/go.sum b/go.sum index d04ae6c..1828f3b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cdr.dev/slog/v3 v3.0.0-rc1 h1:EN7Zim6GvTpAeHQjI0ERDEfqKbTyXRvgH4UhlzLpvWM= -cdr.dev/slog/v3 v3.0.0-rc1/go.mod h1:iO/OALX1VxlI03mkodCGdVP7pXzd2bRMvu3ePvlJ9ak= +cdr.dev/slog/v3 v3.0.0 h1:kXFUqAqK7ogRKcvo4BnduQVp+Jh0uV1AUKf3NW5FU74= +cdr.dev/slog/v3 v3.0.0/go.mod h1:iO/OALX1VxlI03mkodCGdVP7pXzd2bRMvu3ePvlJ9ak= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= @@ -28,14 +28,12 @@ github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMx github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= -github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA= -github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA= -github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= -github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= -github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U= -github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= -github.com/coder/coder/v2 v2.10.1-0.20260303212958-f758443f44ac h1:BmOs70CvLwP7dUt245ffapnSfMME0/tmI2M9nMi6O/w= -github.com/coder/coder/v2 v2.10.1-0.20260303212958-f758443f44ac/go.mod h1:ZyamF6ltx68UIQjbzUchKUTedpUIoyQlwCpOk5c+U4E= +github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g= +github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs= +github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= +github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/coder/coder/v2 v2.33.0-rc.3.0.20260429144328-b1bdb6704fde h1:om3tlQZ//UyyF3hGxCc3/YT5OFaoI8Ixgj5afdTNO/A= +github.com/coder/coder/v2 v2.33.0-rc.3.0.20260429144328-b1bdb6704fde/go.mod h1:w3FcuW2hJS/QnmY7ilsvt0yVpUhlVYdnBozwTkmpkKE= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc= github.com/coder/serpent v0.14.0 h1:g7vt2zBMp3nWyAvyhvQduaI53Ku65U3wITMi01+/8pU= @@ -131,14 +129,14 @@ github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.uber.org/goleak v1.3.1-0.20240429205332-517bace7cc29 h1:w0QrHuh0hhUZ++UTQaBM2DMdrWQghZ/UsUb+Wb1+8YE= go.uber.org/goleak v1.3.1-0.20240429205332-517bace7cc29/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -146,14 +144,14 @@ go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -161,15 +159,15 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= -golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= -golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -181,16 +179,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -198,25 +196,25 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d h1:vsOm753cOAMkt76efriTCDKjpCbK18XGHMJHo0JUKhc= -google.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:0oz9d7g9QLSdv9/lgbIjowW1JoxMbxmBVNe8i6tORJI= -google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d h1:EocjzKLywydp5uZ5tJ79iP6Q0UjDnyiHkGRWxuPBP8s= -google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:48U2I+QQUYhsFrg2SY6r+nJzeOtjey7j//WBESw+qyQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= -google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= +google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI= +google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/landjail/parent.go b/landjail/parent.go index 4cd7796..d399d3d 100644 --- a/landjail/parent.go +++ b/landjail/parent.go @@ -27,7 +27,7 @@ func RunParent(ctx context.Context, logger *slog.Logger, config config.AppConfig ruleEngine := rulesengine.NewRuleEngine(allowRules, logger) // Create auditor - auditor, err := audit.SetupAuditor(ctx, logger, config.DisableAuditLogs, config.LogProxySocketPath) + auditor, err := audit.SetupAuditor(ctx, logger, config.DisableAuditLogs, config.LogProxySocketPath, config.SessionID) if err != nil { return fmt.Errorf("failed to setup auditor: %v", err) } diff --git a/nsjail_manager/parent.go b/nsjail_manager/parent.go index 10554bf..e5d769b 100644 --- a/nsjail_manager/parent.go +++ b/nsjail_manager/parent.go @@ -28,7 +28,7 @@ func RunParent(ctx context.Context, logger *slog.Logger, config config.AppConfig ruleEngine := rulesengine.NewRuleEngine(allowRules, logger) // Create auditor - auditor, err := audit.SetupAuditor(ctx, logger, config.DisableAuditLogs, config.LogProxySocketPath) + auditor, err := audit.SetupAuditor(ctx, logger, config.DisableAuditLogs, config.LogProxySocketPath, config.SessionID) if err != nil { return fmt.Errorf("failed to setup auditor: %v", err) } From cc80579124f9ae5cd5d310862035a9184214251c Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Fri, 1 May 2026 10:07:43 +0000 Subject: [PATCH 4/6] use int32 for sequence numbers --- audit/sequence.go | 4 ++-- audit/sequence_test.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/audit/sequence.go b/audit/sequence.go index 1735d49..7882dd4 100644 --- a/audit/sequence.go +++ b/audit/sequence.go @@ -7,13 +7,13 @@ import "sync/atomic" // session. The counter starts at 0 and is safe for concurrent use by // both the socket auditor and the proxy. type SequenceCounter struct { - next atomic.Uint64 + next atomic.Int32 } // Next returns the next sequence number. The first call returns 0, // subsequent calls return 1, 2, 3, etc. It is safe for concurrent // use. -func (c *SequenceCounter) Next() uint64 { +func (c *SequenceCounter) Next() int32 { // Add returns the new value after incrementing, so subtract 1 // to produce a zero-based sequence. return c.next.Add(1) - 1 diff --git a/audit/sequence_test.go b/audit/sequence_test.go index e0231e2..648aa1a 100644 --- a/audit/sequence_test.go +++ b/audit/sequence_test.go @@ -18,7 +18,7 @@ func TestSequenceCounter_Increments(t *testing.T) { t.Parallel() var c SequenceCounter - for i := range uint64(100) { + for i := range int32(100) { if got := c.Next(); got != i { t.Fatalf("call %d: got %d, want %d", i, got, i) } diff --git a/go.mod b/go.mod index c45cc14..a5e4732 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.9 require ( github.com/cenkalti/backoff/v5 v5.0.3 - github.com/coder/coder/v2 v2.33.0-rc.3.0.20260429144328-b1bdb6704fde + github.com/coder/coder/v2 v2.33.0-rc.3.0.20260501075247-b3e1178358f5 github.com/coder/serpent v0.14.0 github.com/google/uuid v1.6.0 github.com/landlock-lsm/go-landlock v0.0.0-20251103212306-430f8e5cd97c diff --git a/go.sum b/go.sum index 1828f3b..89d4da8 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+Urai github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs= github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= -github.com/coder/coder/v2 v2.33.0-rc.3.0.20260429144328-b1bdb6704fde h1:om3tlQZ//UyyF3hGxCc3/YT5OFaoI8Ixgj5afdTNO/A= -github.com/coder/coder/v2 v2.33.0-rc.3.0.20260429144328-b1bdb6704fde/go.mod h1:w3FcuW2hJS/QnmY7ilsvt0yVpUhlVYdnBozwTkmpkKE= +github.com/coder/coder/v2 v2.33.0-rc.3.0.20260501075247-b3e1178358f5 h1:7V2ZTceP3V8EqYA8kC+3xrRwaZ8SOtgw4b8cElm0rcA= +github.com/coder/coder/v2 v2.33.0-rc.3.0.20260501075247-b3e1178358f5/go.mod h1:w3FcuW2hJS/QnmY7ilsvt0yVpUhlVYdnBozwTkmpkKE= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc= github.com/coder/serpent v0.14.0 h1:g7vt2zBMp3nWyAvyhvQduaI53Ku65U3wITMi01+/8pU= From 852f0e349e63b62d963fd7ed214ceea697ddadc0 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Fri, 1 May 2026 10:36:03 +0000 Subject: [PATCH 5/6] fix unit test type issue --- audit/socket_auditor_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/audit/socket_auditor_test.go b/audit/socket_auditor_test.go index 17435c2..f7295b8 100644 --- a/audit/socket_auditor_test.go +++ b/audit/socket_auditor_test.go @@ -482,7 +482,7 @@ func TestSocketAuditor_AuditRequest_SequenceNumberIncrements(t *testing.T) { select { case log := <-auditor.logCh: - if log.SequenceNumber != uint64(i) { + if log.SequenceNumber != int32(i) { t.Errorf("request %d: expected SequenceNumber=%d, got %d", i, i, log.SequenceNumber) } default: @@ -517,7 +517,7 @@ func TestSocketAuditor_Loop_FlushIncludesSessionID(t *testing.T) { } // Verify sequence numbers are monotonically increasing. for i, log := range req.Logs { - if log.SequenceNumber != uint64(i) { + if log.SequenceNumber != int32(i) { t.Errorf("log %d: expected SequenceNumber=%d, got %d", i, i, log.SequenceNumber) } if log.Time == nil { From 3036c49c187ca761775576c2009cda2a889fed83 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Fri, 1 May 2026 12:27:05 +0000 Subject: [PATCH 6/6] fix(audit): address PR #196 review feedback - Change sessionID type from string to uuid.UUID across SetupAuditor, NewSocketAuditor, SocketAuditor struct, flush(), config.AppConfig, and callers. Call .String() only at the proto serialization boundary in flush(). - Remove TestSequenceCounter_ConcurrentAccess and TestSequenceCounter_IndependentInstances since they only test atomic.Int32 stdlib behavior. - Add zero-time assertion in TestSocketAuditor_AuditRequest_QueuesLog to validate Time is not the zero time after checking for nil. --- audit/multi_auditor.go | 4 ++- audit/multi_auditor_test.go | 10 +++--- audit/sequence_test.go | 62 ------------------------------------ audit/socket_auditor.go | 9 +++--- audit/socket_auditor_test.go | 22 ++++++++----- config/config.go | 3 +- run/run_linux.go | 4 +-- 7 files changed, 32 insertions(+), 82 deletions(-) diff --git a/audit/multi_auditor.go b/audit/multi_auditor.go index 0fff881..91607dc 100644 --- a/audit/multi_auditor.go +++ b/audit/multi_auditor.go @@ -5,6 +5,8 @@ import ( "fmt" "log/slog" "os" + + "github.com/google/uuid" ) // MultiAuditor wraps multiple auditors and sends audit events to all of them. @@ -28,7 +30,7 @@ func (m *MultiAuditor) AuditRequest(req Request) { // provided configuration. It always includes a LogAuditor for stderr logging, // and conditionally adds a SocketAuditor if audit logs are enabled and the // workspace agent's log proxy socket exists. -func SetupAuditor(ctx context.Context, logger *slog.Logger, disableAuditLogs bool, logProxySocketPath string, sessionID string) (Auditor, error) { +func SetupAuditor(ctx context.Context, logger *slog.Logger, disableAuditLogs bool, logProxySocketPath string, sessionID uuid.UUID) (Auditor, error) { stderrAuditor := NewLogAuditor(logger) auditors := []Auditor{stderrAuditor} diff --git a/audit/multi_auditor_test.go b/audit/multi_auditor_test.go index 1ba2a77..2293ff8 100644 --- a/audit/multi_auditor_test.go +++ b/audit/multi_auditor_test.go @@ -7,6 +7,8 @@ import ( "os" "path/filepath" "testing" + + "github.com/google/uuid" ) type mockAuditor struct { @@ -25,7 +27,7 @@ func TestSetupAuditor_DisabledAuditLogs(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) ctx := context.Background() - auditor, err := SetupAuditor(ctx, logger, true, "", "test-session") + auditor, err := SetupAuditor(ctx, logger, true, "", uuid.New()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -50,7 +52,7 @@ func TestSetupAuditor_EmptySocketPath(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) ctx := context.Background() - _, err := SetupAuditor(ctx, logger, false, "", "test-session") + _, err := SetupAuditor(ctx, logger, false, "", uuid.New()) if err == nil { t.Fatal("expected error for empty socket path, got nil") } @@ -62,7 +64,7 @@ func TestSetupAuditor_SocketDoesNotExist(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) ctx := context.Background() - auditor, err := SetupAuditor(ctx, logger, false, "/nonexistent/socket/path", "test-session") + auditor, err := SetupAuditor(ctx, logger, false, "/nonexistent/socket/path", uuid.New()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -100,7 +102,7 @@ func TestSetupAuditor_SocketExists(t *testing.T) { t.Fatalf("failed to close temp file: %v", err) } - auditor, err := SetupAuditor(ctx, logger, false, socketPath, "test-session") + auditor, err := SetupAuditor(ctx, logger, false, socketPath, uuid.New()) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/audit/sequence_test.go b/audit/sequence_test.go index 648aa1a..ffe51d3 100644 --- a/audit/sequence_test.go +++ b/audit/sequence_test.go @@ -1,7 +1,6 @@ package audit import ( - "sync" "testing" ) @@ -24,64 +23,3 @@ func TestSequenceCounter_Increments(t *testing.T) { } } } - -func TestSequenceCounter_ConcurrentAccess(t *testing.T) { - t.Parallel() - - var c SequenceCounter - - const goroutines = 8 - const callsPerGoroutine = 1000 - const total = goroutines * callsPerGoroutine - - seen := make([]bool, total) - var mu sync.Mutex - - var wg sync.WaitGroup - wg.Add(goroutines) - for range goroutines { - go func() { - defer wg.Done() - for range callsPerGoroutine { - n := c.Next() - mu.Lock() - if n >= total { - mu.Unlock() - t.Errorf("sequence number %d out of range [0, %d)", n, total) - return - } - if seen[n] { - mu.Unlock() - t.Errorf("duplicate sequence number %d", n) - return - } - seen[n] = true - mu.Unlock() - } - }() - } - - wg.Wait() - - for i, ok := range seen { - if !ok { - t.Errorf("sequence number %d was never produced", i) - } - } -} - -func TestSequenceCounter_IndependentInstances(t *testing.T) { - t.Parallel() - - var a, b SequenceCounter - - // Advance a a few times. - for range 5 { - a.Next() - } - - // b should still start at 0. - if got := b.Next(); got != 0 { - t.Fatalf("independent counter: got %d, want 0", got) - } -} diff --git a/audit/socket_auditor.go b/audit/socket_auditor.go index 9a613fc..afb50a3 100644 --- a/audit/socket_auditor.go +++ b/audit/socket_auditor.go @@ -8,6 +8,7 @@ import ( "sync/atomic" "time" + "github.com/google/uuid" "google.golang.org/protobuf/types/known/timestamppb" "github.com/coder/coder/v2/agent/boundarylogproxy/codec" @@ -34,7 +35,7 @@ type SocketAuditor struct { batchSize int batchTimerDuration time.Duration socketPath string - sessionID string + sessionID uuid.UUID seq *SequenceCounter droppedChannelFull atomic.Int64 @@ -47,7 +48,7 @@ type SocketAuditor struct { // NewSocketAuditor creates a new SocketAuditor that sends logs to the agent's // boundary log proxy socket after SocketAuditor.Loop is called. The socket path // is read from EnvAuditSocketPath, falling back to defaultAuditSocketPath. -func NewSocketAuditor(logger *slog.Logger, socketPath string, sessionID string, seq *SequenceCounter) *SocketAuditor { +func NewSocketAuditor(logger *slog.Logger, socketPath string, sessionID uuid.UUID, seq *SequenceCounter) *SocketAuditor { // This channel buffer size intends to allow enough buffering for bursty // AI agent network requests while a batch is being sent to the workspace // agent. @@ -105,7 +106,7 @@ type flushErr struct { func (e *flushErr) Error() string { return e.err.Error() } // flush sends the current batch of logs to the given connection. -func flush(conn net.Conn, sessionID string, logs []*agentproto.BoundaryLog) *flushErr { +func flush(conn net.Conn, sessionID uuid.UUID, logs []*agentproto.BoundaryLog) *flushErr { if len(logs) == 0 { return nil } @@ -114,7 +115,7 @@ func flush(conn net.Conn, sessionID string, logs []*agentproto.BoundaryLog) *flu Msg: &codec.BoundaryMessage_Logs{ Logs: &agentproto.ReportBoundaryLogsRequest{ Logs: logs, - SessionId: sessionID, + SessionId: sessionID.String(), }, }, } diff --git a/audit/socket_auditor_test.go b/audit/socket_auditor_test.go index f7295b8..6443781 100644 --- a/audit/socket_auditor_test.go +++ b/audit/socket_auditor_test.go @@ -11,6 +11,8 @@ import ( "testing" "time" + "github.com/google/uuid" + "github.com/coder/coder/v2/agent/boundarylogproxy/codec" agentproto "github.com/coder/coder/v2/agent/proto" ) @@ -36,6 +38,9 @@ func TestSocketAuditor_AuditRequest_QueuesLog(t *testing.T) { if log.Time == nil { t.Fatal("expected Time to be set") } + if log.Time.AsTime().IsZero() { + t.Fatal("expected Time to not be the zero time") + } if log.SequenceNumber != 0 { t.Errorf("expected first SequenceNumber=0, got %d", log.SequenceNumber) } @@ -236,7 +241,7 @@ func TestSocketAuditor_Loop_RetriesOnConnectionFailure(t *testing.T) { logCh: make(chan *agentproto.BoundaryLog, 2*defaultBatchSize), batchSize: defaultBatchSize, batchTimerDuration: time.Hour, // Ensure timer doesn't interfere with the test - sessionID: "test-session-id", + sessionID: uuid.MustParse("00000000-0000-4000-8000-000000000001"), seq: &SequenceCounter{}, } @@ -357,7 +362,7 @@ func TestSocketAuditor_Loop_ReportsBatchFullDrops(t *testing.T) { logCh: make(chan *agentproto.BoundaryLog, 2*defaultBatchSize), batchSize: defaultBatchSize, batchTimerDuration: time.Hour, - sessionID: "test-session-id", + sessionID: uuid.MustParse("00000000-0000-4000-8000-000000000001"), seq: &SequenceCounter{}, } @@ -461,12 +466,12 @@ func TestSocketAuditor_Loop_ShutdownFlushIncludesDrops(t *testing.T) { func TestFlush_EmptyBatch(t *testing.T) { t.Parallel() - err := flush(nil, "", nil) + err := flush(nil, uuid.Nil, nil) if err != nil { t.Errorf("expected nil error for empty batch, got %v", err) } - err = flush(nil, "", []*agentproto.BoundaryLog{}) + err = flush(nil, uuid.Nil, []*agentproto.BoundaryLog{}) if err != nil { t.Errorf("expected nil error for empty slice, got %v", err) } @@ -509,8 +514,9 @@ func TestSocketAuditor_Loop_FlushIncludesSessionID(t *testing.T) { select { case req := <-cr.logs: - if req.SessionId != "test-session-id" { - t.Errorf("expected SessionId=test-session-id, got %q", req.SessionId) + expectedSessionID := uuid.MustParse("00000000-0000-4000-8000-000000000001").String() + if req.SessionId != expectedSessionID { + t.Errorf("expected SessionId=%s, got %q", expectedSessionID, req.SessionId) } if len(req.Logs) != auditor.batchSize { t.Errorf("expected %d logs, got %d", auditor.batchSize, len(req.Logs)) @@ -542,7 +548,7 @@ func setupSocketAuditor(t *testing.T) *SocketAuditor { logCh: make(chan *agentproto.BoundaryLog, 2*defaultBatchSize), batchSize: defaultBatchSize, batchTimerDuration: defaultBatchTimerDuration, - sessionID: "test-session-id", + sessionID: uuid.MustParse("00000000-0000-4000-8000-000000000001"), seq: &SequenceCounter{}, } } @@ -573,7 +579,7 @@ func setupTestAuditor(t *testing.T) (*SocketAuditor, net.Conn) { logCh: make(chan *agentproto.BoundaryLog, 2*defaultBatchSize), batchSize: defaultBatchSize, batchTimerDuration: defaultBatchTimerDuration, - sessionID: "test-session-id", + sessionID: uuid.MustParse("00000000-0000-4000-8000-000000000001"), seq: &SequenceCounter{}, } diff --git a/config/config.go b/config/config.go index 229cf35..73cdb38 100644 --- a/config/config.go +++ b/config/config.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/coder/serpent" + "github.com/google/uuid" "github.com/spf13/pflag" ) @@ -89,7 +90,7 @@ type AppConfig struct { // SessionID is a UUIDv4 generated at process startup. It groups // all audit events produced by this boundary invocation into a // single session. Set by Run, not by configuration. - SessionID string + SessionID uuid.UUID } func NewAppConfigFromCliConfig(cfg CliConfig, targetCMD []string) (AppConfig, error) { diff --git a/run/run_linux.go b/run/run_linux.go index 357f5c1..5344e79 100644 --- a/run/run_linux.go +++ b/run/run_linux.go @@ -14,8 +14,8 @@ import ( ) func Run(ctx context.Context, logger *slog.Logger, cfg config.AppConfig) error { - cfg.SessionID = uuid.New().String() - logger.Info("boundary session started", "session_id", cfg.SessionID) + cfg.SessionID = uuid.New() + logger.Info("boundary session started", "session_id", cfg.SessionID.String()) switch cfg.JailType { case config.NSJailType: