diff --git a/.go-version b/.go-version
index 013173af5e..a6c2798a48 100644
--- a/.go-version
+++ b/.go-version
@@ -1 +1 @@
-1.22.6
+1.23.0
diff --git a/.golangci.yml b/.golangci.yml
index d8e2b63d8e..9306540caf 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -2,11 +2,11 @@ run:
   # timeout for analysis, e.g. 30s, 5m, default is 1m
   timeout: 12m
 
-  skip-dirs:
+issues:
+  exclude-dirs:
     - testdata$
     - test/mock
-
-  skip-files:
+  exclude-files:
     - ".*\\.pb\\.go"
 
 linters:
@@ -29,3 +29,6 @@ linters-settings:
   revive:
     # minimal confidence for issues, default is 0.8
     confidence: 0.0
+    rules:
+      - name: unused-parameter
+        disabled: true
diff --git a/.spire-tool-versions b/.spire-tool-versions
index d39496afee..97d5b48257 100644
--- a/.spire-tool-versions
+++ b/.spire-tool-versions
@@ -1,3 +1,3 @@
-golangci_lint v1.55.0
+golangci_lint v1.60.1
 markdown_lint v0.37.0
 protoc 24.4
diff --git a/go.mod b/go.mod
index 904811b0f8..4f48cd0a62 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/spiffe/spire
 
