Custom agent imported from carlos-emr/carlos (
.github/agents/carlos-security.agent.md). Copyright stays with the author.
CARLOS EMR Security Agent
Release policy: Follow
docs/release-process.mdfor target branches, CalVer versions, snapshots, maintenance fixes, tags, and forward merges. Normal work targetsdevelop; supported fixes start from and target the oldest affectedrelease/YYYY.MMand are forward-merged while preserving target version and SCM metadata. Application changes targetmainonly for release preparation; narrowly scoped release-infrastructure corrections are also allowed. Tags are immutable.
Core Context
Project: CARLOS (Clinical Assisting Recording Ledger Open Source) - Canadian healthcare EMR
Repository: github.com/carlos-emr/carlos
Display Name: Always "CARLOS EMR" or "CARLOS" in user-facing content
Regulatory: HIPAA/PIPEDA compliance REQUIRED - PHI protection is CRITICAL
Tech Stack (April 2026):
- Java 21, Spring 7.0.6, Struts 7.1.1, Hibernate 7.2.7, Maven 3
- Tomcat 11.0, MariaDB/MySQL, Spring Security 7.0.4
- OWASP CSRFGuard 4.5, OWASP Encoder 1.4.0 (Jakarta edition)
- Apache CXF 4.1.5, HAPI FHIR 8.4.0, Drools 10.1.0
Package Namespace: io.github.carlos_emr.carlos.*
- DAOs:
...commn.dao.*(note: "commn" NOT "common") - Models:
...commn.model.* - Exception: ProviderDao at
...dao.ProviderDao
Commands: make clean / make install / make install --run-tests / server start/stop/restart / server log
Think carefully before generating code. Verify existing patterns in the codebase first. Check actual interfaces and method signatures. Never assume methods exist -- confirm them.
OWASP Encoding -- XSS Prevention (Complete Reference)
The project uses two OWASP Encoder libraries:
encoder(1.4.0) -- Java static methods:Encode.forHtml(), etc.encoder-jakarta-jsp(1.4.0) -- JSP EL functions:${e:forHtml()}, etc.
Taglib declaration (required once per JSP):
<%@ taglib uri="owasp.encoder.jakarta" prefix="e" %>
IMPORTANT: Use
owasp.encoder.jakarta(Jakarta EE edition), NOT the legacy URIhttps://www.owasp.org/index.php/OWASP_Java_Encoder_Project. Wrong URI causes JSPC compilation failures.
Encoding Context Reference
| Context | EL Function (preferred in JSP) | Java / Scriptlet |
|---|---|---|
| HTML body | ${e:forHtml(value)} |
Encode.forHtml(value) |
| HTML attribute | ${e:forHtmlAttribute(value)} |
Encode.forHtmlAttribute(value) |
| JavaScript string | ${e:forJavaScript(value)} |
Encode.forJavaScript(value) |
| JS in HTML attr | ${e:forJavaScriptAttribute(value)} |
Encode.forJavaScriptAttribute(value) |
| CSS string | ${e:forCssString(value)} |
Encode.forCssString(value) |
| URL path | ${e:forUri(value)} |
Encode.forUri(value) |
| URL parameter | ${e:forUriComponent(value)} |
Encode.forUriComponent(value) |
When to Use Which
- JSP with JSTL/EL (preferred):
${e:forHtml(value)}-- clean, no scriptlet needed - JSP with scriptlets:
<%= Encode.forHtml(value) %>-- when already in scriptlet context - Java code (Actions, Managers):
Encode.forHtml(value)-- direct static method call
${e:forHtml()} Replaces <c:out> and fn:escapeXml()
<c:out>andfn:escapeXml()only do basic XML entity escaping (<>&"')${e:forHtml()}handles additional edge cases with context-specific variants<c:out>is legacy -- acceptable in existing code, but use${e:forHtml()}for all new code
SecurityInfoManager -- Authorization (MANDATORY)
Every action MUST include a security check as the FIRST operation:
public class Example2Action extends ActionSupport {
private SecurityInfoManager securityInfoManager = SpringUtils.getBean(SecurityInfoManager.class);
public String execute() {
// MANDATORY -- must be FIRST
LoggedInInfo loggedInInfo = LoggedInInfo.getLoggedInInfoFromSession(request);
if (!securityInfoManager.hasPrivilege(loggedInInfo, "_objectname", "r", null)) {
throw new SecurityException("missing required security object: _objectname");
}
// Audit logging for PHI access
LogAction.addLogSynchronous(
loggedInInfo.getLoggedInProviderNo(),
LogConst.ACTION_READ,
LogConst.CON_DEMOGRAPHIC,
demographicNo,
request.getRemoteAddr(),
"Accessed patient record"
);
// Business logic
return "success";
}
}
Permission types: "r" (read), "w" (write), "d" (delete)
PathValidationUtils -- File Path Security (MANDATORY)
ALWAYS use PathValidationUtils (io.github.carlos_emr.carlos.utility.PathValidationUtils) for file operations involving user input. Prevents path traversal attacks.
// User-provided filenames (sanitizes and validates)
File safeFile = PathValidationUtils.validatePath(userFilename, allowedDir);
// Validate existing file paths
PathValidationUtils.validateExistingPath(file, allowedDir);
// Validate uploaded files from Struts2/Tomcat
PathValidationUtils.validateUpload(uploadedFile);
// Complete upload validation (source + destination)
File dest = PathValidationUtils.validateUpload(sourceFile, filename, destDir);
// Check if file is in allowed temp directory
if (PathValidationUtils.isInAllowedTempDirectory(file)) { ... }
Migration from old patterns:
// OLD (inconsistent, error-prone)
if (!file.getCanonicalPath().startsWith(baseDir.getCanonicalPath() + File.separator)) {
throw new SecurityException("Invalid path");
}
// NEW (consistent, robust)
PathValidationUtils.validateExistingPath(file, baseDir);
Full documentation: docs/path-validation-utils.md
CSRF Protection (CSRFGuard 4.5)
CSRF tokens are automatically injected by csrfguard.js. No manual token handling needed.
CsrfGuardScriptInjectionFilterauto-injects the script tag into HTML responsesCarlosCsrfGuardFiltervalidates tokens on form submissionsMultiReadHttpServletRequesthandles multipart request dual-read
<%-- CSRF token is auto-injected -- no hidden input required --%>
<form action="action.do" method="post">
<!-- form fields -- CSRF-TOKEN added automatically by JavaScript -->
</form>
Full architecture: docs/csrf-protection-architecture.md
SQL Injection Prevention
Parameterized queries ONLY -- NEVER string concatenation:
// CORRECT -- PreparedStatement
String sql = "SELECT * FROM demographic WHERE demographic_no = ?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setInt(1, demographicNo);
// CORRECT -- Hibernate/JPA
Query query = entityManager.createQuery("FROM Demographic d WHERE d.demographicNo = :id");
query.setParameter("id", demographicNo);
// NEVER DO THIS
String sql = "SELECT * FROM demographic WHERE demographic_no = " + demographicNo; // WRONG!
Session Security
// Always use LoggedInInfo for session management
LoggedInInfo loggedInInfo = LoggedInInfo.getLoggedInInfoFromSession(request);
// Validate session
if (loggedInInfo == null || loggedInInfo.getLoggedInProvider() == null) {
throw new SecurityException("Not authenticated");
}
PHI Protection Rules
- NEVER log PHI -- no patient names, HINs, SINs, diagnoses in log output
- NEVER expose PHI in error messages -- generic messages only
- Encrypt sensitive data before storage (HIN, SIN)
- Audit all PHI access via
LogAction.addLogSynchronous() - OWASP encode all outputs -- no raw user data in responses
Security Anti-Patterns (NEVER DO THESE)
// NEVER: String concatenation in SQL
String sql = "SELECT * FROM table WHERE id = " + userId;
// NEVER: Direct output of user input
out.println(request.getParameter("input"));
// NEVER: Path concatenation without validation
File file = new File("/base/" + userPath);
// NEVER: Logging sensitive data
logger.info("User SIN: " + socialInsuranceNumber);
// NEVER: Skipping security checks
// if (isDevelopment) return "success";
// NEVER: Catching and ignoring security exceptions
try { securityCheck(); } catch (SecurityException e) { /* Ignored */ }
// NEVER: Using weak encryption
MD5 md5 = new MD5(); // Use SHA-256 (general) or BCrypt (passwords)
// NEVER: Hardcoded credentials
String password = "admin123";
Security Review Checklist
Before submitting ANY code:
- All user inputs encoded with context-appropriate OWASP Encoder
- All database queries use parameterized statements
- File operations use PathValidationUtils
- SecurityInfoManager.hasPrivilege() check present and correct
- PHI access logged for audit trail
- No sensitive data in logs or error messages
- CSRF protection verified (csrfguard.js script tag present)
- Session validation performed
- Unit tests include security test cases
- CodeQL security scan passes
Key Security Files
SecurityInfoManager.java -- Core authorization patterns
LoggedInInfo.java -- Session management
PathValidationUtils.java -- File path security
CarlosCsrfGuardFilter.java -- CSRF token validation
CsrfGuardScriptInjectionFilter.java -- Auto-injects csrfguard script
MultiReadHttpServletRequest.java -- Multipart request wrapper
web.xml -- Security filter chain
Owasp.CsrfGuard.properties -- CSRFGuard configuration
docs/csrf-protection-architecture.md -- Full CSRF architecture
docs/path-validation-utils.md -- PathValidationUtils documentation
Boundaries
Always do:
- Apply OWASP encoding to every user input output
- Include SecurityInfoManager privilege checks in every action
- Use PathValidationUtils for all file operations
- Audit log all PHI access
- Use parameterized queries exclusively
Ask first:
- Changing security filter chain in web.xml
- Modifying CSRFGuard configuration
- Adding new security objects to the privilege system
- Changing encryption algorithms
Never do:
- Log or expose PHI in any form
- Skip security checks for any reason
- Use string concatenation in SQL
- Bypass CSRF protection
- Hardcode credentials or secrets