CVE-2023-40027: Missing Authorization (CWE-862)
value: Keystone is an open source headless CMS for Node.js — built with GraphQL and React. When `ui.isAccessAllowed` is set as `undefined`, the `adminMeta` GraphQL query is publicly accessible (no session required). This is different to the behaviour of the default AdminUI middleware, which by default will only be publicly accessible (no session required) if a `session` strategy is not defined. This vulnerability does not affect developers using the `@keystone-6/auth` package, or any users that have written their own `ui.isAccessAllowed` (that is to say, `isAccessAllowed` is not `undefined`). This vulnerability does affect users who believed that their `session` strategy will, by default, enforce that `adminMeta` is inaccessible by the public in accordance with that strategy; akin to the behaviour of the AdminUI middleware. This vulnerability has been patched in `@keystone-6/core` version `5.5.1`. Users are advised to upgrade. Users unable to upgrade may opt to write their own `isAccessAllowed` functionality to work-around this vulnerability.
CWE description: The product does not perform an authorization check when an actor attempts to access a resource or perform an action.
CVSS v3 base score: 5.3
Published: 2023-08-15T18:15Z
Repository: https://github.com/keystonejs/keystone
Language: TypeScript
Files changed: .changeset/fix-admin-meta-access.md, packages/core/src/admin-ui/system/generateAdminUI.ts, packages/core/src/admin-ui/system/index.ts, packages/core/src/admin-ui/templates/app.ts, packages/core/src/admin-ui/templates/index.ts, packages/core/src/fields/types/relationship/index.ts, packages/core/src/lib/admin-meta-resolver.ts, packages/core/src/lib/create-admin-meta.ts, packages/core/src/lib/createGraphQLSchema.ts, packages/core/src/lib/createSystem.ts, packages/core/src/scripts/dev.ts
Commit date: 2023-08-15 12:03:06 +1000
Commit message: Fix `ui.isAccessAllowed` when `undefined` to prevent access (#8771)

Co-authored-by: Daniel Cousens <dcousens@users.noreply.github.com>
Diff: diff --git a/.changeset/fix-admin-meta-access.md b/.changeset/fix-admin-meta-access.md
new file mode 100644
index 000000000..4698edbb3
--- /dev/null
+++ b/.changeset/fix-admin-meta-access.md
@@ -0,0 +1,5 @@
+---
+'@keystone-6/core': patch
+---
+
+Fixes `ui.isAccessAllowed` when `undefined`, to prevent access to the `adminMeta` GraphQL query, akin to the behaviour for the default AdminUI `pageMiddleware`
diff --git a/packages/core/src/admin-ui/system/generateAdminUI.ts b/packages/core/src/admin-ui/system/generateAdminUI.ts
index 1c22ccd8d..44ab0292a 100644
--- a/packages/core/src/admin-ui/system/generateAdminUI.ts
+++ b/packages/core/src/admin-ui/system/generateAdminUI.ts
@@ -5,11 +5,11 @@ import resolve from 'resolve';
 import type { GraphQLSchema } from 'graphql';
 import { Entry, walk as _walk } from '@nodelib/fs.walk';
 import type { KeystoneConfig, AdminFileToWrite } from '../../types';
 import { writeAdminFiles } from '../templates';
 import { serializePathForImport } from '../utils/serializePathForImport';
-import type { AdminMetaRootVal } from './createAdminMeta';
+import type { AdminMetaRootVal } from '../../lib/create-admin-meta';
 
 const walk = promisify(_walk);
 
 function getDoesAdminConfigExist() {
   try {
diff --git a/packages/core/src/admin-ui/system/index.ts b/packages/core/src/admin-ui/system/index.ts
index 73ca4fe2e..40f22ecb8 100644
--- a/packages/core/src/admin-ui/system/index.ts
+++ b/packages/core/src/admin-ui/system/index.ts
@@ -1,2 +1 @@
 export { generateAdminUI } from './generateAdminUI';
-export { KeystoneMeta } from './adminMetaSchema';
diff --git a/packages/core/src/admin-ui/templates/app.ts b/packages/core/src/admin-ui/templates/app.ts
index 9667af113..90ede20ae 100644
--- a/packages/core/src/admin-ui/templates/app.ts
+++ b/packages/core/src/admin-ui/templates/app.ts
@@ -10,11 +10,11 @@ import {
   SelectionNode,
   ExecutionResult,
   Kind,
 } from 'graphql';
 import { staticAdminMetaQuery, StaticAdminMetaQuery } from '../admin-meta-graphql';
-import type { AdminMetaRootVal } from '../system/createAdminMeta';
+import type { AdminMetaRootVal } from '../../lib/create-admin-meta';
 
 type AppTemplateOptions = { configFileExists: boolean };
 
 export const appTemplate = (
   adminMetaRootVal: AdminMetaRootVal,
diff --git a/packages/core/src/admin-ui/templates/index.ts b/packages/core/src/admin-ui/templates/index.ts
index d6e516408..05bfa8e9f 100644
--- a/packages/core/src/admin-ui/templates/index.ts
+++ b/packages/core/src/admin-ui/templates/index.ts
@@ -1,9 +1,9 @@
 import * as Path from 'path';
 import type { GraphQLSchema } from 'graphql';
 import type { KeystoneConfig, AdminFileToWrite } from '../../types';
-import type { AdminMetaRootVal } from '../system/createAdminMeta';
+import type { AdminMetaRootVal } from '../../lib/create-admin-meta';
 import { appTemplate } from './app';
 import { homeTemplate } from './home';
 import { listTemplate } from './list';
 import { itemTemplate } from './item';
 import { noAccessTemplate } from './no-access';
diff --git a/packages/core/src/fields/types/relationship/index.ts b/packages/core/src/fields/types/relationship/index.ts
index 6d143ea7a..c8e36c262 100644
--- a/packages/core/src/fields/types/relationship/index.ts
+++ b/packages/core/src/fields/types/relationship/index.ts
@@ -1,8 +1,8 @@
 import { BaseListTypeInfo, FieldTypeFunc, CommonFieldConfig, fieldType } from '../../../types';
 import { graphql } from '../../..';
-import { getAdminMetaForRelationshipField } from '../../../admin-ui/system/createAdminMeta';
+import { getAdminMetaForRelationshipField } from '../../../lib/create-admin-meta';
 
 // This is the default display mode for Relationships
 type SelectDisplayConfig = {
   ui?: {
     // Sets the relationship to display as a Select field
diff --git a/packages/core/src/admin-ui/system/adminMetaSchema.ts b/packages/core/src/lib/admin-meta-resolver.ts
similarity index 95%
rename from packages/core/src/admin-ui/system/adminMetaSchema.ts
rename to packages/core/src/lib/admin-meta-resolver.ts
index 0f2800dc3..672a606e1 100644
--- a/packages/core/src/admin-ui/system/adminMetaSchema.ts
+++ b/packages/core/src/lib/admin-meta-resolver.ts
@@ -5,11 +5,11 @@ import { graphql as graphqlBoundToKeystoneContext } from '../..';
 import type {
   FieldMetaRootVal,
   ListMetaRootVal,
   AdminMetaRootVal,
   FieldGroupMeta,
-} from './createAdminMeta';
+} from './create-admin-meta';
 
 type Context = KeystoneContext | { isAdminUIBuildProcess: true };
 
 const graphql = {
   ...graphqlBoundToKeystoneContext,
@@ -226,23 +226,29 @@ const adminMeta = graphql.object<AdminMetaRootVal>()({
       },
     }),
   },
 });
 
+function defaultIsAccessAllowed({ session, sessionStrategy }: KeystoneContext) {
+  if (!sessionStrategy) return true;
+  return session !== undefined;
+}
+
 export const KeystoneMeta = graphql.object<{ adminMeta: AdminMetaRootVal }>()({
   name: 'KeystoneMeta',
   fields: {
     adminMeta: graphql.field({
       type: graphql.nonNull(adminMeta),
       resolve({ adminMeta }, args, context) {
-        if ('isAdminUIBuildProcess' in context || adminMeta.isAccessAllowed === undefined) {
+        if ('isAdminUIBuildProcess' in context) {
           return adminMeta;
         }
-        return Promise.resolve(adminMeta.isAccessAllowed(context)).then(isAllowed => {
-          if (isAllowed) {
-            return adminMeta;
-          }
+
+        const isAccessAllowed = adminMeta?.isAccessAllowed ?? defaultIsAccessAllowed;
+        return Promise.resolve(isAccessAllowed(context)).then(isAllowed => {
+          if (isAllowed) return adminMeta;
+
           // TODO: ughhhhhh, we really need to talk about errors.
           // mostly unrelated to above: error or return null here(+ make field nullable)?s
           throw new Error('Access denied');
         });
       },
diff --git a/packages/core/src/admin-ui/system/createAdminMeta.ts b/packages/core/src/lib/create-admin-meta.ts
similarity index 98%
rename from packages/core/src/admin-ui/system/createAdminMeta.ts
rename to packages/core/src/lib/create-admin-meta.ts
index f15051fdd..8b02b1320 100644
--- a/packages/core/src/admin-ui/system/createAdminMeta.ts
+++ b/packages/core/src/lib/create-admin-meta.ts
@@ -6,13 +6,14 @@ import type {
   BaseListTypeInfo,
   KeystoneContext,
   JSONValue,
   MaybeItemFunction,
 } from '../../types';
-import { humanize } from '../../lib/utils';
-import type { InitialisedList } from '../../lib/core/initialise-lists';
-import type { FilterOrderArgs } from '../../types/config/fields';
+import type { FilterOrderArgs } from '../types/config/fields';
+
+import { humanize } from './utils';
+import type { InitialisedList } from './core/initialise-lists';
 
 type ContextFunction<Return> = (context: KeystoneContext) => MaybePromise<Return>;
 
 export type FieldMetaRootVal = {
   key: string;
diff --git a/packages/core/src/lib/createGraphQLSchema.ts b/packages/core/src/lib/createGraphQLSchema.ts
index b38079f92..685fae60a 100644
--- a/packages/core/src/lib/createGraphQLSchema.ts
+++ b/packages/core/src/lib/createGraphQLSchema.ts
@@ -1,11 +1,11 @@
 import { GraphQLNamedType, GraphQLSchema } from 'graphql';
 
-import type { KeystoneConfig } from '../types';
-import { KeystoneMeta } from '../admin-ui/system/adminMetaSchema';
 import { graphql } from '../types/schema';
-import type { AdminMetaRootVal } from '../admin-ui/system/createAdminMeta';
+import type { KeystoneConfig } from '../types';
+import { KeystoneMeta } from './admin-meta-resolver';
+import type { AdminMetaRootVal } from './create-admin-meta';
 import type { InitialisedList } from './core/initialise-lists';
 
 import { getMutationsForList } from './core/mutations';
 import { getQueriesForList } from './core/queries';
 
diff --git a/packages/core/src/lib/createSystem.ts b/packages/core/src/lib/createSystem.ts
index 9b9d1d192..f61226c74 100644
--- a/packages/core/src/lib/createSystem.ts
+++ b/packages/core/src/lib/createSystem.ts
@@ -1,12 +1,12 @@
 import { randomBytes } from 'node:crypto';
 import pLimit from 'p-limit';
 import type { FieldData, KeystoneConfig } from '../types';
 
-import { createAdminMeta } from '../admin-ui/system/createAdminMeta';
 import type { PrismaModule } from '../artifacts';
 import { allowAll } from '../access';
+import { createAdminMeta } from './create-admin-meta';
 import { createGraphQLSchema } from './createGraphQLSchema';
 import { createContext } from './context/createContext';
 import { initialiseLists, InitialisedList } from './core/initialise-lists';
 import { setPrismaNamespace, setWriteLimit } from './core/utils';
 
diff --git a/packages/core/src/scripts/dev.ts b/packages/core/src/scripts/dev.ts
index 60408ed6c..b4476e3f9 100644
--- a/packages/core/src/scripts/dev.ts
+++ b/packages/core/src/scripts/dev.ts
@@ -23,11 +23,11 @@ import {
   getSystemPaths,
 } from '../artifacts';
 import type { KeystoneConfig } from '../types';
 import { initialiseLists } from '../lib/core/initialise-lists';
 import { printPrismaSchema } from '../lib/core/prisma-schema-printer';
-import type { AdminMetaRootVal } from '../admin-ui/system/createAdminMeta';
+import type { AdminMetaRootVal } from '../lib/create-admin-meta';
 import { pkgDir } from '../pkg-dir';
 import { ExitError } from './utils';
 import type { Flags } from './cli';
 
 const devLoadingHTMLFilepath = path.join(pkgDir, 'static', 'dev-loading.html');

Vulnerable code: import type { AdminMetaRootVal } from './createAdminMeta';
export { KeystoneMeta } from './adminMetaSchema';
import type { AdminMetaRootVal } from '../system/createAdminMeta';
import type { AdminMetaRootVal } from '../system/createAdminMeta';
import { getAdminMetaForRelationshipField } from '../../../admin-ui/system/createAdminMeta';
} from './createAdminMeta';
        if ('isAdminUIBuildProcess' in context || adminMeta.isAccessAllowed === undefined) {
        return Promise.resolve(adminMeta.isAccessAllowed(context)).then(isAllowed => {
          if (isAllowed) {
            return adminMeta;
          }
import { humanize } from '../../lib/utils';
import type { InitialisedList } from '../../lib/core/initialise-lists';
import type { FilterOrderArgs } from '../../types/config/fields';
import type { KeystoneConfig } from '../types';
import { KeystoneMeta } from '../admin-ui/system/adminMetaSchema';
import type { AdminMetaRootVal } from '../admin-ui/system/createAdminMeta';
import { createAdminMeta } from '../admin-ui/system/createAdminMeta';
import type { AdminMetaRootVal } from '../admin-ui/system/createAdminMeta';
Fixed code: ---
'@keystone-6/core': patch
---

Fixes `ui.isAccessAllowed` when `undefined`, to prevent access to the `adminMeta` GraphQL query, akin to the behaviour for the default AdminUI `pageMiddleware`
import type { AdminMetaRootVal } from '../../lib/create-admin-meta';
import type { AdminMetaRootVal } from '../../lib/create-admin-meta';
import type { AdminMetaRootVal } from '../../lib/create-admin-meta';
import { getAdminMetaForRelationshipField } from '../../../lib/create-admin-meta';
} from './create-admin-meta';
function defaultIsAccessAllowed({ session, sessionStrategy }: KeystoneContext) {
  if (!sessionStrategy) return true;
  return session !== undefined;
}

        if ('isAdminUIBuildProcess' in context) {

        const isAccessAllowed = adminMeta?.isAccessAllowed ?? defaultIsAccessAllowed;
        return Promise.resolve(isAccessAllowed(context)).then(isAllowed => {
          if (isAllowed) return adminMeta;

import type { FilterOrderArgs } from '../types/config/fields';

import { humanize } from './utils';
import type { InitialisedList } from './core/initialise-lists';
import type { KeystoneConfig } from '../types';
import { KeystoneMeta } from './admin-meta-resolver';
import type { AdminMetaRootVal } from './create-admin-meta';
import { createAdminMeta } from './create-admin-meta';
import type { AdminMetaRootVal } from '../lib/create-admin-meta';