dotCMS / dotCMS/core

Security audit: enumerate REST endpoints reachable without authentication

Open
#35,543 0 comments 0 reactions 3 assignees View on GitHub

@wezell is already working on this.

Since May 4, 2026.

dotCMS : Rest API OKR : Security & Privacy Priority : 2 High Type : Documentation
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

Summary

Audit of dotCMS REST/JAX-RS endpoints that are reachable without authentication, to confirm each is intentionally public, document the security posture, and identify any follow-up hardening (rate limiting, deployment defaults, etc.).

Authentication in dotCMS is enforced through com.dotcms.rest.WebResource (InitBuilder.requiredAnonAccess(...)). Endpoints fall into three categories:

  1. Endpoints that explicitly opt in to anonymous access via AnonymousAccess.READ / AnonymousAccess.WRITE — only effective when the system property CONTENT_APIS_ALLOW_ANONYMOUS is set to the matching level.
  2. Bootstrap endpoints that omit WebResource.init by design (login, password reset, i18n bundles, K8s probes, etc.).
  3. Endpoints with configurable auth (e.g. HealthResource gated by health.detailed.authentication.required).

1. Endpoints declaring anonymous access

Read-allowed (AnonymousAccess.READ)
Method Path Resource
GET /api/v1/tags dotCMS/src/main/java/com/dotcms/rest/TagResource.java (deprecated v1)
GET /api/v2/tags/{search} dotCMS/src/main/java/com/dotcms/rest/api/v2/tags/TagResource.java
GET /api/v1/content/{inodeOrIdentifier} dotCMS/src/main/java/com/dotcms/rest/api/v1/content/ContentResource.java
GET /api/v1/content/_canlock/{inodeOrIdentifier} same
POST /api/v1/content/related same
GET /api/v2/languages/{language}/keys dotCMS/src/main/java/com/dotcms/rest/api/v2/languages/LanguagesResource.java
Write-allowed (AnonymousAccess.WRITE)
Method Path Resource
POST /api/content/{params:.*} dotCMS/src/main/java/com/dotcms/rest/ContentResource.java (legacy)
PUT /api/v1/content/_draft …/v1/content/ContentResource.java
PUT /api/v1/content/_lock/{inodeOrIdentifier} same
PUT /api/v1/content/_unlock/{inodeOrIdentifier} same
PUT /api/v1/content/_refresh/{identifierOrInode} same
POST /api/v1/temp, /api/v1/temp/byUrl …/v1/temp/TempFileResource.java
PUT/POST/PATCH workflow fire-action variants — see §3 …/v1/workflow/WorkflowResource.java

2. Always-public bootstrap endpoints

These omit WebResource.init and are public by design. Confirmed not to be accidental — each has a clear bootstrap purpose (login UI, password recovery, K8s health, i18n).

Method Path Resource Purpose
POST /api/v1/authentication AuthenticationResource Credentials login
GET /api/v1/authentication/logInUser AuthenticationResource Current session user
POST /api/v1/authentication/api-token CreateJsonWebTokenResource JWT from credentials (deprecated)
POST /api/v1/loginform LoginFormResource Login UI config (company, i18n)
POST /api/v1/forgotpassword ForgotPasswordResource Send password reset email
POST /api/v1/changePassword ResetPasswordResource Token-gated password reset
GET /api/v1/appconfiguration AppContextInitResource App config for login page
GET /api/v1/system/i18n/{lang}/{rsrc} I18NResource i18n message bundles
GET /api/v1/system-status/, /api/v1/probes/{alive,light,ready} MonitorResource K8s liveness/readiness probes
GET/POST /api/v1/health[/...] HealthResource Health checks; gated by health.detailed.authentication.required (default: required)

3. Workflow fire-action variants (AnonymousAccess.WRITE)

All in dotCMS/src/main/java/com/dotcms/rest/api/v1/workflow/WorkflowResource.java. Anonymous access is conditional on CONTENT_APIS_ALLOW_ANONYMOUS=WRITE; per-contentlet permission checks still run inside the method bodies.

Method Path Variant
PUT /api/v1/workflow/actions/fire Fire action by name (single + multipart)
PUT /api/v1/workflow/actions/{actionId}/fire Fire by action ID
PUT /api/v1/workflow/actions/{actionId}/firemultipart Fire by action ID, multipart
PUT /api/v1/workflow/actions/default/fire/{systemAction} Fire system action (NEW/EDIT/PUBLISH/UNPUBLISH/ARCHIVE/UNARCHIVE/DELETE/DESTROY)
PUT /api/v1/workflow/actions/default/firemultipart/{systemAction} System action, multipart
POST /api/v1/workflow/actions/default/fire/{systemAction} Bulk fire on multiple contentlets
PATCH /api/v1/workflow/actions/default/fire/{systemAction} Bulk merge/update via query

In-method auth checks observed:

  • Contentlet-level permission validation via permissionAPI.doesUserHavePermission(...) (READ / WRITE / EDIT / PUBLISH).
  • Workflow action reachability check from the contentlet's current step.
  • Optional individualPermissions / assign form fields honored.

Notes

  • @PermitAll is not used as the access-control mechanism in this codebase — anonymous access is gated through WebResource.InitBuilder.requiredAnonAccess(...) plus the CONTENT_APIS_ALLOW_ANONYMOUS system property.
  • The scan covered ~784 JAX-RS resource files. Eight contained explicit requiredAnonAccess(READ|WRITE) declarations; the bootstrap endpoints listed in §2 are the complete set of resources that omit WebResource.init entirely.
  • Final runtime exposure depends on the deployed value of CONTENT_APIS_ALLOW_ANONYMOUS (default READ in many deployments).

Suggested follow-ups

  • Confirm each AnonymousAccess.READ/WRITE declaration is still required and document the rationale next to the annotation.
  • Document the recommended production value of CONTENT_APIS_ALLOW_ANONYMOUS and surface it prominently in deployment / hardening docs.
  • Verify rate limiting / brute-force protection on /api/v1/authentication, /api/v1/authentication/api-token, /api/v1/forgotpassword, /api/v1/changePassword.
  • Re-evaluate whether the deprecated v1 TagResource and CreateJsonWebTokenResource can be removed.
  • Decide whether anonymous workflow fire-action endpoints should be opt-in per workflow/content type rather than a global system property.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.