CVE-2023-40027 (published 2023-08-15T18:15Z)

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.

CVSS v3 base score: 5.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)

Weakness information:
cwe_id: CWE-862
cwe_name: Missing Authorization
description: The product does not perform an authorization check when an actor attempts to access a resource or perform an action.
extended_description: 
            Assuming a user with a given identity, authorization is the process of determining whether that user can access a given resource, based on the user's privileges and any permissions or other access-control specifications that apply to the resource.
            When access control checks are not applied, users are able to access data or perform actions that they should not be allowed to perform. This can lead to a wide range of problems, including information exposures, denial of service, and arbitrary code execution.
         
         

Fix information:
repo_url: https://github.com/keystonejs/keystone
commit_details: repo_url: https://github.com/keystonejs/keystone
author: Daniel Cousens
author_date: 2023-08-15 12:03:06+10:00
msg: Fix `ui.isAccessAllowed` when `undefined` to prevent access (#8771)

Co-authored-by: Daniel Cousens <dcousens@users.noreply.github.com>
repository: repo_url: https://github.com/keystonejs/keystone
repo_name: keystonejs/keystone
description: The most powerful headless CMS for Node.js — built with GraphQL and React
repo_language: TypeScript
owner: keystonejs
file_changes: filename: fix-admin-meta-access.md
change_type: ModificationType.ADD
diff: @@ -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`

programming_language: SQL
filename: generateAdminUI.ts
change_type: ModificationType.MODIFY
diff: @@ -7,7 +7,7 @@ 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);
 

programming_language: TypeScript
filename: index.ts
change_type: ModificationType.MODIFY
diff: @@ -1,2 +1 @@
 export { generateAdminUI } from './generateAdminUI';
-export { KeystoneMeta } from './adminMetaSchema';

programming_language: TypeScript
filename: app.ts
change_type: ModificationType.MODIFY
diff: @@ -12,7 +12,7 @@ import {
   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 };
 

programming_language: TypeScript
filename: index.ts
change_type: ModificationType.MODIFY
diff: @@ -1,7 +1,7 @@
 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';

programming_language: TypeScript
filename: index.ts
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,6 @@
 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 = {

programming_language: TypeScript
filename: admin-meta-resolver.ts
change_type: ModificationType.RENAME
diff: @@ -7,7 +7,7 @@ import type {
   ListMetaRootVal,
   AdminMetaRootVal,
   FieldGroupMeta,
-} from './createAdminMeta';
+} from './create-admin-meta';
 
 type Context = KeystoneContext | { isAdminUIBuildProcess: true };
 
@@ -228,19 +228,25 @@ 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');

programming_language: JavaScript
filename: create-admin-meta.ts
change_type: ModificationType.RENAME
diff: @@ -8,9 +8,10 @@ import type {
   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>;
 

programming_language: TypeScript
filename: createGraphQLSchema.ts
change_type: ModificationType.MODIFY
diff: @@ -1,9 +1,9 @@
 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';

programming_language: TypeScript
filename: createSystem.ts
change_type: ModificationType.MODIFY
diff: @@ -2,9 +2,9 @@ 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';

programming_language: TypeScript
filename: dev.ts
change_type: ModificationType.MODIFY
diff: @@ -25,7 +25,7 @@ import {
 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';

programming_language: TypeScript