diff --git a/api/plugin_helpers.go b/api/plugin_helpers.go
index 5bb566300536..b96cb9becf78 100644
--- a/api/plugin_helpers.go
+++ b/api/plugin_helpers.go
@@ -40,7 +40,8 @@ const (
 // path matches that path or not (useful specifically for the paths that
 // contain templated fields.)
 var sudoPaths = map[string]*regexp.Regexp{
-	"/auth/token/accessors":                         regexp.MustCompile(`^/auth/token/accessors/?$`),
+	"/auth/token/accessors": regexp.MustCompile(`^/auth/token/accessors/?$`),
+	// TODO /auth/token/revoke-orphan requires sudo but isn't represented as such in the OpenAPI spec
 	"/pki/root":                                     regexp.MustCompile(`^/pki/root$`),
 	"/pki/root/sign-self-issued":                    regexp.MustCompile(`^/pki/root/sign-self-issued$`),
 	"/sys/audit":                                    regexp.MustCompile(`^/sys/audit$`),
@@ -52,28 +53,33 @@ var sudoPaths = map[string]*regexp.Regexp{
 	"/sys/config/cors":                              regexp.MustCompile(`^/sys/config/cors$`),
 	"/sys/config/ui/headers":                        regexp.MustCompile(`^/sys/config/ui/headers/?$`),
 	"/sys/config/ui/headers/{header}":               regexp.MustCompile(`^/sys/config/ui/headers/.+$`),
-	"/sys/leases":                                   regexp.MustCompile(`^/sys/leases$`),
-	"/sys/leases/lookup/":                           regexp.MustCompile(`^/sys/leases/lookup/?$`),
-	"/sys/leases/lookup/{prefix}":                   regexp.MustCompile(`^/sys/leases/lookup/.+$`),
-	"/sys/leases/revoke-force/{prefix}":             regexp.MustCompile(`^/sys/leases/revoke-force/.+$`),
-	"/sys/leases/revoke-prefix/{prefix}":            regexp.MustCompile(`^/sys/leases/revoke-prefix/.+$`),
-	"/sys/plugins/catalog/{name}":                   regexp.MustCompile(`^/sys/plugins/catalog/[^/]+$`),
-	"/sys/plugins/catalog/{type}":                   regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+$`),
-	"/sys/plugins/catalog/{type}/{name}":            regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+/[^/]+$`),
-	"/sys/raw":                                      regexp.MustCompile(`^/sys/raw$`),
-	"/sys/raw/{path}":                               regexp.MustCompile(`^/sys/raw/.+$`),
-	"/sys/remount":                                  regexp.MustCompile(`^/sys/remount$`),
-	"/sys/revoke-force/{prefix}":                    regexp.MustCompile(`^/sys/revoke-force/.+$`),
-	"/sys/revoke-prefix/{prefix}":                   regexp.MustCompile(`^/sys/revoke-prefix/.+$`),
-	"/sys/rotate":                                   regexp.MustCompile(`^/sys/rotate$`),
 	"/sys/internal/inspect/router/{tag}":            regexp.MustCompile(`^/sys/internal/inspect/router/.+$`),
+	"/sys/leases":                                   regexp.MustCompile(`^/sys/leases$`),
+	// This entry is a bit wrong... sys/leases/lookup does NOT require sudo. But sys/leases/lookup/ with a trailing
+	// slash DOES require sudo. But the part of the Vault CLI that uses this logic doesn't pass operation-appropriate
+	// trailing slashes, it always strips them off, so we end up giving the wrong answer for one of these.
+	"/sys/leases/lookup":                 regexp.MustCompile(`^/sys/leases/lookup/?$`),
+	"/sys/leases/lookup/{prefix}":        regexp.MustCompile(`^/sys/leases/lookup/.+$`),
+	"/sys/leases/revoke-force/{prefix}":  regexp.MustCompile(`^/sys/leases/revoke-force/.+$`),
+	"/sys/leases/revoke-prefix/{prefix}": regexp.MustCompile(`^/sys/leases/revoke-prefix/.+$`),
+	"/sys/plugins/catalog/{name}":        regexp.MustCompile(`^/sys/plugins/catalog/[^/]+$`),
+	"/sys/plugins/catalog/{type}":        regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+$`),
+	"/sys/plugins/catalog/{type}/{name}": regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+/[^/]+$`),
+	"/sys/raw":                           regexp.MustCompile(`^/sys/raw$`),
+	"/sys/raw/{path}":                    regexp.MustCompile(`^/sys/raw/.+$`),
+	"/sys/remount":                       regexp.MustCompile(`^/sys/remount$`),
+	"/sys/revoke-force/{prefix}":         regexp.MustCompile(`^/sys/revoke-force/.+$`),
+	"/sys/revoke-prefix/{prefix}":        regexp.MustCompile(`^/sys/revoke-prefix/.+$`),
+	"/sys/rotate":                        regexp.MustCompile(`^/sys/rotate$`),
+	// TODO /sys/seal requires sudo but isn't represented as such in the OpenAPI spec
+	// TODO /sys/step-down requires sudo but isn't represented as such in the OpenAPI spec
 
 	// enterprise-only paths
 	"/sys/replication/dr/primary/secondary-token":          regexp.MustCompile(`^/sys/replication/dr/primary/secondary-token$`),
 	"/sys/replication/performance/primary/secondary-token": regexp.MustCompile(`^/sys/replication/performance/primary/secondary-token$`),
 	"/sys/replication/primary/secondary-token":             regexp.MustCompile(`^/sys/replication/primary/secondary-token$`),
 	"/sys/replication/reindex":                             regexp.MustCompile(`^/sys/replication/reindex$`),
-	"/sys/storage/raft/snapshot-auto/config/":              regexp.MustCompile(`^/sys/storage/raft/snapshot-auto/config/?$`),
+	"/sys/storage/raft/snapshot-auto/config":               regexp.MustCompile(`^/sys/storage/raft/snapshot-auto/config/?$`),
 	"/sys/storage/raft/snapshot-auto/config/{name}":        regexp.MustCompile(`^/sys/storage/raft/snapshot-auto/config/[^/]+$`),
 }
 
