Add granular per-API-family control over anonymous REST/GraphQL access (independent of custom /api/vtl endpoints)
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Description
Reported via Freshdesk #38556 (customer on dotCMS 25.07.10 LTS, self-hosted).
A customer's security team needs to disable anonymous access to the built-in content/query/GraphQL REST APIs (e.g. /api/content, /api/v1/content, /api/es, /api/v1/graphql, /api/widget) while keeping their custom /api/vtl/* scripting endpoints publicly (anonymously) accessible. dotCMS provides no way to make that split today.
Current behavior
CONTENT_APIS_ALLOW_ANONYMOUS is a single global switch (NONE / READ / WRITE, default READ). Both the built-in content/GraphQL APIs and custom VTL endpoints resolve their anonymous access through this same setting:
AnonymousAccess.systemSetting()readsCONTENT_APIS_ALLOW_ANONYMOUS(defaultREAD).WebResource.init(...)setsaccess = rejectWhenNoUser ? NONE : systemSetting()andcheckAnonymousPermissions(...)throws401whensystemSetting() == NONE.- GraphQL:
DotGraphQLContextBuilderauthenticates viaWebResourcewithrequiredAnonAccess(AnonymousAccess.systemSetting()). - Custom VTL:
VTLResource.processRequest(...)callswebResource.init(null, request, response, false, null)→ also resolves tosystemSetting().
Net effect: setting CONTENT_APIS_ALLOW_ANONYMOUS=none to lock down the built-in APIs also disables the customer's custom /api/vtl/* endpoints, because they share the one switch. There is no per-endpoint / per-API-family granularity, and no GraphQL-specific toggle.
Why existing options don't suffice
- Rules engine is not a viable REST auth boundary.
CMSFilterfiresRulesEngine.fireRules(EVERY_REQUEST), but the GraphQL endpoint is a standalone servlet (DotGraphQLHttpServlet, mapped to/api/v1/graphqlinweb.xml) that performs its own auth and returns its own response, so URL/redirect rules do not reliably intercept it. Relying on Rules to gate REST endpoints is fragile and unsupported. - Removing the
CMS Anonymousrole from contentlets breaks legitimate anonymous VTL usage — the customer's Velocity$dotcontent.pull()search hubs run in an anonymous request context and stop resolving. - Velocity Secrets require an authenticated user, so they can't back anonymous VTL either.
Desired capability
A supported, granular way to disable anonymous access to the built-in content/query/GraphQL API families independently of custom /api/vtl/* scripting endpoints — without requiring customers to duplicate gating at an edge/reverse proxy.
Acceptance Criteria
- An administrator can disable anonymous access to the built-in content/query/GraphQL API families (
/api/content,/api/v1/content,/api/es,/api/v1/graphql,/api/widget) without disabling anonymous access to custom/api/vtl/*endpoints. - With the new control active, an anonymous request to
/api/v1/graphqlreturns401/403. - With the new control active, an anonymous request to
/api/content(and/api/v1/content,/api/es) returns401/403. - With the new control active, an anonymous request to a custom
/api/vtl/*endpoint still succeeds, and$dotcontent.pull()inside that VTL executes with the expected anonymous results. - Existing
CONTENT_APIS_ALLOW_ANONYMOUSbehavior is preserved (default unchanged) when the new control is not configured — no breaking change on upgrade. - The control's behavior and its interaction with
CONTENT_APIS_ALLOW_ANONYMOUSandREST_API_REJECT_WITH_NO_USERare documented. - Integration/Postman coverage: each built-in API family blocked-when-anonymous, and VTL allowed-when-anonymous.
Open Design Questions (for eng/product triage)
- Mechanism shape: per-API-family config keys? an include/exclude path list? or role-scoping that distinguishes the VTL execution context from direct content-API calls?
- Should GraphQL be independently toggleable from the classic content APIs, or is one "built-in content APIs" group sufficient?
- Precedence: how does the new control interact with
CONTENT_APIS_ALLOW_ANONYMOUS(per-family override of the global default?) and withREST_API_REJECT_WITH_NO_USER?
Additional Context
Source references (main @ 0d85453):
dotCMS/.../com/dotcms/rest/AnonymousAccess.java—CONTENT_APIS_ALLOW_ANONYMOUS, defaultREAD.dotCMS/.../com/dotcms/rest/WebResource.java:204—access = rejectWhenNoUser ? NONE : systemSetting();:366-382—checkAnonymousPermissions.dotCMS/.../com/dotcms/graphql/DotGraphQLContextBuilder.java—requiredAnonAccess(AnonymousAccess.systemSetting()).dotCMS/.../com/dotcms/rest/api/v1/vtl/VTLResource.java—webResource.init(null, request, response, false, null).dotCMS/.../com/dotmarketing/filters/CMSFilter.java:93—RulesEngine.fireRules(EVERY_REQUEST).dotCMS/src/main/webapp/WEB-INF/web.xml—DotGraphQLHttpServlet→/api/v1/graphql.
Current workaround provided to the customer: gate by path at the edge/reverse proxy — deny anonymous traffic to the built-in API families and allow-list /api/vtl/*.
Related (already fixed, different problem): #35037 / PR #35038 — anonymous GraphQL permission check.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with AnonymousAccess.java, WebResource.java, DotGraphQLContextBuilder.java, VTLResource.java, and web.xml to trace how anonymous access is resolved for each API family. Review the existing integration/Postman coverage and compare REST_API_REJECT_WITH_NO_USER with CONTENT_APIS_ALLOW_ANONYMOUS. Done means the approved control blocks anonymous built-in APIs while preserving anonymous VTL behavior, keeps existing defaults, and documents the interaction.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql, java
- Domain
- api, authentication, backend-api-design, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100