fix(containers): use POST for duplicate + sanitize id in log (#34900)#35384
fix(containers): use POST for duplicate + sanitize id in log (#34900)#35384dsolistorres merged 6 commits intomainfrom
Conversation
|
Claude finished @dsolistorres's task in 3m 16s —— View job PR Review
All four reviewers completed. The backend review comment has been updated — all previous findings are resolved in this push. No new issues found. |
|
Claude finished @dsolistorres's task in 1m 15s —— View job PR Review
Fix is correct and complete. The root cause was a clear mismatch: Sibling consistency: Test coverage: No Java integration tests cover the container One note worth acknowledging: No issues to block merge. |
🔍 dotCMS Backend Review[🟠 High]
Logger.error(this, MessageConstants.CONTAINER_ID_WITH + SecurityUtils.sanitizeForLogging(containerId) + ...);
failedToDelete.add(new FailedResultView(containerId, "Container does not exist")); // ← raw id in response body💡 Apply [🟠 High]
} catch(Exception e){
Logger.debug(this, e.getMessage(), e);
failedToDelete.add(new FailedResultView(containerId, e.getMessage())); // raw id in response
}💡 Wrap [🟡 Medium]
// Sanitized string goes to both log AND HTTP response body:
throw new DoesNotExistException("Live Version of the Container with Id: "
+ SecurityUtils.sanitizeForLogging(containerId) + MessageConstants.DOES_NOT_EXIST);💡 Sanitize only the Logger.error(this, "Live Version ... Id: " + SecurityUtils.sanitizeForLogging(containerId) + ...);
throw new DoesNotExistException("Live Version ... Id: " + containerId + MessageConstants.DOES_NOT_EXIST);[🟡 Medium]
throw new ResourceNotFoundException("Can't find Container:" + containerId); // unsanitized💡 Apply [🟡 Medium]
Logger.error(this, MessageConstants.CONTAINER + SecurityUtils.sanitizeForLogging(containerForm.getIdentifier()) + ", does not exists");
throw new DoesNotExistException(MessageConstants.CONTAINER + SecurityUtils.sanitizeForLogging(containerForm.getIdentifier()) + " does not exists");💡 Extract to a local variable: Next steps
|
The Containers portlet's Duplicate action issues an HTTP PUT to
/api/v1/containers/{id}/_copy, but the JAX-RS handler was annotated
@post, producing 405 Method Not Allowed. Switch the annotation to
@put (consistent with TemplateResource._copy and SiteResource._copy)
and update the three Postman CopyContainer* tests accordingly.
Refs: #34900
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Revert the backend /_copy endpoint to @post (non-idempotent create semantics) and flip the frontend DotContainersService.copy() to http.post so the two sides agree. Also sanitize the caller-supplied container id with SecurityUtils.sanitizeForLogging before writing it to the error log to prevent log-injection via CR/LF/control characters. Refs: #34900 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrap every Logger.* call in ContainerResource that concatenated a raw path/query/body parameter (containerId, hostId, containerForm.getIdentifier(), bulk id lists) with SecurityUtils.sanitizeForLogging(...) to prevent log injection via CR/LF/control characters. Exception messages passed as the Throwable argument and values retrieved from successful API lookups are left untouched. Refs: #34900 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The exception messages thrown from ContainerResource bubble up to DoesNotExistExceptionMapper, which copies getMessage() verbatim into the 404 JSON body. Mirror the SecurityUtils.sanitizeForLogging(...) wrap already applied to the paired Logger calls so CR/LF/control characters from path/query/body input can't be reflected back to the client. Refs: #34900 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bb9dfad to
daf124a
Compare
Apply the PR review follow-ups: - Sanitize caller-supplied containerId in all FailedResultView entries returned by the five bulk endpoints (both the "does not exist" branch and the catch(Exception) branch) so an attacker-controlled id can no longer be echoed verbatim into the HTTP 200 response body. - Sanitize the containerId / contentletId interpolated into ResourceNotFoundException messages in the private getContainer and removeContentletFromContainer helpers, whose messages are copied into the 404 response body by ResourceNotFoundExceptionMapper. - Revert the previous SecurityUtils.sanitizeForLogging(...) calls inside DoesNotExistException constructor arguments. Logger sanitization stays in place; the exception message reverts to the raw identifier so legitimate API consumers continue to see the exact id they submitted (JSON serialization already escapes CR/LF, so this is not a response-splitting vector) and the sanitizeForLogging helper is no longer misused for user-facing content. Refs: #34900 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
✅ dotCMS Backend Review: no issues found. All findings from the previous two review cycles are resolved in this push:
Database, Java standards, and REST API reviewers found no issues. |
Replace the five separate containerForm.getIdentifier() calls in ContainerResource.update with a single final local, so the value is read from the form once and reused for the working-container lookup, Logger.error, DoesNotExistException, the new container version, and the ActivityLogger audit line. Refs: #34900 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
POST(non-idempotent create semantics): flipDotContainersService.copy()fromhttp.puttohttp.postso it matches the backend's existing@POST /_copyhandler. This resolves the HTTP 405 that broke duplication.ContainerResourceagainst log injection: everyLogger.*call that concatenated a caller-controlled string (path/querycontainerId, bodycontainerForm.getIdentifier(), bodyhostId, and bulk id lists) now runs throughSecurityUtils.sanitizeForLogging(...), which strips CR/LF and control characters. Trusted values (objects returned from successful API lookups,Throwablesecond-args) are left alone.dot-containers.service.spec.ts) to assertPOST.Fixes
Files changed
core-web/apps/dotcms-ui/src/app/api/services/dot-containers/dot-containers.service.ts—http.put→http.postcore-web/apps/dotcms-ui/src/app/api/services/dot-containers/dot-containers.service.spec.ts— expectPOSTdotCMS/src/main/java/com/dotcms/rest/api/v1/container/ContainerResource.java— wrap every untrusted-inputLogger.*call withSecurityUtils.sanitizeForLogging(...):resolveHost(warn):hostIdupdate(error):containerForm.getIdentifier()getLiveById/getWorkingById(debug + error):containerIdpublish/unpublish/archive/unarchive/delete(debug + error):containerIdcopy(error):idbulkDelete/bulkPublish/bulkUnpublish/bulkArchive/bulkUnarchive(debug on joined payload + error on loopcontainerId)dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml— regenerateddotcms-postman/src/main/resources/postman/Containers.postman_collection.json— no net change vs.main(verb stays POST)Test plan
yarn nx test dotcms-ui --testPathPattern=dot-containers.service.spec./mvnw verify -pl :dotcms-postman -Dpostman.test.skip=false -Dpostman.collections=Containers/api/v1/containers?containerId=bogus%0AINJECTEDagainst publish/unpublish/archive/delete and confirming the CR/LF in the id is replaced with_in the server log.Notes / out of scope
ActivityLogger.logInfo(...)audit-log calls andJsonUtil.getJsonStringFromObject(containerForm)dumps were intentionally left alone; JSON serialization already escapes CR/LF, and audit-log hardening can be addressed in a follow-up if desired.ResponseEntityView, etc.) were not touched to keep the diff focused.🤖 Generated with Claude Code