Moderate severityOSV Advisory· Published Jan 27, 2026· Updated Jan 28, 2026
StudioCMS has an Authorization Bypass Through User-Controlled Key
CVE-2026-24134
Description
StudioCMS is a server-side-rendered, Astro native, headless content management system. Versions prior to 0.2.0 contain a Broken Object Level Authorization (BOLA) vulnerability in the Content Management feature that allows users with the "Visitor" role to access draft content created by Editor/Admin/Owner users. Version 0.2.0 patches the issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
studiocmsnpm | < 0.2.0 | 0.2.0 |
Affected products
1- Range: 0.1.0-beta.1, @astrolicious/studiocms-blog@0.1.0-beta.2, @astrolicious/studiocms-blog@0.1.0-beta.3, …
Patches
1efc10bee20dbfeat(auth): implement middleware-level permission checks for dashboard routes (#1214)
5 files changed · +122 −0
.changeset/metal-trains-lead.md+5 −0 added@@ -0,0 +1,5 @@ +--- +"studiocms": patch +--- + +Fix: Reworks permission checks for dashboard routes to be at the middleware level to prevent unauthorized access
packages/studiocms/frontend/middleware/_authmap.ts+63 −0 added@@ -0,0 +1,63 @@ +import { dashboardConfig } from 'studiocms:config'; + +type AuthenticatedRoute = { + pathname: string; + requiredPermissionLevel: 'owner' | 'admin' | 'editor' | 'visitor'; +}; + +// Import the dashboard route override from the configuration +// If no override is set, it defaults to 'dashboard' +// This allows for flexibility in the dashboard route without hardcoding it +const dashboardRoute = dashboardConfig.dashboardRouteOverride || 'dashboard'; + +/** + * List of authenticated routes with their required permission levels. + * This list is used to determine if a user has the necessary permissions + * to access specific dashboard routes. + */ +export const authenticatedRoutes: AuthenticatedRoute[] = [ + { + pathname: `/${dashboardRoute}/system-management`, + requiredPermissionLevel: 'owner', + }, + { + pathname: `/${dashboardRoute}/smtp-configuration`, + requiredPermissionLevel: 'owner', + }, + { + pathname: `/${dashboardRoute}`, + requiredPermissionLevel: 'editor', + }, + { + pathname: `/${dashboardRoute}/user-management`, + requiredPermissionLevel: 'admin', + }, + { + pathname: `/${dashboardRoute}/user-management/**`, + requiredPermissionLevel: 'admin', + }, + { + pathname: `/${dashboardRoute}/taxonomy`, + requiredPermissionLevel: 'editor', + }, + { + pathname: `/${dashboardRoute}/taxonomy/categories`, + requiredPermissionLevel: 'editor', + }, + { + pathname: `/${dashboardRoute}/taxonomy/tags`, + requiredPermissionLevel: 'editor', + }, + { + pathname: `/${dashboardRoute}/plugins/**`, + requiredPermissionLevel: 'editor', + }, + { + pathname: `/${dashboardRoute}/content-management`, + requiredPermissionLevel: 'editor', + }, + { + pathname: `/${dashboardRoute}/content-management/**`, + requiredPermissionLevel: 'editor', + }, +];
packages/studiocms/frontend/middleware/index.ts+46 −0 modified@@ -19,7 +19,9 @@ import { SDKCore } from 'studiocms:sdk'; import SCMSUiVersion from 'studiocms:ui/version'; import SCMSVersion from 'studiocms:version'; import { defineMiddlewareRouter, Effect } from '@withstudiocms/effect'; +import micromatch from 'micromatch'; import { STUDIOCMS_EDITOR_CSRF_COOKIE_NAME } from '#consts'; +import { authenticatedRoutes } from './_authmap.js'; import { getUserPermissions, makeFallbackSiteConfig, SetLocal, setLocals } from './utils.js'; // Import the dashboard route override from the configuration @@ -145,6 +147,50 @@ export const onRequest = defineMiddlewareRouter([ // Check if the user is logged in and redirect to the login page if not if (!userSessionData.isLoggedIn) return context.redirect(StudioCMSRoutes.authLinks.loginURL); + // Get the current path + const currentPath = context.url.pathname; + + // Check if the user has permission to access the current route + // If not, redirect to the dashboard home page + const userPermissionLevel = + context.locals.StudioCMS.security?.userSessionData.permissionLevel; + + if (!userPermissionLevel) { + // How did the user get here? Log them out to reset session + return context.redirect(`/${dashboardRoute}/logout`); + } + + // Using micromatch to handle wildcard route matching + const matchChance1 = authenticatedRoutes.find((route) => + micromatch.isMatch(currentPath, route.pathname) + ); + + // if trailing `/` exists, try matching without it + const matchChance2 = + matchChance1 || + (() => { + if (currentPath.endsWith('/')) { + const trimmedPath = currentPath.slice(0, -1); + return authenticatedRoutes.find((route) => + micromatch.isMatch(trimmedPath, route.pathname) + ); + } + return null; + })(); + + if (matchChance1 || matchChance2) { + // biome-ignore lint/style/noNonNullAssertion: only used after checking for existence + const matchingRoute = matchChance1 || matchChance2!; + const requiredLevel = matchingRoute.requiredPermissionLevel; + const levels = ['visitor', 'editor', 'admin', 'owner']; + const userLevelIndex = levels.indexOf(userPermissionLevel); + const requiredLevelIndex = levels.indexOf(requiredLevel); + + if (userLevelIndex < requiredLevelIndex) { + return context.redirect(`/${dashboardRoute}`); + } + } + // Else, Continue to the next middleware return next(); }),
packages/studiocms/package.json+2 −0 modified@@ -257,6 +257,7 @@ "jose": "^6.1.3", "micromark": "^4.0.2", "micromark-extension-gfm": "^3.0.0", + "micromatch": "^4.0.8", "magicast": "^0.5.1", "mdast-util-to-markdown": "^2.1.2", "mrmime": "^2.0.1", @@ -270,6 +271,7 @@ }, "devDependencies": { "@types/mdast": "^4.0.4", + "@types/micromatch": "^4.0.10", "@types/node": "catalog:", "@types/semver": "^7.7.1", "@types/three": "0.169.0",
pnpm-lock.yaml+6 −0 modified@@ -1113,6 +1113,9 @@ importers: micromark-extension-gfm: specifier: ^3.0.0 version: 3.0.0 + micromatch: + specifier: ^4.0.8 + version: 4.0.8 mrmime: specifier: ^2.0.1 version: 2.0.1 @@ -1147,6 +1150,9 @@ importers: '@types/mdast': specifier: ^4.0.4 version: 4.0.4 + '@types/micromatch': + specifier: ^4.0.10 + version: 4.0.10 '@types/node': specifier: 'catalog:' version: 22.19.6
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-8cw6-53m5-4932ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-24134ghsaADVISORY
- github.com/withstudiocms/studiocms/commit/efc10bee20db090fdd75463622c30dda390c50adghsax_refsource_MISCWEB
- github.com/withstudiocms/studiocms/releases/tag/studiocms%400.2.0ghsax_refsource_MISCWEB
- github.com/withstudiocms/studiocms/security/advisories/GHSA-8cw6-53m5-4932ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.