@@ -252,6 +258,8 @@ func SudoPaths() map[string]*regexp.Regexp {
 // Determine whether the given path requires the sudo capability.
 // Note that this uses hardcoded static path information, so will return incorrect results for paths in namespaces,
 // or for secret engines mounted at non-default paths.
+// Expects to receive a path with an initial slash, but no trailing slashes, as the Vault CLI (the only known and
+// expected user of this function) sanitizes its paths that way.
 func IsSudoPath(path string) bool {
 	// Return early if the path is any of the non-templated sudo paths.
 	if _, ok := sudoPaths[path]; ok {
diff --git a/builtin/credential/github/backend.go b/builtin/credential/github/backend.go
index f8bbcc403c61..f3669bbfd4db 100644
--- a/builtin/credential/github/backend.go
+++ b/builtin/credential/github/backend.go
@@ -8,7 +8,7 @@ import (
 	"net/url"
 
 	"github.com/google/go-github/github"
-	cleanhttp "github.com/hashicorp/go-cleanhttp"
+	"github.com/hashicorp/go-cleanhttp"
 	"github.com/hashicorp/vault/sdk/framework"
 	"github.com/hashicorp/vault/sdk/logical"
 	"golang.org/x/oauth2"
@@ -43,6 +43,21 @@ func Backend() *backend {
 		OperationPrefix: operationPrefixGithub,
 		OperationSuffix: "team-mapping",
 	}
+	teamMapPaths[0].Operations = map[logical.Operation]framework.OperationHandler{
+		logical.ListOperation: &framework.PathOperation{
+			Callback: teamMapPaths[0].Callbacks[logical.ListOperation],
+			Summary:  teamMapPaths[0].HelpSynopsis,
+		},
+		logical.ReadOperation: &framework.PathOperation{
+			Callback: teamMapPaths[0].Callbacks[logical.ReadOperation],
+			Summary:  teamMapPaths[0].HelpSynopsis,
+			DisplayAttrs: &framework.DisplayAttributes{
+				OperationVerb:   "list",
+				OperationSuffix: "teams2", // The ReadOperation is redundant with the ListOperation
+			},
+		},
+	}
+	teamMapPaths[0].Callbacks = nil
 
 	b.UserMap = &framework.PolicyMap{
 		PathMap: framework.PathMap{
@@ -61,6 +76,21 @@ func Backend() *backend {
 		OperationPrefix: operationPrefixGithub,
 		OperationSuffix: "user-mapping",
 	}
+	userMapPaths[0].Operations = map[logical.Operation]framework.OperationHandler{
+		logical.ListOperation: &framework.PathOperation{
+			Callback: userMapPaths[0].Callbacks[logical.ListOperation],
+			Summary:  userMapPaths[0].HelpSynopsis,
+		},
+		logical.ReadOperation: &framework.PathOperation{
+			Callback: userMapPaths[0].Callbacks[logical.ReadOperation],
+			Summary:  userMapPaths[0].HelpSynopsis,
+			DisplayAttrs: &framework.DisplayAttributes{
+				OperationVerb:   "list",
+				OperationSuffix: "users2", // The ReadOperation is redundant with the ListOperation
+			},
+		},
+	}
+	userMapPaths[0].Callbacks = nil
 
 	allPaths := append(teamMapPaths, userMapPaths...)
 	b.Backend = &framework.Backend{
diff --git a/builtin/logical/pki/backend_test.go b/builtin/logical/pki/backend_test.go
index 308d661d6698..4a5132b553c6 100644
--- a/builtin/logical/pki/backend_test.go
+++ b/builtin/logical/pki/backend_test.go
@@ -6767,8 +6767,8 @@ func TestProperAuthing(t *testing.T) {
 		"cert/unified-delta-crl":                 shouldBeUnauthedReadList,
 		"cert/unified-delta-crl/raw":             shouldBeUnauthedReadList,
 		"cert/unified-delta-crl/raw/pem":         shouldBeUnauthedReadList,
-		"certs":                                  shouldBeAuthed,
-		"certs/revoked":                          shouldBeAuthed,
+		"certs/":                                 shouldBeAuthed,
+		"certs/revoked/":                         shouldBeAuthed,
 		"certs/revocation-queue":                 shouldBeAuthed,
 		"certs/unified-revoked":                  shouldBeAuthed,
 		"config/acme":                            shouldBeAuthed,
@@ -6817,7 +6817,7 @@ func TestProperAuthing(t *testing.T) {
 		"issuer/default/sign-verbatim":           shouldBeAuthed,
 		"issuer/default/sign-verbatim/test":      shouldBeAuthed,
 		"issuer/default/sign/test":               shouldBeAuthed,
-		"issuers":                                shouldBeUnauthedReadList,
+		"issuers/":                               shouldBeUnauthedReadList,
 		"issuers/generate/intermediate/exported": shouldBeAuthed,
 		"issuers/generate/intermediate/internal": shouldBeAuthed,
 		"issuers/generate/intermediate/existing": shouldBeAuthed,
@@ -6829,7 +6829,7 @@ func TestProperAuthing(t *testing.T) {
 		"issuers/import/cert":                    shouldBeAuthed,
 		"issuers/import/bundle":                  shouldBeAuthed,
 		"key/default":                            shouldBeAuthed,
-		"keys":                                   shouldBeAuthed,
+		"keys/":                                  shouldBeAuthed,
 		"keys/generate/internal":                 shouldBeAuthed,
 		"keys/generate/exported":                 shouldBeAuthed,
 		"keys/generate/kms":                      shouldBeAuthed,
@@ -6839,7 +6839,7 @@ func TestProperAuthing(t *testing.T) {
 		"revoke":                                 shouldBeAuthed,
 		"revoke-with-key":                        shouldBeAuthed,
 		"roles/test":                             shouldBeAuthed,
-		"roles":                                  shouldBeAuthed,
+		"roles/":                                 shouldBeAuthed,
 		"root":                                   shouldBeAuthed,
 		"root/generate/exported":                 shouldBeAuthed,
 		"root/generate/internal":                 shouldBeAuthed,
@@ -6864,7 +6864,7 @@ func TestProperAuthing(t *testing.T) {
 		"unified-crl/delta/pem":                  shouldBeUnauthedReadList,
 		"unified-ocsp":                           shouldBeUnauthedWriteOnly,
 		"unified-ocsp/dGVzdAo=":                  shouldBeUnauthedReadList,
-		"eab":                                    shouldBeAuthed,
+		"eab/":                                   shouldBeAuthed,
 		"eab/" + eabKid:                          shouldBeAuthed,
 	}
 
@@ -6953,7 +6953,8 @@ func TestProperAuthing(t *testing.T) {
 
 		handler, present := paths[raw_path]
 		if !present {
-			t.Fatalf("OpenAPI reports PKI mount contains %v->%v but was not tested to be authed or authed.", openapi_path, raw_path)
+			t.Fatalf("OpenAPI reports PKI mount contains %v -> %v but was not tested to be authed or not authed.",
+				openapi_path, raw_path)
 		}
 
 		openapi_data := raw_data.(map[string]interface{})
diff --git a/builtin/logical/ssh/backend_test.go b/builtin/logical/ssh/backend_test.go
index 13f9f73624af..a33c6224585c 100644
--- a/builtin/logical/ssh/backend_test.go
+++ b/builtin/logical/ssh/backend_test.go
@@ -2765,7 +2765,7 @@ func TestProperAuthing(t *testing.T) {
 		"public_key":         shouldBeUnauthedReadList,
 		"roles/test-ca":      shouldBeAuthed,
 		"roles/test-otp":     shouldBeAuthed,
-		"roles":              shouldBeAuthed,
+		"roles/":             shouldBeAuthed,
 		"sign/test-ca":       shouldBeAuthed,
 		"tidy/dynamic-keys":  shouldBeAuthed,
 		"verify":             shouldBeUnauthedWriteOnly,
@@ -2809,7 +2809,8 @@ func TestProperAuthing(t *testing.T) {
 
 		handler, present := paths[raw_path]
 		if !present {
-			t.Fatalf("OpenAPI reports SSH mount contains %v->%v  but was not tested to be authed or authed.", openapi_path, raw_path)
+			t.Fatalf("OpenAPI reports SSH mount contains %v -> %v but was not tested to be authed or not authed.",
+				openapi_path, raw_path)
 		}
 
 		openapi_data := raw_data.(map[string]interface{})
diff --git a/changelog/21723.txt b/changelog/21723.txt
new file mode 100644
index 000000000000..cefe5e1c5ad3
--- /dev/null
+++ b/changelog/21723.txt
@@ -0,0 +1,3 @@
+```release-note:improvement
+openapi: List operations are now given first-class representation in the OpenAPI document, rather than sometimes being overlaid with a read operation at the same path
+```
diff --git a/sdk/framework/openapi.go b/sdk/framework/openapi.go
index 6ef8a8414df6..d92b14c68ea9 100644
--- a/sdk/framework/openapi.go
+++ b/sdk/framework/openapi.go
@@ -107,13 +107,12 @@ type OASLicense struct {
 }
 
 type OASPathItem struct {
-	Description       string             `json:"description,omitempty"`
-	Parameters        []OASParameter     `json:"parameters,omitempty"`
-	Sudo              bool               `json:"x-vault-sudo,omitempty" mapstructure:"x-vault-sudo"`
-	Unauthenticated   bool               `json:"x-vault-unauthenticated,omitempty" mapstructure:"x-vault-unauthenticated"`
-	CreateSupported   bool               `json:"x-vault-createSupported,omitempty" mapstructure:"x-vault-createSupported"`
-	DisplayNavigation bool               `json:"x-vault-displayNavigation,omitempty" mapstructure:"x-vault-displayNavigation"`
-	DisplayAttrs      *DisplayAttributes `json:"x-vault-displayAttrs,omitempty" mapstructure:"x-vault-displayAttrs"`
+	Description     string             `json:"description,omitempty"`
+	Parameters      []OASParameter     `json:"parameters,omitempty"`
+	Sudo            bool               `json:"x-vault-sudo,omitempty" mapstructure:"x-vault-sudo"`
+	Unauthenticated bool               `json:"x-vault-unauthenticated,omitempty" mapstructure:"x-vault-unauthenticated"`
+	CreateSupported bool               `json:"x-vault-createSupported,omitempty" mapstructure:"x-vault-createSupported"`
+	DisplayAttrs    *DisplayAttributes `json:"x-vault-displayAttrs,omitempty" mapstructure:"x-vault-displayAttrs"`
 
 	Get    *OASOperation `json:"get,omitempty"`
 	Post   *OASOperation `json:"post,omitempty"`
@@ -309,6 +308,7 @@ func documentPath(p *Path, backend *Backend, requestResponsePrefix string, doc *
 
 		// Process each supported operation by building up an Operation object
 		// with descriptions, properties and examples from the framework.Path data.
+		var listOperation *OASOperation
 		for opType, opHandler := range operations {
 			props := opHandler.Properties()
 			if props.Unpublished || forceUnpublished {
@@ -324,11 +324,6 @@ func documentPath(p *Path, backend *Backend, requestResponsePrefix string, doc *
 				}
 			}
 
-			// If both List and Read are defined, only process Read.
-			if opType == logical.ListOperation && operations[logical.ReadOperation] != nil {
-				continue
-			}
-
 			op := NewOASOperation()
 
 			operationID := constructOperationID(
@@ -408,9 +403,10 @@ func documentPath(p *Path, backend *Backend, requestResponsePrefix string, doc *
 				}
 			}
 
-			// LIST is represented as GET with a `list` query parameter.
+			// LIST is represented as GET with a `list` query parameter. Code later on in this function will assign
+			// list operations to a path with an extra trailing slash, ensuring they do not collide with read
+			// operations.
 			if opType == logical.ListOperation {
-				// Only accepts List (due to the above skipping of ListOperations that also have ReadOperations)
 				op.Parameters = append(op.Parameters, OASParameter{
 					Name:        "list",
 					Description: "Must be set to `true`",
@@ -418,14 +414,6 @@ func documentPath(p *Path, backend *Backend, requestResponsePrefix string, doc *
 					In:          "query",
 					Schema:      &OASSchema{Type: "string", Enum: []interface{}{"true"}},
 				})
-			} else if opType == logical.ReadOperation && operations[logical.ListOperation] != nil {
-				// Accepts both Read and List
-				op.Parameters = append(op.Parameters, OASParameter{
-					Name:        "list",
-					Description: "Return a list if `true`",
-					In:          "query",
-					Schema:      &OASSchema{Type: "string"},
-				})
 			}
 
 			// Add tags based on backend type
@@ -521,18 +509,79 @@ func documentPath(p *Path, backend *Backend, requestResponsePrefix string, doc *
 			switch opType {
 			case logical.CreateOperation, logical.UpdateOperation:
 				pi.Post = op
-			case logical.ReadOperation, logical.ListOperation:
+			case logical.ReadOperation:
 				pi.Get = op
 			case logical.DeleteOperation:
 				pi.Delete = op
+			case logical.ListOperation:
+				listOperation = op
 			}
 		}
 
-		openAPIPath := "/" + path
-		if doc.Paths[openAPIPath] != nil {
-			backend.Logger().Warn("OpenAPI spec generation: multiple framework.Path instances generated the same path; last processed wins", "path", openAPIPath)
+		// The conventions enforced by the Vault HTTP routing code make it impossible to match a path with a trailing
+		// slash to anything other than a ListOperation. Catch mistakes in path definition, to enforce that if both of
+		// the two following blocks of code (non-list, and list) write an OpenAPI path to the output document, then the
+		// first one will definitely not have a trailing slash.
+		originalPathHasTrailingSlash := strings.HasSuffix(path, "/")
+		if originalPathHasTrailingSlash && (pi.Get != nil || pi.Post != nil || pi.Delete != nil) {
+			backend.Logger().Warn(
+				"OpenAPI spec generation: discarding impossible-to-invoke non-list operations from path with "+
+					"required trailing slash; this is a bug in the backend code", "path", path)
+			pi.Get = nil
+			pi.Post = nil
+			pi.Delete = nil
+		}
+
+		// Write the regular, non-list, OpenAPI path to the OpenAPI document, UNLESS we generated a ListOperation, and
+		// NO OTHER operation types. In that fairly common case (there are lots of list-only endpoints), we avoid
+		// writing a redundant OpenAPI path for (e.g.) "auth/token/accessors" with no operations, only to then write
+		// one for "auth/token/accessors/" immediately below.
+		//
+		// On the other hand, we do still write the OpenAPI path here if we generated ZERO operation types - this serves
+		// to provide documentation to a human that an endpoint exists, even if it has no invokable OpenAPI operations.
+		// Examples of this include kv-v2's ".*" endpoint (regex cannot be translated to OpenAPI parameters), and the
+		// auth/oci/login endpoint (implements ResolveRoleOperation only, only callable from inside Vault).
+		if listOperation == nil || pi.Get != nil || pi.Post != nil || pi.Delete != nil {
+			openAPIPath := "/" + path
+			if doc.Paths[openAPIPath] != nil {
+				backend.Logger().Warn(
+					"OpenAPI spec generation: multiple framework.Path instances generated the same path; "+
+						"last processed wins", "path", openAPIPath)
+			}
+			doc.Paths[openAPIPath] = &pi
+		}
+
+		// If there is a ListOperation, write it to a separate OpenAPI path in the document.
+		if listOperation != nil {
+			// Append a slash here to disambiguate from the path written immediately above.
+			// However, if the path already contains a trailing slash, we want to avoid doubling it, and it is
+			// guaranteed (through the interaction of logic in the last two blocks) that the block immediately above
+			// will NOT have written a path to the OpenAPI document.
+			if !originalPathHasTrailingSlash {
+				path += "/"
+			}
+
+			listPathItem := OASPathItem{
+				Description:  pi.Description,
+				Parameters:   pi.Parameters,
+				DisplayAttrs: pi.DisplayAttrs,
+
+				// Since the path may now have an extra slash on the end, we need to recalculate the special path
+				// matches, as the sudo or unauthenticated status may be changed as a result!
+				Sudo:            specialPathMatch(path, sudoPaths),
+				Unauthenticated: specialPathMatch(path, unauthPaths),
+
+				Get: listOperation,
+			}
+
+			openAPIPath := "/" + path
+			if doc.Paths[openAPIPath] != nil {
+				backend.Logger().Warn(
+					"OpenAPI spec generation: multiple framework.Path instances generated the same path; "+
+						"last processed wins", "path", openAPIPath)
+			}
+			doc.Paths[openAPIPath] = &listPathItem
 		}
-		doc.Paths[openAPIPath] = &pi
 	}
 
 	return nil
diff --git a/sdk/framework/testdata/operations.json b/sdk/framework/testdata/operations.json
index 7fca0e265014..91bd64d27059 100644
--- a/sdk/framework/testdata/operations.json
+++ b/sdk/framework/testdata/operations.json
@@ -47,17 +47,7 @@
           "200": {
             "description": "OK"
           }
-        },
-        "parameters": [
-          {
-            "name": "list",
-            "description": "Return a list if `true`",
-            "in": "query",
-            "schema": {
-              "type": "string"
-            }
-          }
-        ]
+        }
       },
       "post": {
         "operationId": "kv-write-foo-id",
@@ -82,6 +72,59 @@
           }
         }
       }
+    },
+    "/foo/{id}/": {
+      "description": "Synopsis",
+      "x-vault-sudo": true,
+      "x-vault-displayAttrs": {
+        "navigation": true
+      },
+      "parameters": [
+        {
+          "name": "format",
+          "description": "a query param",
+          "in": "query",
+          "schema": {
+            "type": "string"
+          }
+        },
+        {
+          "name": "id",
+          "description": "id path parameter",
+          "in": "path",
+          "schema": {
+            "type": "string"
+          },
+          "required": true
+        }
+      ],
+      "get": {
+        "operationId": "kv-list-foo-id",
+        "tags": [
+          "secrets"
+        ],
+        "summary": "List Summary",
+        "description": "List Description",
+        "responses": {
+          "200": {
+            "description": "OK"
+          }
+        },
+        "parameters": [
+          {
+            "name": "list",
+            "description": "Must be set to `true`",
+            "required": true,
+            "in": "query",
+            "schema": {
+              "type": "string",
+              "enum": [
+                "true"
+              ]
+            }
+          }
+        ]
+      }
     }
   },
   "components": {
diff --git a/sdk/framework/testdata/operations_list.json b/sdk/framework/testdata/operations_list.json
index a08208b24fa4..d7bc50187c78 100644
--- a/sdk/framework/testdata/operations_list.json
+++ b/sdk/framework/testdata/operations_list.json
@@ -10,7 +10,7 @@
     }
   },
   "paths": {
-    "/foo/{id}": {
+    "/foo/{id}/": {
       "description": "Synopsis",
       "x-vault-sudo": true,
       "x-vault-displayAttrs": {
diff --git a/vault/external_tests/api/sudo_paths_test.go b/vault/external_tests/api/sudo_paths_test.go
index 0ca470f1d87c..f2590992a86f 100644
--- a/vault/external_tests/api/sudo_paths_test.go
+++ b/vault/external_tests/api/sudo_paths_test.go
@@ -5,19 +5,13 @@ package api
 
 import (
 	"fmt"
+	"strings"
 	"testing"
 
 	log "github.com/hashicorp/go-hclog"
 	"github.com/hashicorp/vault/api"
-	"github.com/hashicorp/vault/audit"
-	auditFile "github.com/hashicorp/vault/builtin/audit/file"
-	credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
-	"github.com/hashicorp/vault/builtin/logical/database"
-	"github.com/hashicorp/vault/builtin/logical/pki"
-	"github.com/hashicorp/vault/builtin/logical/transit"
 	"github.com/hashicorp/vault/helper/builtinplugins"
 	"github.com/hashicorp/vault/sdk/helper/jsonutil"
-	"github.com/hashicorp/vault/sdk/logical"
 	"github.com/hashicorp/vault/vault"
 )
 
@@ -33,24 +27,13 @@ func TestSudoPaths(t *testing.T) {
 		EnableRaw:           true,
 		EnableIntrospection: true,
 		Logger:              log.NewNullLogger(),
-		CredentialBackends: map[string]logical.Factory{
-			"userpass": credUserpass.Factory,
-		},
-		AuditBackends: map[string]audit.Factory{
-			"file": auditFile.Factory,
-		},
-		LogicalBackends: map[string]logical.Factory{
-			"database":       database.Factory,
-			"generic-leased": vault.LeasedPassthroughBackendFactory,
-			"pki":            pki.Factory,
-			"transit":        transit.Factory,
-		},
-		BuiltinRegistry: builtinplugins.Registry,
+		BuiltinRegistry:     builtinplugins.Registry,
 	}
 	client, _, closer := testVaultServerCoreConfig(t, coreConfig)
 	defer closer()
 
-	for credBackendName := range coreConfig.CredentialBackends {
+	// At present there are no auth methods with sudo paths, except for the automatically mounted token backend
+	for _, credBackendName := range []string{} {
 		err := client.Sys().EnableAuthWithOptions(credBackendName, &api.EnableAuthOptions{
 			Type: credBackendName,
 		})
@@ -59,7 +42,8 @@ func TestSudoPaths(t *testing.T) {
 		}
 	}
 
-	for logicalBackendName := range coreConfig.LogicalBackends {
+	// Each secrets engine that contains sudo paths (other than automatically mounted ones) must be mounted here
+	for _, logicalBackendName := range []string{"pki"} {
 		err := client.Sys().Mount(logicalBackendName, &api.MountInput{
 			Type: logicalBackendName,
 		})
@@ -77,11 +61,24 @@ func TestSudoPaths(t *testing.T) {
 
 	// check for missing paths
 	for path := range sudoPathsFromSpec {
-		if _, ok := sudoPathsInCode[path]; !ok {
+		pathTrimmed := strings.TrimRight(path, "/")
+		if _, ok := sudoPathsInCode[pathTrimmed]; !ok {
 			t.Fatalf(
 				"A path in the OpenAPI spec is missing from the static list of "+
 					"sudo paths in the api module (%s). Please reconcile the two "+
-					"accordingly.", path)
+					"accordingly.", pathTrimmed)
+		}
+	}
+
+	// check for extra paths
+	for path := range sudoPathsInCode {
+		if _, ok := sudoPathsFromSpec[path]; !ok {
+			if _, ok := sudoPathsFromSpec[path+"/"]; !ok {
+				t.Fatalf(
+					"A path in the static list of sudo paths in the api module "+
+						"is not marked as a sudo path in the OpenAPI spec (%s). Please reconcile the two "+
+						"accordingly.", path)
+			}
 		}
 	}
 }
diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go
index ce525d5b9134..f90b50c0cac2 100644
--- a/vault/logical_system_paths.go
+++ b/vault/logical_system_paths.go
@@ -3678,6 +3678,9 @@ func (b *SystemBackend) policyPaths() []*framework.Path {
 							},
 						}},
 					},
+					DisplayAttrs: &framework.DisplayAttributes{
+						OperationSuffix: "acl-policies2", // this endpoint duplicates sys/policies/acl
+					},
 				},
 				logical.ListOperation: &framework.PathOperation{
 					Callback: b.handlePoliciesList(PolicyTypeACL),
@@ -3695,6 +3698,9 @@ func (b *SystemBackend) policyPaths() []*framework.Path {
 							},
 						}},
 					},
+					DisplayAttrs: &framework.DisplayAttributes{
+						OperationSuffix: "acl-policies3", // this endpoint duplicates sys/policies/acl
+					},
 				},
 			},
 
diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go
index 5ee5b3fdf221..cea1c8afbc6c 100644
--- a/vault/logical_system_test.go
+++ b/vault/logical_system_test.go
@@ -4103,7 +4103,7 @@ func TestSystemBackend_OpenAPI(t *testing.T) {
 		}{
 			{path: "/auth/token/lookup", tag: "auth"},
 			{path: "/cubbyhole/{path}", tag: "secrets"},
-			{path: "/identity/group/id", tag: "identity"},
+			{path: "/identity/group/id/", tag: "identity"},
 			{path: expectedSecretPrefix + "^.*$", unpublished: true},
 			{path: "/sys/policy", tag: "system"},
 		}