Security audit: enumerate REST endpoints reachable without authentication
@wezell is already working on this.
Since May 4, 2026.
- 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:
- Endpoints that explicitly opt in to anonymous access via
AnonymousAccess.READ/AnonymousAccess.WRITE— only effective when the system propertyCONTENT_APIS_ALLOW_ANONYMOUSis set to the matching level. - Bootstrap endpoints that omit
WebResource.initby design (login, password reset, i18n bundles, K8s probes, etc.). - Endpoints with configurable auth (e.g.
HealthResourcegated byhealth.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/assignform fields honored.
Notes
@PermitAllis not used as the access-control mechanism in this codebase — anonymous access is gated throughWebResource.InitBuilder.requiredAnonAccess(...)plus theCONTENT_APIS_ALLOW_ANONYMOUSsystem 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 omitWebResource.initentirely. - Final runtime exposure depends on the deployed value of
CONTENT_APIS_ALLOW_ANONYMOUS(defaultREADin many deployments).
Suggested follow-ups
- Confirm each
AnonymousAccess.READ/WRITEdeclaration is still required and document the rationale next to the annotation. - Document the recommended production value of
CONTENT_APIS_ALLOW_ANONYMOUSand 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
TagResourceandCreateJsonWebTokenResourcecan 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
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.
Assessment
This issue has not been assessed yet.