Meridian PMS
Security Assessment
Executive Summary
Meridian PMS is a cloud-hosted healthcare patient management system built on AWS infrastructure using a microservices architecture. The platform enables healthcare organizations to manage patient records, appointments, medications, and telehealth sessions through a publicly accessible web portal. Given its handling of Protected Health Information (PHI) and Personally Identifiable Information (PII), the system falls under HIPAA regulatory requirements and represents a high-value target for threat actors.
This threat model assesses the security posture of Meridian PMS's core components: Amazon Cognito authentication, JWT session management, MariaDB patient data storage, S3 file storage, real-time WebRTC/Socket.IO communications, and third-party API integrations. The analysis identifies 15 prioritized threats across authentication bypass, data exposure, API abuse, and infrastructure compromise categories.
The current architecture has significant gaps in JWT validation hardening, S3 access controls, WebRTC TURN server authentication, and audit logging completeness. Eight high-risk threats require immediate remediation to achieve acceptable HIPAA compliance posture. The third-party integration with ScriptGuard introduces supply chain risk that is not adequately addressed by current controls.
Architecture Diagram
Apache/EC2] NodeAPI[Node.js API
Express.js] Lambda[AWS Lambda
Serverless Functions] WebRTC[WebRTC Gateway
Telehealth Video] SocketIO[Socket.IO Server
Real-time Messaging] end subgraph TrustBoundary3["Authentication"] Cognito[Amazon Cognito
User Pools + 2FA] JWT[JWT Token Service] end subgraph TrustBoundary4["Data Tier"] MariaDB[(MariaDB
Patient Records)] S3[(AWS S3
File Storage)] end subgraph TrustBoundary5["Monitoring"] Prometheus[Prometheus] ELK[ELK Stack] end User --> WAF Patient --> WAF WAF --> ALB ALB --> React React --> NodeAPI NodeAPI --> Cognito Cognito --> JWT NodeAPI --> MariaDB NodeAPI --> S3 NodeAPI --> Lambda NodeAPI --> WebRTC NodeAPI --> SocketIO NodeAPI --> ExtAPI NodeAPI --> Prometheus NodeAPI --> ELK Lambda --> MariaDB Lambda --> S3
Assets Inventory
| Asset | Description | Classification | Owner | Location |
|---|---|---|---|---|
| Patient Records | Name, DOB, SSN, address, medical history | PHI/PII Critical | Data Team | MariaDB |
| Medical Documents | Lab results, imaging, prescriptions | PHI Critical | Data Team | S3 |
| User Credentials | Passwords, MFA secrets, session tokens | Sensitive | Security Team | Cognito |
| Appointment Data | Scheduling, provider assignments | PHI High | Application Team | MariaDB |
| Medication Records | Prescriptions, dosages, refill history | PHI Critical | Data Team | MariaDB |
| Telehealth Sessions | Video streams, chat transcripts | PHI Critical | Application Team | WebRTC/S3 |
| Audit Logs | Access logs, authentication events | Sensitive | Security Team | ELK Stack |
| API Keys | Third-party integration credentials | Sensitive | DevOps Team | AWS Secrets Manager |
| JWT Signing Keys | Token signing/verification keys | Critical | Security Team | Cognito/EC2 |
Risk Scoring Methodology
Risk ratings follow the OWASP Risk Rating Methodology: Risk = Likelihood x Impact.
Likelihood considers exploitability (skill required, tooling availability, public knowledge of the attack vector) and exposure (whether the component is internet-facing, requires authentication, or sits behind existing controls).
Impact considers the scope of data at risk (PHI record count, data sensitivity classification), HIPAA regulatory exposure (specific §164 subsections violated), and service availability consequences.
High Risk
Exploitable by an unauthenticated or low-skill attacker against an internet-facing component, OR directly exposes PHI/PII, OR bypasses a primary security control (authentication, authorization, encryption).
Medium Risk
Requires authenticated access or specific preconditions to exploit, AND exposes system metadata or operational data rather than PHI directly, OR degrades a defence-in-depth layer without fully bypassing it.
Threat Register
| ID | Category | Threat | Component | Risk | Status |
|---|---|---|---|---|---|
| AUTH-01 | Spoofing | JWT algorithm confusion—attacker substitutes RS256 with HS256 using public key as secret | JWT Authentication | High | Not Addressed |
| AUTH-02 | Spoofing | Cognito user enumeration via differential response timing on login attempts | Amazon Cognito | Medium | Not Addressed |
| AUTH-03 | Elevation of Privilege | JWT token theft via XSS allows session hijacking with full user privileges | Frontend/JWT | High | Partial |
| AUTH-04 | Repudiation | Insufficient logging of authentication failures prevents breach detection | ELK Stack | Medium | Not Addressed |
| DATA-01 | Info Disclosure | S3 bucket misconfiguration exposes patient documents publicly | S3 Storage | High | Not Addressed |
| DATA-02 | Info Disclosure | MariaDB connection strings hardcoded in application code or environment variables | Node.js API | High | Not Addressed |
| DATA-03 | Tampering | SQL injection in patient search endpoint modifies or extracts PHI | MariaDB/API | High | Not Addressed |
| API-01 | Elevation of Privilege | IDOR vulnerability allows patients to access other patients' records via predictable IDs | Patient API | High | Not Addressed |
| API-02 | DoS | Lack of rate limiting enables API abuse and resource exhaustion | Node.js API | Medium | Not Addressed |
| API-03 | Spoofing | ScriptGuard API key compromise enables unauthorized medication data access | Third-party Integration | High | Not Addressed |
| RTC-01 | Info Disclosure | WebRTC TURN server lacks authentication, allowing unauthorized relay usage | WebRTC Gateway | High | Not Addressed |
| RTC-02 | Info Disclosure | Socket.IO connections lack origin validation, enabling cross-site hijacking | Socket.IO Server | Medium | Not Addressed |
| INFRA-01 | Elevation of Privilege | Lambda function IAM roles overly permissive, enabling lateral movement | AWS Lambda | Medium | Not Addressed |
| INFRA-02 | Info Disclosure | Prometheus metrics endpoint exposed without authentication leaks system info | Prometheus | Medium | Not Addressed |
| INFRA-03 | Tampering | GitHub Actions workflow injection via malicious PR modifies deployment | CI/CD Pipeline | High | Not Addressed |
Mitigations
Pin JWT Algorithm in Verification
Recommendation
Explicitly restrict accepted algorithms during token verification. Never allow the algorithm to be derived from the token header alone.
const jwt = require('jsonwebtoken');
const verifyToken = (token) => {
return jwt.verify(token, publicKey, {
algorithms: ['RS256'], // Explicitly pin algorithm
issuer: 'https://cognito-idp.us-east-1.amazonaws.com/YOUR_POOL_ID',
audience: 'YOUR_CLIENT_ID'
});
};
Additional Controls
- Rotate signing keys quarterly via Cognito key rotation
- Monitor for tokens with unexpected algorithm headers in ELK
Implement Secure Token Storage
Recommendation
Store tokens in HttpOnly cookies instead of localStorage. Implement Content Security Policy headers.
// Express.js cookie configuration
res.cookie('accessToken', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 3600000 // 1 hour
});
CSP Header Configuration
Content-Security-Policy: default-src 'self'; script-src 'self'; connect-src 'self' wss://your-domain.com
Enforce S3 Private Access
Recommendation
Enable S3 Block Public Access at account level and enforce secure transport via bucket policy.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyInsecureTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::meridian-pms-patient-docs",
"arn:aws:s3:::meridian-pms-patient-docs/*"
],
"Condition": {
"Bool": { "aws:SecureTransport": "false" }
}
}
]
}
Implement Parameterized Queries
Recommendation
Use parameterized queries exclusively. Enable MariaDB query logging for injection attempt detection.
// Using mysql2 with prepared statements
const searchPatients = async (searchTerm) => {
const [rows] = await pool.execute(
'SELECT id, name, dob FROM patients WHERE name LIKE ? OR mrn = ?',
[`%${searchTerm}%`, searchTerm]
);
return rows;
};
Input Validation Layer
const Joi = require('joi');
const patientSearchSchema = Joi.object({
query: Joi.string().max(100).pattern(/^[a-zA-Z0-9\s\-]+$/).required()
});
Implement Authorization Checks for IDOR Prevention
Recommendation
Validate resource ownership on every request. Implement middleware that verifies the authenticated user has access to the requested patient record.
const authorizePatientAccess = async (req, res, next) => {
const requestedPatientId = req.params.patientId;
const userId = req.user.sub; // From JWT
const hasAccess = await checkPatientAccess(userId, requestedPatientId);
if (!hasAccess) {
logger.warn('IDOR attempt', { userId, requestedPatientId, ip: req.ip });
return res.status(403).json({ error: 'Access denied' });
}
next();
};
app.get('/api/patients/:patientId', authorizePatientAccess, getPatientHandler);
Secure Third-Party API Integration
Recommendation
Store API keys in AWS Secrets Manager with rotation. Implement request signing and IP allowlisting.
// Rotate keys and use short-lived tokens where supported
const getScriptGuardClient = async () => {
const apiKey = await getSecretValue('meridian-pms/scriptguard/apikey');
return axios.create({
baseURL: 'https://api.scriptguard.io/v2',
headers: {
'X-API-Key': apiKey,
'X-Request-ID': crypto.randomUUID()
},
timeout: 5000
});
};
Network Controls
- Configure VPC endpoints for outbound API calls
- Implement egress filtering to allow only ScriptGuard IP ranges
- Log all third-party API requests to ELK
Secure WebRTC TURN Server
Recommendation
Implement time-limited TURN credentials generated per session.
const generateTurnCredentials = (userId) => {
const timestamp = Math.floor(Date.now() / 1000) + 3600; // 1 hour validity
const username = `${timestamp}:${userId}`;
const credential = crypto
.createHmac('sha1', TURN_SECRET)
.update(username)
.digest('base64');
return {
urls: ['turn:turn.meridian-pms.com:443?transport=tcp'],
username,
credential
};
};
Secure GitHub Actions Workflows
Recommendation
Require approval for workflows from first-time contributors. Pin action versions to SHA.
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
permissions:
contents: read
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # Requires approval
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
with:
role-to-assume: arn:aws:iam::ACCOUNT:role/github-deploy
aws-region: us-east-1
Mitigate Cognito User Enumeration
Recommendation
Enable Cognito's PREVENT_USER_EXISTENCE_ERRORS setting.
aws cognito-idp update-user-pool-client \
--user-pool-id us-east-1_XXXXX \
--client-id YOUR_CLIENT_ID \
--prevent-user-existence-errors ENABLED
Additional Controls
- Constant-time comparison in custom auth Lambda triggers
- Rate limit login attempts per source IP via WAF rules
Implement Comprehensive Authentication Logging
Recommendation
Log all authentication events with structured fields for correlation. Ship to ELK with alerting.
const logAuthEvent = (event, req) => {
logger.info({
type: 'AUTH_EVENT',
action: event.action,
userId: event.userId || 'unknown',
sourceIp: req.ip,
userAgent: req.headers['user-agent'],
timestamp: new Date().toISOString(),
failureReason: event.reason || null
});
};
Alerting Rules
- >5 failed logins from single IP within 10 mins
- Successful login from new geographic region
- Retain auth logs minimum 6 years per HIPAA §164.312(b)
Implement API Rate Limiting
Recommendation
Apply rate limits at both API Gateway and application layers using sliding window counters.
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req) => req.user?.sub || req.ip,
handler: (req, res) => {
logger.warn('Rate limit exceeded', { userId: req.user?.sub, ip: req.ip });
res.status(429).json({ error: 'Too many requests' });
}
});
app.use('/api/', apiLimiter);
Additional Controls
- AWS WAF rate-based rules at 2,000 req/5 min per IP
- Stricter limits on /api/patients/search at 20 req/min
Validate Socket.IO Connection Origins
Recommendation
Restrict Socket.IO connections to trusted origins and require JWT authentication on handshake.
const io = require('socket.io')(server, {
cors: {
origin: ['https://meridian-pms.com'],
methods: ['GET', 'POST'],
credentials: true
}
});
io.use((socket, next) => {
const token = socket.handshake.auth.token;
try {
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
socket.user = decoded;
next();
} catch (err) {
logger.warn('Socket.IO auth failed', { ip: socket.handshake.address, error: err.message });
next(new Error('Authentication required'));
}
});
Apply Least-Privilege Lambda IAM Roles
Recommendation
Create per-function IAM roles scoped to minimum required resources. Avoid wildcard permissions.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::meridian-pms-patient-docs/lab-results/*"
},
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:us-east-1:ACCOUNT:secret:meridian-pms/mariadb/prod-*"
}
]
}
Additional Controls
- Use IAM Access Analyzer to identify unused permissions
- Enable CloudTrail for Lambda invocations
- Review IAM policies quarterly
Secure Prometheus Metrics Endpoint
Recommendation
Place Prometheus behind an authentication proxy and restrict network access to monitoring VPC subnet.
server {
listen 9090 ssl;
server_name prometheus.internal.meridian-pms.com;
ssl_certificate /etc/ssl/certs/prometheus.crt;
ssl_certificate_key /etc/ssl/private/prometheus.key;
location / {
auth_basic "Prometheus";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:9091;
}
}
Network Controls
- Bind Prometheus to localhost (127.0.0.1:9091)
- Restrict security group ingress to monitoring subnet CIDR only
- Scrub sensitive labels from exported metrics using metric_relabel_configs
HIPAA Security Rule Mapping
| HIPAA Requirement | §164 Reference | Current State | Gap |
|---|---|---|---|
| Access Control | §164.312(a)(1) | Cognito + JWT implemented | IDOR vulnerability (API-01) undermines access control |
| Audit Controls | §164.312(b) | ELK Stack deployed | Insufficient auth failure logging (AUTH-04) |
| Integrity Controls | §164.312(c)(1) | TLS in transit stated | SQL injection risk (DATA-03) threatens integrity |
| Transmission Security | §164.312(e)(1) | TLS/SSL configured | WebRTC TURN lacks auth (RTC-01) |
| Person Authentication | §164.312(d) | 2FA via Cognito | JWT algorithm confusion (AUTH-01) bypasses auth |
| Encryption | §164.312(a)(2)(iv) | At-rest encryption stated | S3 bucket policy gaps (DATA-01) |
| Contingency Plan | §164.308(a)(7) | Not documented | Backup/recovery procedures needed |
| Security Incident | §164.308(a)(6) | Prometheus monitoring | No incident response playbook documented |
Priority HIPAA Remediation Actions
Risk Summary
Multiple unaddressed vulnerabilities in authentication and data protection create significant breach risk and HIPAA compliance gaps.
References
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | December 19, 2025 | Security Architecture Team | Initial threat model |