fix: 230 remaining code scanning vulnerabilities across 111 files#1082
Conversation
… and cookie security Agent-Logs-Url: https://github.com/carlos-emr/carlos/sessions/a960b595-8395-480f-8fea-04cca000a153 Co-authored-by: yingbull <8680161+yingbull@users.noreply.github.com>
| expiredCookie.setHttpOnly(true); | ||
| // Add expired cookie to response to overwrite browser's stored cookie | ||
| response.addCookie(cookie); | ||
| response.addCookie(expiredCookie); |
Check warning
Code scanning / CodeQL
Failure to use secure cookies Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 3 months ago
To fix the problem, ensure that every cookie you add to the HttpServletResponse has the Secure flag set so it is only sent over HTTPS. In this logout routine, you already construct “expired” cookies to delete existing ones; you just need to additionally call expiredCookie.setSecure(true); before adding them to the response.
The best minimal fix without changing existing functionality is to modify Logout2Action.logout() in src/main/java/io/github/carlos_emr/carlos/login/Logout2Action.java, inside the loop that clears cookies. Between the existing expiredCookie.setHttpOnly(true); and response.addCookie(expiredCookie); lines, add a call to expiredCookie.setSecure(true);. No new imports or helper methods are required, since Cookie is already imported from jakarta.servlet.http.
Concretely, in the region around lines 217–225, update the code to:
- Keep the creation and configuration of
expiredCookieas is. - Add
expiredCookie.setSecure(true);afterexpiredCookie.setHttpOnly(true);.
This ensures the deletion cookies are sent only over secure channels and resolves the CodeQL alert.
| @@ -221,6 +221,8 @@ | ||
| expiredCookie.setPath("/"); | ||
| // Mark as HttpOnly to prevent client-side script access | ||
| expiredCookie.setHttpOnly(true); | ||
| // Mark as Secure to ensure cookie is only sent over HTTPS | ||
| expiredCookie.setSecure(true); | ||
| // Add expired cookie to response to overwrite browser's stored cookie | ||
| response.addCookie(expiredCookie); | ||
| } |
| file.setExecutable(true, false); | ||
| file.setReadable(true, false); | ||
| // owner-only permissions: second arg "true" = ownerOnly | ||
| file.setWritable(true, true); |
…and open redirect Agent-Logs-Url: https://github.com/carlos-emr/carlos/sessions/a960b595-8395-480f-8fea-04cca000a153 Co-authored-by: yingbull <8680161+yingbull@users.noreply.github.com>
…TLS bypass in CxfClientUtils/EdtClientBuilder Agent-Logs-Url: https://github.com/carlos-emr/carlos/sessions/bf0242ee-26fd-4eaf-8909-c679ccbdc370 Co-authored-by: yingbull <8680161+yingbull@users.noreply.github.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
📝 WalkthroughWalkthroughThis PR applies comprehensive security hardening across 20+ Java files and one JSP file, implementing cryptographic algorithm upgrades (AES-GCM, SHA-256, RSA-2048), input validation via allowlists, HTTP header and cookie sanitization, constant-time password comparison, and conditional TLS certificate validation. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Failed to generate code suggestions for PR |
There was a problem hiding this comment.
Pull request overview
This WIP PR addresses CARLOS EMR code-scanning findings by hardening header construction, cryptography, TLS configuration, and reflection-based factories across several modules (reports, labs, scheduling, forms, login/logout).
Changes:
- Prevent HTTP response splitting by sanitizing header values (filenames, redirect targets, cookie names).
- Replace insecure crypto primitives (AES/ECB, MD5, RSA-1024, SSLv3) with stronger defaults (AES/GCM, SHA-256, RSA-2048, TLS).
- Reduce attack surface from unsafe reflection by introducing allowlists for form/graphic factories and gating TLS-bypass behavior behind config.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/webapp/eform/fieldNoteReport/fieldnotereportdetail.jsp | Sanitizes generated download filename to mitigate response splitting. |
| src/main/java/io/github/carlos_emr/carlos/utility/MiscUtils.java | Adds prominent warning when enabling JVM-wide trust-all TLS behavior. |
| src/main/java/io/github/carlos_emr/carlos/utility/EncryptionUtils.java | Renames config key constant; updates deprecated AES usage to AES/GCM with IV-prefixing. |
| src/main/java/io/github/carlos_emr/carlos/utility/CxfClientUtils.java | Replaces SSLv3 with TLS and makes trust-all conditional via config + adds getter. |
| src/main/java/io/github/carlos_emr/carlos/report/pageUtil/RptDownloadCSVServlet.java | Sanitizes CSV download filename before using in Content-Disposition. |
| src/main/java/io/github/carlos_emr/carlos/mds/pageUtil/ReportReassign2Action.java | Sanitizes request URI to prevent Location-header splitting. |
| src/main/java/io/github/carlos_emr/carlos/managers/DemographicManagerImpl.java | Adds regex allowlist guard before compiling client-provided regex. |
| src/main/java/io/github/carlos_emr/carlos/login/Startup.java | Updates encryption key property name usage during startup initialization. |
| src/main/java/io/github/carlos_emr/carlos/login/Logout2Action.java | Rebuilds expired cookies using sanitized names; marks as HttpOnly. |
| src/main/java/io/github/carlos_emr/carlos/lab/ca/all/util/LabVersionComparator.java | Uses SHA-256 instead of MD5 for lab duplicate detection hashing. |
| src/main/java/io/github/carlos_emr/carlos/lab/ca/all/util/KeyPairGen.java | Increases generated RSA key size to 2048 bits. |
| src/main/java/io/github/carlos_emr/carlos/lab/ca/all/pageUtil/LabUpload2Action.java | Documents externally-mandated AES/ECB interoperability constraint. |
| src/main/java/io/github/carlos_emr/carlos/lab/ca/all/pageUtil/DownloadEmbeddedDocumentFromLab2Action.java | Sanitizes lab number used in Content-Disposition filename. |
| src/main/java/io/github/carlos_emr/carlos/integration/ebs/client/ng/EdtClientBuilder.java | Gates trust-all TLS behavior behind CxfClientUtils allowAllSsl config. |
| src/main/java/io/github/carlos_emr/carlos/form/graphic/FrmGraphicFactory.java | Replaces reflection-by-name with an allowlisted class map and safer instantiation. |
| src/main/java/io/github/carlos_emr/carlos/form/FrmRecordFactory.java | Introduces allowlist validation for form record class loading. |
| src/main/java/io/github/carlos_emr/carlos/fax/admin/ConfigureFax2Action.java | Renames masked password placeholder constant to avoid “hardcoded password” signal. |
| src/main/java/io/github/carlos_emr/carlos/documentManager/actions/SplitDocument2Action.java | Restricts saved file permissions to owner-only (not world-writable). |
| src/main/java/io/github/carlos_emr/carlos/demographic/pageUtil/ImportDemographicDataAction42Action.java | Switches report hashing from MD5 to SHA-256. |
| src/main/java/io/github/carlos_emr/carlos/demographic/pageUtil/DemographicExportAction42Action.java | Documents and explicitly preserves readable-by-JS status cookie behavior (HttpOnly=false). |
| src/main/java/io/github/carlos_emr/carlos/demographic/pageUtil/CihiExportPHC_VRS2Action.java | Migrates anonymization hash to SHA-256 and updates related error message. |
| src/main/java/io/github/carlos_emr/carlos/commn/model/HashAudit.java | Updates declared hash algorithm constant to SHA-256. |
| src/main/java/io/github/carlos_emr/carlos/casemgmt/web/EChartPrint2Action.java | Sanitizes demographic number used in Content-Disposition filename. |
| src/main/java/io/github/carlos_emr/carlos/casemgmt/service/CaseManagementManagerImpl.java | Uses constant-time comparison for note unlock password check. |
| src/main/java/io/github/carlos_emr/carlos/appointment/search/SearchConfig.java | Migrates token encryption/decryption to AES/GCM with IV-prefixing. |
| // Prevent regex injection / ReDoS: validate regex against a safe allowlist pattern. | ||
| // The expected format (produced by updatedemographicprovider.jsp) is: | ||
| // "^[A-Z]-[A-Z].*$" e.g. "^[A-M].*$" or "^[A-F].*$" | ||
| // Any other pattern is rejected to prevent catastrophic backtracking. | ||
| if (!regex.matches("\\^\\[[A-Za-z]-[A-Za-z]\\].*") && !regex.matches("\\^\\[[A-Za-z]\\].*")) { | ||
| logger.warn("Rejected potentially unsafe regex pattern from client: (pattern omitted for security)"); | ||
| return new ArrayList<>(); | ||
| } |
There was a problem hiding this comment.
The regex allowlist check is too permissive: the patterns end with .*, so a malicious client can append arbitrary regex (including catastrophic-backtracking constructs) and still pass validation (e.g., a safe prefix plus an unsafe suffix). Tighten the validation to only accept the exact expected formats (e.g., ^[A-M].*$ and ^[A-Z]-[A-Z].*$ with the trailing .*$ treated as literals), or parse and validate the character range explicitly before compiling the regex.
| public String decrypt(String toDecrypt) throws Exception { | ||
| if (secretKey == null) return (toDecrypt); | ||
|
|
||
| byte[] encryptedData = Base64.decodeBase64(toDecrypt.getBytes()); | ||
| Cipher cipher = Cipher.getInstance("AES"); | ||
| cipher.init(Cipher.DECRYPT_MODE, secretKey); | ||
| byte[] unencryptedByteArray = cipher.doFinal(encryptedData); | ||
| byte[] combined = Base64.decodeBase64(toDecrypt.getBytes()); | ||
| ByteBuffer buf = ByteBuffer.wrap(combined); | ||
| byte[] iv = new byte[12]; | ||
| buf.get(iv); | ||
| byte[] ciphertext = new byte[buf.remaining()]; | ||
| buf.get(ciphertext); | ||
| Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); | ||
| cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, iv)); | ||
| byte[] unencryptedByteArray = cipher.doFinal(ciphertext); |
There was a problem hiding this comment.
decrypt(String) assumes the Base64-decoded byte array is at least 12 bytes and will throw BufferUnderflowException on malformed/short input. Since these encrypted tokens come from clients (e.g., bookingType/timeSlot ids), add a length check before reading the IV and fail gracefully (e.g., return null/throw a checked exception) rather than relying on a runtime buffer exception.
| String key = CarlosProperties.getInstance().getProperty(SECRET_KEY_ENV_VAR); | ||
| String key = CarlosProperties.getInstance().getProperty(ENCRYPTION_KEY_CONFIG_PROPERTY); | ||
| if (Objects.isNull(key)) { | ||
| logger.error("Secret key not found in environment variables."); |
There was a problem hiding this comment.
The error message in prepareSecretKeySpec() is now misleading: the key is read from CarlosProperties using ENCRYPTION_KEY_CONFIG_PROPERTY, but the log still says "environment variables". Update the message to reference the correct configuration source/property to reduce operational confusion during startup/incident debugging.
| logger.error("Secret key not found in environment variables."); | |
| logger.error("Secret key not found in CarlosProperties for property '{}'.", ENCRYPTION_KEY_CONFIG_PROPERTY); |
|
@cubicai review |
|
🧠 Learnings used✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/main/java/io/github/carlos_emr/carlos/managers/DemographicManagerImpl.java (1)
921-954:⚠️ Potential issue | 🟠 MajorAdd regex validation to these methods to prevent ReDoS attacks.
The methods
getDemographicNumbersByMidwifeNumberAndDemographicLastNameRegex,getDemographicNumbersByNurseNumberAndDemographicLastNameRegex, andgetDemographicNumbersByResidentNumberAndDemographicLastNameRegexaccept alastNameRegexparameter from the JSP form (request.getParameter("regexp")) that is passed directly to the DAO layer without validation. The DAO executes this regex in a raw SQL query:d.last_name REGEXP ?3.Apply the same allowlist validation used in
getDemographicsNameRangeByProvider(line 375):if (!regex.matches("\\^\\[[A-Za-z]-[A-Za-z]\\].*") && !regex.matches("\\^\\[[A-Za-z]\\].*")) { logger.warn("Rejected potentially unsafe regex pattern from client: (pattern omitted for security)"); return new ArrayList<>(); }This prevents malicious regex patterns from reaching the database and causing ReDoS attacks through catastrophic backtracking.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/io/github/carlos_emr/carlos/managers/DemographicManagerImpl.java` around lines 921 - 954, These three methods (getDemographicNumbersByMidwifeNumberAndDemographicLastNameRegex, getDemographicNumbersByNurseNumberAndDemographicLastNameRegex, getDemographicNumbersByResidentNumberAndDemographicLastNameRegex) are passing raw lastNameRegex to the DAO; add the same allowlist validation used in getDemographicsNameRangeByProvider to reject unsafe patterns: before calling demographicExtDao, validate lastNameRegex with the two allowed patterns (e.g. matches("\^\\[[A-Za-z]-[A-Za-z]\\].*") or matches("\^\\[[A-Za-z]\\].*")); if it fails, log a warning via logger.warn("Rejected potentially unsafe regex pattern from client: (pattern omitted for security)") and return an empty List<Integer> to avoid forwarding the regex to the DB. Ensure you reference the parameter lastNameRegex and preserve the existing checkPrivilege call and return types.src/main/java/io/github/carlos_emr/carlos/lab/ca/all/util/LabVersionComparator.java (1)
53-63:⚠️ Potential issue | 🟡 MinorUpdate JavaDoc to match SHA-256 behavior.
isLabDuplicate(...)now hashes with SHA-256, but the method JavaDoc still says MD5 (Line 45). Please align docs with the implementation.📝 Proposed doc fix
- * Checks if the current lab data is a duplicate by comparing its MD5 hash with previous versions. + * Checks if the current lab data is a duplicate by comparing its SHA-256 hash with previous versions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/io/github/carlos_emr/carlos/lab/ca/all/util/LabVersionComparator.java` around lines 53 - 63, The JavaDoc for isLabDuplicate(...) incorrectly states MD5 while the implementation uses SHA-256 (DigestUtils.sha256Hex) on the HL7 body after removeFirstLine; update the method JavaDoc to mention SHA-256 hashing (and that the comparison uses DigestUtils.sha256Hex on the lab body without the first line) so the documentation matches the behavior of isLabDuplicate, currentLabHash, and the IntStream comparison logic.src/main/java/io/github/carlos_emr/carlos/utility/CxfClientUtils.java (1)
88-99:⚠️ Potential issue | 🟠 MajorGuard the new
allow_all_ssl_certificatesread.Every other optional setting in
initialiseFromConfigXml()is defensive. This lookup is not. IfConfigXmlUtils.getPropertyString(...)throws here, the static initializer aborts and laterCxfClientUtilsuse fails instead of falling back toallowAllSsl=false.🛡️ Suggested hardening
- allowAllSsl = Boolean.parseBoolean(ConfigXmlUtils.getPropertyString("misc", "allow_all_ssl_certificates")); + try { + allowAllSsl = Boolean.parseBoolean( + ConfigXmlUtils.getPropertyString("misc", "allow_all_ssl_certificates")); + } catch (Throwable t) { + allowAllSsl = false; + logger.warn("Unable to read misc.allow_all_ssl_certificates; keeping SSL certificate validation enabled.", t); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/io/github/carlos_emr/carlos/utility/CxfClientUtils.java` around lines 88 - 99, The static initialisation in initialiseFromConfigXml() currently calls ConfigXmlUtils.getPropertyString("misc","allow_all_ssl_certificates") directly into allowAllSsl which can throw and abort the static initializer; wrap that lookup in a try/catch so failures default allowAllSsl to false, log the exception via logger.error with context, and only call MiscUtils.setJvmDefaultSSLSocketFactoryAllowAllCertificates() when allowAllSsl is true; ensure the behaviour and final logger.info line still reports the resolved allowAllSsl value so the class can continue to initialize if the config lookup fails.
♻️ Duplicate comments (2)
src/main/java/io/github/carlos_emr/carlos/documentManager/actions/SplitDocument2Action.java (1)
340-344:⚠️ Potential issue | 🟠 MajorHandle failed permission updates instead of ignoring return values.
At Line 341, Line 342, and Line 344, the
Filepermission setters can fail and returnfalse. Ignoring those results can silently leave insecure permissions in place.Suggested fix
private void setFilePermissions(File file) { // owner-only permissions: second arg "true" = ownerOnly - file.setWritable(true, true); - file.setReadable(true, true); + boolean writableSet = file.setWritable(true, true); + boolean readableSet = file.setReadable(true, true); // Document files do not need execute permission - file.setExecutable(false); + boolean executableCleared = file.setExecutable(false); + + if (!writableSet || !readableSet || !executableCleared) { + MiscUtils.getLogger().warn("Failed to fully apply secure file permissions"); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/io/github/carlos_emr/carlos/documentManager/actions/SplitDocument2Action.java` around lines 340 - 344, The File permission setters in SplitDocument2Action (calls to file.setWritable, file.setReadable, file.setExecutable) currently ignore their boolean return values; update the code in SplitDocument2Action.java to check each return value and handle failures (e.g., log an error via the existing logger or throw an IOException) so permission-update failures are not silent; reference the specific calls file.setWritable(true, true), file.setReadable(true, true), and file.setExecutable(false) and ensure you surface or handle any false result with a clear message including the target filename and which permission failed.src/main/java/io/github/carlos_emr/carlos/login/Logout2Action.java (1)
217-225:⚠️ Potential issue | 🟠 MajorSet the
Secureflag on logout deletion cookies (Line 225).
expiredCookieis added withoutsetSecure(...). In HTTPS deployments this leaves a known cookie-security warning unresolved and can make deletion behavior inconsistent for secure cookies.🔧 Proposed fix
Cookie expiredCookie = new Cookie(safeName, ""); // Set maxAge to 0 to delete cookie immediately expiredCookie.setMaxAge(0); // Set path to "/" to ensure cookie is deleted across entire application expiredCookie.setPath("/"); // Mark as HttpOnly to prevent client-side script access expiredCookie.setHttpOnly(true); + // Match transport security for deletion cookie + expiredCookie.setSecure(request.isSecure()); // Add expired cookie to response to overwrite browser's stored cookie response.addCookie(expiredCookie);#!/bin/bash set -euo pipefail target="$(fd -p 'Logout2Action.java' | head -n1)" echo "Reviewing: $target" nl -ba "$target" | sed -n '205,230p' echo echo "Checking cookie hardening calls near addCookie:" rg -n -C3 'new Cookie\(|setHttpOnly\(|setSecure\(|addCookie\(' "$target"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/io/github/carlos_emr/carlos/login/Logout2Action.java` around lines 217 - 225, The expiredCookie created in Logout2Action is missing the Secure flag; update the logout cookie deletion code that constructs expiredCookie (the Cookie instance named expiredCookie) to call expiredCookie.setSecure(true) before response.addCookie(expiredCookie) so the deletion cookie matches secure cookies in HTTPS deployments; ensure this change is applied alongside the existing setHttpOnly(true), setPath("/"), and setMaxAge(0) calls in the same block.
🧹 Nitpick comments (5)
src/main/java/io/github/carlos_emr/carlos/fax/admin/ConfigureFax2Action.java (1)
184-191: Optional: deduplicate masked-password update checks.The same guard appears in multiple places; extracting a small helper would reduce drift risk in future edits.
Refactor sketch
+ private boolean shouldPersistCredential(String value) { + return value != null && !MASKED_CREDENTIAL.equals(value); + } ... - if (sitePasswd != null && !MASKED_CREDENTIAL.equals(sitePasswd)) { + if (shouldPersistCredential(sitePasswd)) { savedFaxConfig.setPasswd(sitePasswd.trim()); } ... - if (faxPasswds != null && idx < faxPasswds.length && faxPasswds[idx] != null && !MASKED_CREDENTIAL.equals(faxPasswds[idx])) { + if (faxPasswds != null && idx < faxPasswds.length && shouldPersistCredential(faxPasswds[idx])) { savedFaxConfig.setFaxPasswd(faxPasswds[idx].trim()); } ... - if (sitePasswd != null && !MASKED_CREDENTIAL.equals(sitePasswd)) { + if (shouldPersistCredential(sitePasswd)) { faxConfig.setPasswd(sitePasswd.trim()); } ... - if (faxPasswds != null && idx < faxPasswds.length && faxPasswds[idx] != null && !MASKED_CREDENTIAL.equals(faxPasswds[idx])) { + if (faxPasswds != null && idx < faxPasswds.length && shouldPersistCredential(faxPasswds[idx])) { faxConfig.setFaxPasswd(faxPasswds[idx].trim()); } ... - if (sitePasswd != null && !MASKED_CREDENTIAL.equals(sitePasswd)) { + if (shouldPersistCredential(sitePasswd)) { faxConfig.setPasswd(sitePasswd.trim()); }Also applies to: 210-218, 257-258
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/io/github/carlos_emr/carlos/fax/admin/ConfigureFax2Action.java` around lines 184 - 191, Duplicate masked-password checks (using MASKED_CREDENTIAL) appear around savedFaxConfig.setPasswd(...) and savedFaxConfig.setFaxPasswd(...); extract a small helper method (e.g., updateIfNotMasked or applyCredentialIfNotMasked) that accepts the raw value and a Consumer<String> or BiConsumer<SavedFaxConfig,String> to trim and call the appropriate setter only when value != null and !MASKED_CREDENTIAL.equals(value); replace the repeated guard blocks around savedFaxConfig.setPasswd, savedFaxConfig.setFaxPasswd (and other similar spots at the noted ranges) with calls to this helper to centralize the logic and avoid duplication.src/main/java/io/github/carlos_emr/carlos/form/FrmRecordFactory.java (1)
127-128: Consider modernizing deprecated reflection API.
Class.newInstance()is deprecated since Java 9 in favor ofclazz.getConstructor().newInstance(), which provides better exception handling. This is a pre-existing issue but worth addressing while you're hardening this code.♻️ Suggested modernization
try { - Class classDefinition = Class.forName(fullName); - myClass = (FrmRecord) classDefinition.newInstance(); - } catch (InstantiationException e) { - MiscUtils.getLogger().debug("debug", e); - } catch (IllegalAccessException e) { - MiscUtils.getLogger().debug("debug", e); - } catch (ClassNotFoundException e) { - MiscUtils.getLogger().debug("debug", e); + Class<?> classDefinition = Class.forName(fullName); + myClass = (FrmRecord) classDefinition.getConstructor().newInstance(); + } catch (Exception e) { + MiscUtils.getLogger().debug("Could not instantiate form record: " + fullName, e); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/io/github/carlos_emr/carlos/form/FrmRecordFactory.java` around lines 127 - 128, Replace the deprecated classDefinition.newInstance() usage in FrmRecordFactory where you call Class.forName(fullName) and cast to FrmRecord; instead obtain the no-arg constructor and invoke it (e.g., classDefinition.getConstructor().newInstance()) and update exception handling to catch ReflectiveOperationException (or the specific checked exceptions like NoSuchMethodException/InvocationTargetException/InstantiationException/IllegalAccessException) so failures are handled correctly when instantiating FrmRecord.src/main/java/io/github/carlos_emr/carlos/lab/ca/all/pageUtil/LabUpload2Action.java (1)
248-248: Consider documenting MD5WithRSA signature algorithm constraint.Similar to the AES and RSA padding comments added in this PR,
MD5WithRSAat line 248 is a weak signature algorithm. If this is also constrained by the external lab protocol, consider adding a comment documenting that constraint for consistency. If not protocol-bound, this could be upgraded toSHA256WithRSAin a future change.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/io/github/carlos_emr/carlos/lab/ca/all/pageUtil/LabUpload2Action.java` at line 248, The Signature.getInstance("MD5WithRSA") usage in LabUpload2Action should be annotated: if the external lab protocol requires MD5WithRSA, add a clear inline comment at the Signature.getInstance(...) call in LabUpload2Action explaining that the weak MD5WithRSA algorithm is mandated by the protocol and cannot be changed; otherwise replace the algorithm with a stronger alternative such as "SHA256WithRSA" (updating any tests/compatibility as needed) and document the change in the same location. Ensure the comment references the Signature.getInstance("MD5WithRSA") invocation so future readers know whether this is a protocol constraint or an area to upgrade.src/main/java/io/github/carlos_emr/carlos/mds/pageUtil/ReportReassign2Action.java (1)
205-208: Harden redirect target validation beyond CR/LF stripping.This blocks header-splitting, but still trusts a user-derived URI as redirect target. Constrain it to an in-app path prefix before composing
newURL.As per coding guidelines `src/main/java/**/{*Action,*Manager,*Service,*Servlet,*Filter}.java`: Use OWASP Encoder for ALL user inputs.🔒 Suggested hardening patch
- String rawUri = request.getRequestURI(); - newURL = rawUri.replaceAll("[\r\n]", ""); + String sanitizedUri = request.getRequestURI() + .replaceAll("[\\r\\n\\u0000-\\u001F\\u007F-\\u009F]", ""); + if (!sanitizedUri.startsWith(request.getContextPath() + "/")) { + throw new SecurityException("Invalid redirect target"); + } + newURL = sanitizedUri;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/io/github/carlos_emr/carlos/mds/pageUtil/ReportReassign2Action.java` around lines 205 - 208, The code currently strips CR/LF but still trusts request.getRequestURI() for newURL; change ReportReassign2Action so you only accept in-app paths (e.g., verify the cleaned rawUri starts with an allowed prefix like "/app/" or other application root) and if not, set newURL to a safe default internal path; after validating the prefix, encode the final path using the OWASP Java Encoder (e.g., Encode.forUriComponent or appropriate Encode method) before assigning to newURL and before using it in the Location/redirect to ensure all user input is safely encoded.src/main/java/io/github/carlos_emr/carlos/integration/ebs/client/ng/EdtClientBuilder.java (1)
355-368: Keep the TLS policy in one place.This now mirrors
CxfClientUtils.configureSsl(...)almost line-for-line. Delegating the full conduit setup here will reduce drift between the two trust-all code paths.♻️ Suggested simplification
public static void configureSsl(HTTPConduit conduit) { - TLSClientParameters tls = conduit.getTlsClientParameters(); - if (tls == null) { - tls = new TLSClientParameters(); - } - boolean allowAllSsl = CxfClientUtils.isAllowAllSsl(); - tls.setDisableCNCheck(allowAllSsl); - if (allowAllSsl) { - MiscUtils.getLogger().warn("EdtClientBuilder.configureSsl: TLS certificate validation disabled " - + "(allow_all_ssl_certificates=true). Do not use this setting in production environments."); - tls.setTrustManagers(new X509TrustManager[]{new TrustAllManager()}); - } - tls.setSecureSocketProtocol("TLS"); - conduit.setTlsClientParameters(tls); + CxfClientUtils.configureSsl(conduit); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/io/github/carlos_emr/carlos/integration/ebs/client/ng/EdtClientBuilder.java` around lines 355 - 368, The TLS setup in EdtClientBuilder.configureSsl duplicates CxfClientUtils.configureSsl; remove the duplicated conduit configuration and delegate to the centralized implementation by calling CxfClientUtils.configureSsl(conduit) from inside EdtClientBuilder.configureSsl so the trust-all behavior and TLS parameters are consistent; ensure any imports are present and that HTTPConduit is passed through unchanged so conduit.setTlsClientParameters and allow-all handling remain governed by CxfClientUtils.configureSsl.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/java/io/github/carlos_emr/carlos/commn/model/HashAudit.java`:
- Line 48: The change to ALGORITHM = "SHA-256" breaks verification because
makeHash() still always uses the constant; update makeHash() in class HashAudit
to honor the instance's persisted algorithm field (e.g., use this.algorithm or
getAlgorithm() when available) and only fall back to the ALGORITHM constant if
the instance value is null/empty, or alternatively perform a migration/backfill
before switching the default; ensure setAlgorithm() and any code creating
HashAudit keep the stored algorithm untouched so existing MD5 rows are verified
with their original algorithm.
In `@src/main/java/io/github/carlos_emr/carlos/utility/EncryptionUtils.java`:
- Around line 158-173: The deprecated decrypt(SecretKey, byte[]) now incorrectly
assumes the new IV-prefixed AES-GCM format; update it to detect whether
encryptedData contains a 12-byte IV (e.g., check length and/or a magic/flag) and
branch accordingly: if IV present, proceed with the existing ByteBuffer
extraction, Cipher.getInstance("AES/GCM/NoPadding") and GCMParameterSpec(128,
iv); if IV is not present (old format), fall back to the previous ECB decryption
path using Cipher.getInstance("AES/ECB/PKCS5Padding") (or the original algorithm
used) and secretKey to decrypt the whole encryptedData. Ensure the method
(decrypt) handles null secretKey as before and throws the same exceptions.
In `@src/main/webapp/eform/fieldNoteReport/fieldnotereportdetail.jsp`:
- Around line 50-52: The Content-Disposition filename creation (variable
filename and the response.setHeader("Content-Disposition", ...)) is too
permissive and vulnerable to parameter injection; instead apply a strict
allowlist to residentName (e.g., only letters, digits, underscore, hyphen),
truncate to a safe max length, then encode/quote the resulting filename using
the OWASP Encoder for filename contexts and set the header with a quoted
filename (e.g., "filename=\"...\"") to prevent delimiter injection; update the
code that builds filename and the response.setHeader call accordingly to use
these rules and OWASP encoding helpers.
---
Outside diff comments:
In
`@src/main/java/io/github/carlos_emr/carlos/lab/ca/all/util/LabVersionComparator.java`:
- Around line 53-63: The JavaDoc for isLabDuplicate(...) incorrectly states MD5
while the implementation uses SHA-256 (DigestUtils.sha256Hex) on the HL7 body
after removeFirstLine; update the method JavaDoc to mention SHA-256 hashing (and
that the comparison uses DigestUtils.sha256Hex on the lab body without the first
line) so the documentation matches the behavior of isLabDuplicate,
currentLabHash, and the IntStream comparison logic.
In
`@src/main/java/io/github/carlos_emr/carlos/managers/DemographicManagerImpl.java`:
- Around line 921-954: These three methods
(getDemographicNumbersByMidwifeNumberAndDemographicLastNameRegex,
getDemographicNumbersByNurseNumberAndDemographicLastNameRegex,
getDemographicNumbersByResidentNumberAndDemographicLastNameRegex) are passing
raw lastNameRegex to the DAO; add the same allowlist validation used in
getDemographicsNameRangeByProvider to reject unsafe patterns: before calling
demographicExtDao, validate lastNameRegex with the two allowed patterns (e.g.
matches("\^\\[[A-Za-z]-[A-Za-z]\\].*") or matches("\^\\[[A-Za-z]\\].*")); if it
fails, log a warning via logger.warn("Rejected potentially unsafe regex pattern
from client: (pattern omitted for security)") and return an empty List<Integer>
to avoid forwarding the regex to the DB. Ensure you reference the parameter
lastNameRegex and preserve the existing checkPrivilege call and return types.
In `@src/main/java/io/github/carlos_emr/carlos/utility/CxfClientUtils.java`:
- Around line 88-99: The static initialisation in initialiseFromConfigXml()
currently calls
ConfigXmlUtils.getPropertyString("misc","allow_all_ssl_certificates") directly
into allowAllSsl which can throw and abort the static initializer; wrap that
lookup in a try/catch so failures default allowAllSsl to false, log the
exception via logger.error with context, and only call
MiscUtils.setJvmDefaultSSLSocketFactoryAllowAllCertificates() when allowAllSsl
is true; ensure the behaviour and final logger.info line still reports the
resolved allowAllSsl value so the class can continue to initialize if the config
lookup fails.
---
Duplicate comments:
In
`@src/main/java/io/github/carlos_emr/carlos/documentManager/actions/SplitDocument2Action.java`:
- Around line 340-344: The File permission setters in SplitDocument2Action
(calls to file.setWritable, file.setReadable, file.setExecutable) currently
ignore their boolean return values; update the code in SplitDocument2Action.java
to check each return value and handle failures (e.g., log an error via the
existing logger or throw an IOException) so permission-update failures are not
silent; reference the specific calls file.setWritable(true, true),
file.setReadable(true, true), and file.setExecutable(false) and ensure you
surface or handle any false result with a clear message including the target
filename and which permission failed.
In `@src/main/java/io/github/carlos_emr/carlos/login/Logout2Action.java`:
- Around line 217-225: The expiredCookie created in Logout2Action is missing the
Secure flag; update the logout cookie deletion code that constructs
expiredCookie (the Cookie instance named expiredCookie) to call
expiredCookie.setSecure(true) before response.addCookie(expiredCookie) so the
deletion cookie matches secure cookies in HTTPS deployments; ensure this change
is applied alongside the existing setHttpOnly(true), setPath("/"), and
setMaxAge(0) calls in the same block.
---
Nitpick comments:
In
`@src/main/java/io/github/carlos_emr/carlos/fax/admin/ConfigureFax2Action.java`:
- Around line 184-191: Duplicate masked-password checks (using
MASKED_CREDENTIAL) appear around savedFaxConfig.setPasswd(...) and
savedFaxConfig.setFaxPasswd(...); extract a small helper method (e.g.,
updateIfNotMasked or applyCredentialIfNotMasked) that accepts the raw value and
a Consumer<String> or BiConsumer<SavedFaxConfig,String> to trim and call the
appropriate setter only when value != null and !MASKED_CREDENTIAL.equals(value);
replace the repeated guard blocks around savedFaxConfig.setPasswd,
savedFaxConfig.setFaxPasswd (and other similar spots at the noted ranges) with
calls to this helper to centralize the logic and avoid duplication.
In `@src/main/java/io/github/carlos_emr/carlos/form/FrmRecordFactory.java`:
- Around line 127-128: Replace the deprecated classDefinition.newInstance()
usage in FrmRecordFactory where you call Class.forName(fullName) and cast to
FrmRecord; instead obtain the no-arg constructor and invoke it (e.g.,
classDefinition.getConstructor().newInstance()) and update exception handling to
catch ReflectiveOperationException (or the specific checked exceptions like
NoSuchMethodException/InvocationTargetException/InstantiationException/IllegalAccessException)
so failures are handled correctly when instantiating FrmRecord.
In
`@src/main/java/io/github/carlos_emr/carlos/integration/ebs/client/ng/EdtClientBuilder.java`:
- Around line 355-368: The TLS setup in EdtClientBuilder.configureSsl duplicates
CxfClientUtils.configureSsl; remove the duplicated conduit configuration and
delegate to the centralized implementation by calling
CxfClientUtils.configureSsl(conduit) from inside EdtClientBuilder.configureSsl
so the trust-all behavior and TLS parameters are consistent; ensure any imports
are present and that HTTPConduit is passed through unchanged so
conduit.setTlsClientParameters and allow-all handling remain governed by
CxfClientUtils.configureSsl.
In
`@src/main/java/io/github/carlos_emr/carlos/lab/ca/all/pageUtil/LabUpload2Action.java`:
- Line 248: The Signature.getInstance("MD5WithRSA") usage in LabUpload2Action
should be annotated: if the external lab protocol requires MD5WithRSA, add a
clear inline comment at the Signature.getInstance(...) call in LabUpload2Action
explaining that the weak MD5WithRSA algorithm is mandated by the protocol and
cannot be changed; otherwise replace the algorithm with a stronger alternative
such as "SHA256WithRSA" (updating any tests/compatibility as needed) and
document the change in the same location. Ensure the comment references the
Signature.getInstance("MD5WithRSA") invocation so future readers know whether
this is a protocol constraint or an area to upgrade.
In
`@src/main/java/io/github/carlos_emr/carlos/mds/pageUtil/ReportReassign2Action.java`:
- Around line 205-208: The code currently strips CR/LF but still trusts
request.getRequestURI() for newURL; change ReportReassign2Action so you only
accept in-app paths (e.g., verify the cleaned rawUri starts with an allowed
prefix like "/app/" or other application root) and if not, set newURL to a safe
default internal path; after validating the prefix, encode the final path using
the OWASP Java Encoder (e.g., Encode.forUriComponent or appropriate Encode
method) before assigning to newURL and before using it in the Location/redirect
to ensure all user input is safely encoded.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3e87f6ea-10e9-4772-8e29-872ac7859746
📒 Files selected for processing (25)
src/main/java/io/github/carlos_emr/carlos/appointment/search/SearchConfig.javasrc/main/java/io/github/carlos_emr/carlos/casemgmt/service/CaseManagementManagerImpl.javasrc/main/java/io/github/carlos_emr/carlos/casemgmt/web/EChartPrint2Action.javasrc/main/java/io/github/carlos_emr/carlos/commn/model/HashAudit.javasrc/main/java/io/github/carlos_emr/carlos/demographic/pageUtil/CihiExportPHC_VRS2Action.javasrc/main/java/io/github/carlos_emr/carlos/demographic/pageUtil/DemographicExportAction42Action.javasrc/main/java/io/github/carlos_emr/carlos/demographic/pageUtil/ImportDemographicDataAction42Action.javasrc/main/java/io/github/carlos_emr/carlos/documentManager/actions/SplitDocument2Action.javasrc/main/java/io/github/carlos_emr/carlos/fax/admin/ConfigureFax2Action.javasrc/main/java/io/github/carlos_emr/carlos/form/FrmRecordFactory.javasrc/main/java/io/github/carlos_emr/carlos/form/graphic/FrmGraphicFactory.javasrc/main/java/io/github/carlos_emr/carlos/integration/ebs/client/ng/EdtClientBuilder.javasrc/main/java/io/github/carlos_emr/carlos/lab/ca/all/pageUtil/DownloadEmbeddedDocumentFromLab2Action.javasrc/main/java/io/github/carlos_emr/carlos/lab/ca/all/pageUtil/LabUpload2Action.javasrc/main/java/io/github/carlos_emr/carlos/lab/ca/all/util/KeyPairGen.javasrc/main/java/io/github/carlos_emr/carlos/lab/ca/all/util/LabVersionComparator.javasrc/main/java/io/github/carlos_emr/carlos/login/Logout2Action.javasrc/main/java/io/github/carlos_emr/carlos/login/Startup.javasrc/main/java/io/github/carlos_emr/carlos/managers/DemographicManagerImpl.javasrc/main/java/io/github/carlos_emr/carlos/mds/pageUtil/ReportReassign2Action.javasrc/main/java/io/github/carlos_emr/carlos/report/pageUtil/RptDownloadCSVServlet.javasrc/main/java/io/github/carlos_emr/carlos/utility/CxfClientUtils.javasrc/main/java/io/github/carlos_emr/carlos/utility/EncryptionUtils.javasrc/main/java/io/github/carlos_emr/carlos/utility/MiscUtils.javasrc/main/webapp/eform/fieldNoteReport/fieldnotereportdetail.jsp
|
|
||
| public final static String NOTE = "enc"; | ||
| public final static String ALGORITHM = "MD5"; | ||
| public final static String ALGORITHM = "SHA-256"; |
There was a problem hiding this comment.
Don't change HashAudit's default digest without a legacy strategy.
Line 48 flips new hashes to SHA-256, but makeHash() on Line 121 still ignores the entity's persisted algorithm field and always uses the constant. Any existing hash_audit row written as MD5—or any caller using setAlgorithm()—will now rehash to SHA-256 while still carrying a different algorithm value, which breaks historical audit verification semantics. Either migrate/backfill old rows before this switch, or make makeHash() honor the stored algorithm until the migration is complete.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/io/github/carlos_emr/carlos/commn/model/HashAudit.java` at line
48, The change to ALGORITHM = "SHA-256" breaks verification because makeHash()
still always uses the constant; update makeHash() in class HashAudit to honor
the instance's persisted algorithm field (e.g., use this.algorithm or
getAlgorithm() when available) and only fall back to the ALGORITHM constant if
the instance value is null/empty, or alternatively perform a migration/backfill
before switching the default; ensure setAlgorithm() and any code creating
HashAudit keep the stored algorithm untouched so existing MD5 rows are verified
with their original algorithm.
| @Deprecated | ||
| public static byte[] decrypt(SecretKey secretKey, byte[] encryptedData) | ||
| throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, | ||
| IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { | ||
| if (secretKey == null) { | ||
| return encryptedData; | ||
| } else { | ||
| Cipher cipher = Cipher.getInstance("AES"); | ||
| cipher.init(2, secretKey); | ||
| return cipher.doFinal(encryptedData); | ||
| } | ||
| ByteBuffer buf = ByteBuffer.wrap(encryptedData); | ||
| byte[] iv = new byte[12]; | ||
| buf.get(iv); | ||
| byte[] ciphertext = new byte[buf.remaining()]; | ||
| buf.get(ciphertext); | ||
| Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); | ||
| cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, iv)); | ||
| return cipher.doFinal(ciphertext); | ||
| } |
There was a problem hiding this comment.
Corresponding breaking change in deprecated decrypt(SecretKey, byte[]).
Same concerns as the encrypt method - this expects the new IV-prefixed format and will fail on data encrypted with the old ECB implementation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/io/github/carlos_emr/carlos/utility/EncryptionUtils.java`
around lines 158 - 173, The deprecated decrypt(SecretKey, byte[]) now
incorrectly assumes the new IV-prefixed AES-GCM format; update it to detect
whether encryptedData contains a 12-byte IV (e.g., check length and/or a
magic/flag) and branch accordingly: if IV present, proceed with the existing
ByteBuffer extraction, Cipher.getInstance("AES/GCM/NoPadding") and
GCMParameterSpec(128, iv); if IV is not present (old format), fall back to the
previous ECB decryption path using Cipher.getInstance("AES/ECB/PKCS5Padding")
(or the original algorithm used) and secretKey to decrypt the whole
encryptedData. Ensure the method (decrypt) handles null secretKey as before and
throws the same exceptions.
|
@claude does this LGTM do thorough review of bot findings and your own assessment of the PR and what else you find / was missed. |
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
|
@claude review all bot reviews what are valid or not why or why not what to do and why give a numbered summary what was missed and needs to be fixed based on your own review |
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
|
@claude |
|
Claude finished @yingbull's task in 39s —— View job Fixing Content-Disposition filename injection in fieldnotereportdetail.jsp
Done. Applied the following changes to
// Before
String filename = residentName.replace(", ", "").replace(" ", "")
.replaceAll("[\r\n\u0000-\u001F\u007F-\u009F]", "") + ".doc";
response.setHeader("Content-Disposition", "attachment; filename=" + filename);
// After
String safeBaseName = residentName.replaceAll("[^A-Za-z0-9_-]", "_");
if (safeBaseName.isEmpty()) safeBaseName = "field-note-report";
if (safeBaseName.length() > 100) safeBaseName = safeBaseName.substring(0, 100);
String filename = safeBaseName + ".doc";
response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");| Branch |
…tdetail.jsp Apply strict [A-Za-z0-9_-] allowlist to residentName, truncate to 100 chars, and quote the filename value to prevent Content-Disposition parameter delimiter injection (header splitting via unquoted filename). Co-authored-by: Michael Yingbull <yingbull@users.noreply.github.com>
|
@claude review all bot reviews what are valid or not why or why not what to do and why give a numbered summary what was missed and needs to be fixed based on your own review |
|
Claude finished @yingbull's task in 2m 17s —— View job PR #1082 — Bot Review Analysis
Bot Review Findings — Validity AssessmentGitHub Advanced Security1. Missing The deletion cookies in GitHub Code Quality2. Ignored return values from All three Copilot3. Regex allowlist ends in The patterns 4. byte[] combined = Base64.decodeBase64(toDecrypt.getBytes());
ByteBuffer buf = ByteBuffer.wrap(combined);
byte[] iv = new byte[12];
buf.get(iv); // throws BufferUnderflowException if combined.length < 12These tokens come from clients (URL params). Malformed/short input throws 5. Stale log message in
CodeRabbit6. The PR changed 7. Deprecated The deprecated method was changed from AES/ECB to AES/GCM with IV-prefix parsing. Any data encrypted with the old ECB method (pre-PR) and stored in the DB cannot now be decrypted — the method will crash trying to read a 12-byte IV from ECB ciphertext that has none. The method is deprecated, but deprecated means "don't use going forward," not "all existing stored data was migrated." Fix: detect old format (length check + ECB fallback path) or ensure all callers have migrated before this change lands. 8. Three DemographicManagerImpl methods pass raw
if (!lastNameRegex.matches("\\^\\[[A-Za-z]-[A-Za-z]\\].*") && !lastNameRegex.matches("\\^\\[[A-Za-z]\\].*")) {
logger.warn("Rejected potentially unsafe regex pattern from client: (pattern omitted for security)");
return new ArrayList<>();
}9. Every other config lookup (lines 65–86) is wrapped in allowAllSsl = Boolean.parseBoolean(ConfigXmlUtils.getPropertyString("misc", "allow_all_ssl_certificates"));If 10. JavaDoc for Nitpick Findings (Low Priority — Not Required)
Summary: What Needs to Be Fixed
Issues #1, #6, #7, #8, #9 can be fixed directly. Issues #4 and #5 require care around backward compatibility with existing stored data — confirm migration strategy before merging. |
|
Fix 1, 6, 7, 8, 9 |
|
@claude Fix 1, 6, 7, 8, 9 |
|
Claude finished @yingbull's task in 5m 16s —— View job • Fixing issues 1, 6, 7, 8, 9
Summary of changes
|
- Logout2Action: add setSecure(request.isSecure()) to deletion cookies so they override Secure-flagged cookies in HTTPS deployments (#1) - DemographicManagerImpl: add isSafeLastNameRegex() allowlist guard to the three midwife/nurse/resident regex methods to prevent ReDoS via the DB REGEXP operator (#6) - CxfClientUtils: wrap allow_all_ssl_certificates config read in try/catch so a lookup failure defaults to allowAllSsl=false instead of aborting the static init (#7) - EncryptionUtils: fix stale "environment variables" log/exception messages to reference CarlosProperties and the property name SECRET_KEY_ENV_VAR (#8) - LabVersionComparator: migrate isLabDuplicate() from MD5 to SHA-256 and update the JavaDoc to match (#9) Co-authored-by: Michael Yingbull <yingbull@users.noreply.github.com>
Summary by CodeRabbit
Release Notes
Bug Fixes
Security Improvements