-go 1.22.6
+go 1.23.0
 
 require (
 	cloud.google.com/go/iam v1.1.12
diff --git a/pkg/agent/manager/manager_test.go b/pkg/agent/manager/manager_test.go
index ed67df7b5a..6fd93bf266 100644
--- a/pkg/agent/manager/manager_test.go
+++ b/pkg/agent/manager/manager_test.go
@@ -1688,7 +1688,7 @@ func (h *mockAPI) BatchNewX509SVID(_ context.Context, req *svidv1.BatchNewX509SV
 		entry, ok := entries[param.EntryId]
 		if !ok {
 			resp.Results = append(resp.Results, &svidv1.BatchNewX509SVIDResponse_Result{
-				Status: api.CreateStatus(codes.NotFound, "entry %q not found", param.EntryId),
+				Status: api.CreateStatusf(codes.NotFound, "entry %q not found", param.EntryId),
 			})
 			continue
 		}
diff --git a/pkg/common/util/certs_test.go b/pkg/common/util/certs_test.go
index 4c63c43c7f..05bc74e709 100644
--- a/pkg/common/util/certs_test.go
+++ b/pkg/common/util/certs_test.go
@@ -17,5 +17,5 @@ func TestLoadCertPool(t *testing.T) {
 	// be ignored.
 	pool, err := LoadCertPool("testdata/mixed-bundle.pem")
 	require.NoError(err)
-	require.Len(pool.Subjects(), 2) //nolint:staticcheck // these pools are not system pools so the use of Subjects() is ok for now
+	require.Len(pool.Subjects(), 2)
 }
diff --git a/pkg/common/util/task.go b/pkg/common/util/task.go
index d6b5ac8ff4..27584f17f7 100644
--- a/pkg/common/util/task.go
+++ b/pkg/common/util/task.go
@@ -28,7 +28,7 @@ func RunTasks(ctx context.Context, tasks ...func(context.Context) error) error {
 	runTask := func(task func(context.Context) error) (err error) {
 		defer func() {
 			if r := recover(); r != nil {
-				err = fmt.Errorf("panic: %v\n%s\n", r, string(debug.Stack())) //nolint: revive // newlines are intentional
+				err = fmt.Errorf("panic: %v\n%s\n", r, string(debug.Stack()))
 			}
 			wg.Done()
 		}()
@@ -68,7 +68,7 @@ func SerialRun(tasks ...func(context.Context) error) func(ctx context.Context) e
 	return func(ctx context.Context) (err error) {
 		defer func() {
 			if r := recover(); r != nil {
-				err = fmt.Errorf("panic: %v\n%s\n", r, string(debug.Stack())) //nolint: revive // newlines are intentional
+				err = fmt.Errorf("panic: %v\n%s\n", r, string(debug.Stack()))
 			}
 		}()
 
diff --git a/pkg/server/api/bundle/v1/service_test.go b/pkg/server/api/bundle/v1/service_test.go
index cba82c45e6..86ce239281 100644
--- a/pkg/server/api/bundle/v1/service_test.go
+++ b/pkg/server/api/bundle/v1/service_test.go
@@ -2036,7 +2036,7 @@ func TestBatchCreateFederatedBundle(t *testing.T) {
 				},
 			},
 			expectedResults: []*bundlev1.BatchCreateFederatedBundleResponse_Result{
-				{Status: api.CreateStatus(codes.InvalidArgument, `failed to convert bundle: unable to parse X.509 authority: %v`, expectedX509Err)},
+				{Status: api.CreateStatusf(codes.InvalidArgument, `failed to convert bundle: unable to parse X.509 authority: %v`, expectedX509Err)},
 			},
 			expectedLogMsgs: []spiretest.LogEntry{
 				{
@@ -2787,7 +2787,7 @@ func TestBatchSetFederatedBundle(t *testing.T) {
 				},
 			},
 			expectedResults: []*bundlev1.BatchSetFederatedBundleResponse_Result{
-				{Status: api.CreateStatus(codes.InvalidArgument, `failed to convert bundle: unable to parse X.509 authority: %v`, expectedX509Err)},
+				{Status: api.CreateStatusf(codes.InvalidArgument, `failed to convert bundle: unable to parse X.509 authority: %v`, expectedX509Err)},
 			},
 			expectedLogMsgs: []spiretest.LogEntry{
 				{
diff --git a/pkg/server/api/status.go b/pkg/server/api/status.go
index d00e879a5a..6f5ff94ff3 100644
--- a/pkg/server/api/status.go
+++ b/pkg/server/api/status.go
@@ -11,7 +11,15 @@ import (
 )
 
 // CreateStatus creates a proto Status
-func CreateStatus(code codes.Code, format string, a ...any) *types.Status {
+func CreateStatus(code codes.Code, msg string) *types.Status {
+	return &types.Status{
+		Code:    int32(code),
+		Message: msg,
+	}
+}
+
+// CreateStatus creates a proto Status
+func CreateStatusf(code codes.Code, format string, a ...any) *types.Status {
 	return &types.Status{
 		Code:    int32(code),
 		Message: fmt.Sprintf(format, a...),
diff --git a/pkg/server/datastore/sqlstore/sqlstore.go b/pkg/server/datastore/sqlstore/sqlstore.go
index 685707dcfb..010d9dfc9e 100644
--- a/pkg/server/datastore/sqlstore/sqlstore.go
+++ b/pkg/server/datastore/sqlstore/sqlstore.go
@@ -4418,7 +4418,7 @@ func validateRegistrationEntry(entry *common.RegistrationEntry) error {
 		return sqlError.New("invalid request: missing registered entry")
 	}
 
-	if entry.Selectors == nil || len(entry.Selectors) == 0 {
+	if len(entry.Selectors) == 0 {
 		return sqlError.New("invalid registration entry: missing selector list")
 	}
 
@@ -4479,8 +4479,7 @@ func validateRegistrationEntryForUpdate(entry *common.RegistrationEntry, mask *c
 		return sqlError.New("invalid request: missing registered entry")
 	}
 
-	if (mask == nil || mask.Selectors) &&
-		(entry.Selectors == nil || len(entry.Selectors) == 0) {
+	if (mask == nil || mask.Selectors) && len(entry.Selectors) == 0 {
 		return sqlError.New("invalid registration entry: missing selector list")
 	}
 
diff --git a/pkg/server/plugin/keymanager/awskms/awskms.go b/pkg/server/plugin/keymanager/awskms/awskms.go
index 70d9e8b33b..45577a5964 100644
--- a/pkg/server/plugin/keymanager/awskms/awskms.go
+++ b/pkg/server/plugin/keymanager/awskms/awskms.go
@@ -495,7 +495,7 @@ func (p *Plugin) refreshAliases(ctx context.Context) error {
 	}
 
 	if errs != nil {
-		return fmt.Errorf(strings.Join(errs, ": "))
+		return errors.New(strings.Join(errs, ": "))
 	}
 	return nil
 }
@@ -594,7 +594,7 @@ func (p *Plugin) disposeAliases(ctx context.Context) error {
 	}
 
 	if errs != nil {
-		return fmt.Errorf(strings.Join(errs, ": "))
+		return errors.New(strings.Join(errs, ": "))
 	}
 
 	return nil
@@ -700,7 +700,7 @@ func (p *Plugin) disposeKeys(ctx context.Context) error {
 		}
 	}
 	if errs != nil {
-		return fmt.Errorf(strings.Join(errs, ": "))
+		return errors.New(strings.Join(errs, ": "))
 	}
 
 	return nil
diff --git a/pkg/server/plugin/keymanager/azurekeyvault/azure_key_vault.go b/pkg/server/plugin/keymanager/azurekeyvault/azure_key_vault.go
index b9db9cdb4d..e8325fb3fa 100644
--- a/pkg/server/plugin/keymanager/azurekeyvault/azure_key_vault.go
+++ b/pkg/server/plugin/keymanager/azurekeyvault/azure_key_vault.go
@@ -272,7 +272,7 @@ func (p *Plugin) refreshKeys(ctx context.Context) error {
 	}
 
 	if errs != nil {
-		return fmt.Errorf(strings.Join(errs, ": "))
+		return errors.New(strings.Join(errs, ": "))
 	}
 	return nil
 }
diff --git a/pkg/server/plugin/keymanager/gcpkms/gcpkms.go b/pkg/server/plugin/keymanager/gcpkms/gcpkms.go
index a20231acf1..47b696b1ce 100644
--- a/pkg/server/plugin/keymanager/gcpkms/gcpkms.go
+++ b/pkg/server/plugin/keymanager/gcpkms/gcpkms.go
@@ -777,7 +777,7 @@ func (p *Plugin) keepActiveCryptoKeys(ctx context.Context) error {
 	}
 
 	if errs != nil {
-		return fmt.Errorf(strings.Join(errs, "; "))
+		return errors.New(strings.Join(errs, "; "))
 	}
 	return nil
 }
diff --git a/pkg/server/plugin/nodeattestor/awsiid/iid.go b/pkg/server/plugin/nodeattestor/awsiid/iid.go
index f211808afa..3df80a0c27 100644
--- a/pkg/server/plugin/nodeattestor/awsiid/iid.go
+++ b/pkg/server/plugin/nodeattestor/awsiid/iid.go
@@ -436,12 +436,12 @@ func unmarshalAndValidateIdentityDocument(data []byte, getAWSCACertificate func(
 	switch publicKeyType {
 	case RSA1024:
 		if err := verifyRSASignature(caCert.PublicKey.(*rsa.PublicKey), attestationData.Document, signature); err != nil {
-			return imds.InstanceIdentityDocument{}, status.Errorf(codes.InvalidArgument, err.Error())
+			return imds.InstanceIdentityDocument{}, status.Error(codes.InvalidArgument, err.Error())
 		}
 	case RSA2048:
 		pkcs7Sig, err := decodeAndParsePKCS7Signature(signature, caCert)
 		if err != nil {
-			return imds.InstanceIdentityDocument{}, status.Errorf(codes.InvalidArgument, err.Error())
+			return imds.InstanceIdentityDocument{}, status.Error(codes.InvalidArgument, err.Error())
 		}
 
 		if err := pkcs7Sig.Verify(); err != nil {
diff --git a/pkg/server/plugin/nodeattestor/azuremsi/msi.go b/pkg/server/plugin/nodeattestor/azuremsi/msi.go
index 40ede04329..b3b662839f 100644
--- a/pkg/server/plugin/nodeattestor/azuremsi/msi.go
+++ b/pkg/server/plugin/nodeattestor/azuremsi/msi.go
@@ -261,7 +261,7 @@ func (p *MSIAttestorPlugin) Configure(_ context.Context, req *configv1.Configure
 
 	td, err := spiffeid.TrustDomainFromString(req.CoreConfiguration.TrustDomain)
 	if err != nil {
-		return nil, status.Errorf(codes.InvalidArgument, err.Error())
+		return nil, status.Error(codes.InvalidArgument, err.Error())
 	}
 
 	tenants := make(map[string]*tenantConfig)
diff --git a/pkg/server/plugin/upstreamauthority/vault/vault_client_test.go b/pkg/server/plugin/upstreamauthority/vault/vault_client_test.go
index d3f158a04e..941cabca9d 100644
--- a/pkg/server/plugin/upstreamauthority/vault/vault_client_test.go
+++ b/pkg/server/plugin/upstreamauthority/vault/vault_client_test.go
@@ -521,7 +521,7 @@ func TestConfigureTLSWithTokenAuth(t *testing.T) {
 
 	testPool, err := testRootCAs()
 	require.NoError(t, err)
-	require.Equal(t, testPool.Subjects(), tcc.RootCAs.Subjects()) //nolint:staticcheck // these pools are not system pools so the use of Subjects() is ok for now
+	require.Equal(t, testPool.Subjects(), tcc.RootCAs.Subjects())
 }
 
 func TestConfigureTLSWithAppRoleAuth(t *testing.T) {
@@ -543,7 +543,7 @@ func TestConfigureTLSWithAppRoleAuth(t *testing.T) {
 
 	testPool, err := testRootCAs()
 	require.NoError(t, err)
-	require.Equal(t, testPool.Subjects(), tcc.RootCAs.Subjects()) //nolint:staticcheck // these pools are not system pools so the use of Subjects() is ok for now
+	require.Equal(t, testPool.Subjects(), tcc.RootCAs.Subjects())
 }
 
 func TestConfigureTLSInvalidCACert(t *testing.T) {
diff --git a/support/oidc-discovery-provider/main.go b/support/oidc-discovery-provider/main.go
index 2b6dd69e45..960dc50138 100644
--- a/support/oidc-discovery-provider/main.go
+++ b/support/oidc-discovery-provider/main.go
@@ -157,7 +157,7 @@ func newSource(log logrus.FieldLogger, config *Config) (JWKSSource, error) {
 	case config.WorkloadAPI != nil:
 		workloadAPIAddr, err := config.getWorkloadAPIAddr()
 		if err != nil {
-			return nil, errs.New(err.Error())
+			return nil, errs.Wrap(err)
 		}
 		return NewWorkloadAPISource(WorkloadAPISourceConfig{
 			Log:          log,
diff --git a/test/spiretest/socketapi_windows.go b/test/spiretest/socketapi_windows.go
index 30c751aa8f..0a6eaf715f 100644
--- a/test/spiretest/socketapi_windows.go
+++ b/test/spiretest/socketapi_windows.go
@@ -39,7 +39,7 @@ func StartGRPCOnNamedPipeServer(t *testing.T, pipeName string, registerFn func(s
 }
 
 func ServeGRPCServerOnNamedPipe(t *testing.T, server *grpc.Server, pipeName string) net.Addr {
-	listener, err := winio.ListenPipe(fmt.Sprintf(`\\.\`+filepath.Join("pipe", pipeName)), nil)
+	listener, err := winio.ListenPipe(`\\.\`+filepath.Join("pipe", pipeName), nil)
 	require.NoError(t, err)
 	ServeGRPCServerOnListener(t, server, listener)
 	return namedpipe.AddrFromName(namedpipe.GetPipeName(listener.Addr().String()))