Engineering review with 23 findings across security, reliability, and API design. Includes severity ratings, affected files, and remediation roadmap.
Date: February 16, 2026
Repo: wbtw-repositories/diagnostic.ly-nodejs
Prepared for: Engineering Team
A comprehensive review of the diagnostic.ly-nodejs codebase revealed 23 findings across 6 categories. These range from security concerns that should be addressed immediately to API design inconsistencies that create friction for integrating partners.
| Severity | Count | Description |
|---|---|---|
| CRITICAL | 3 | Security vulnerabilities that need immediate attention |
| HIGH | 6 | Issues that affect reliability, data integrity, or partner experience |
| MEDIUM | 8 | Design inconsistencies that increase integration friction |
| LOW | 6 | Quality-of-life improvements and technical debt |
Severity: CRITICAL
Files: src/routes/addressVerify.route.ts, src/routes/spot.route.ts, src/routes/channelPartners.route.ts
Several endpoints that perform write operations have no authentication middleware:
| Endpoint | Issue |
|---|---|
PUT /api/address-verify/orders | Updates order address – no auth AND no schema validation |
POST /api/spot/create/order | Creates orders via SpotDx – no auth |
PUT /api/spot/update/order | Updates order status – no auth |
PUT /api/spot/get/order-status | Retrieves/syncs order data – no auth |
PUT /api/channel-partner/user/:id | Updates channel partner user – auth is commented out |
POST /api/hash/decode | Decodes hashed IDs – no auth |
Risk: Any external actor who discovers these endpoints can modify order addresses, create orders, update order statuses, or decode internal IDs without credentials.
Recommendation:
validateAccount or validateAccountV2 middleware to all write endpointsPUT /api/address-verify/ordersSeverity: CRITICAL
Files: src/model/Users.ts, src/constants/user.ts
User PII fields (email, name, last_name, phone_number, address, address2, city, state, country, postal, country_code, password, username) are AES-encrypted in MySQL using 'provisioning' as the encryption key.
Risk: If this key is hardcoded in the codebase or stored in a plain environment variable, anyone with database access or source code access can decrypt all patient PII. This is a HIPAA compliance concern for a healthcare platform.
Recommendation:
.env fileSeverity: CRITICAL
Files: src/app.ts (Express configuration)
The API has a 50MB max payload size and no rate limiting on any endpoint. There is no evidence of rate limiting middleware (e.g., express-rate-limit) in the codebase.
Risk: The API is susceptible to:
Recommendation:
POST /api/order – 10/minute, GET /api/test-results – 30/minute)429 Too Many Requests with Retry-After headerSeverity: HIGH
Files: src/middlewares/auth.ts
Every authentication and authorization failure returns 400 ClientError:
Impact: Integrators cannot programmatically distinguish between “your request body is malformed” and “your credentials are wrong.” This makes automated error handling and monitoring difficult for partner systems.
Recommendation:
401 Unauthorized403 Forbidden403 Forbidden400 for actual request validation errorsSeverity: HIGH
Files: Cron configuration, src/controller/webhook.controller.ts
Order status update webhooks are dispatched by a cron job that runs every 12 hours. For a healthcare diagnostics platform where timely status updates drive clinical workflows, this is a significant delay.
Example scenario: A specimen arrives at the lab at 8:00 AM. The partner lab’s system won’t be notified until the next cron run, potentially 12 hours later. This delays specimen processing and result turnaround.
Recommendation:
POST /api/order/:id/webhook/retry endpoint for partners to request an immediate re-sendSeverity: HIGH
Files: src/controller/webhook.controller.ts, src/controller/testResults.controller.ts
Failed outbound webhooks are logged in webhook_logs with webhook_failed = 1, but there is no automatic retry mechanism. If a partner’s endpoint is temporarily down, they miss the update permanently.
The Crelio inbound webhook has a basic reprocessing mechanism (20-minute window), but outbound partner webhooks do not.
Recommendation:
max_retries configuration (e.g., 5 attempts) per support entitySeverity: HIGH
Files: src/controller/testResults.controller.ts
The test results outbound webhook sets enable_webhook_notification = 0 (marking results as sent) immediately after receiving an HTTP 200 from the partner. However:
Recommendation:
Severity: HIGH
Files: src/controller/users.controller.ts
GET /api/user returns all users for an account with no pagination. For accounts with thousands of patients, this will:
Recommendation:
page and limit query parameters (consistent with test results endpoint)pagination object in the response (total, page, limit, totalPages)Severity: HIGH
Files: src/controller/orders.controller.ts (134KB)
The V1 order creation controller is a 134KB monolithic file that:
DB pool instead of DBTx connection)While the route currently points to V2 (createOrderV2), the V1 code is still importable and could be accidentally referenced.
Recommendation:
Severity: MEDIUM
The API returns different response structures depending on the endpoint:
| Endpoint | Response Shape |
|---|---|
| Orders | { "statusCode": 200, "message": "...", "data": {...} } |
| Users | { "message": "...", "data": {...} } (no statusCode) |
| Test Results | { "status": "success", "statusCode": 200, "message": "...", "data": [...] } |
| Webhooks | { "success": true, "message": "..." } |
| Address Verify | { "message": "...", "verifiedAddress": {...} } |
Impact: Partners must write different response parsing logic for each endpoint. This increases integration time and error-handling complexity.
Recommendation: Standardize all responses to a single envelope:
{
"status": "success" | "error",
"statusCode": 200,
"message": "...",
"data": { ... },
"pagination": { ... } // when applicable
}
Severity: MEDIUM
Files: src/validations/user.ts, src/validations/order.ts
| Context | Accepted Values |
|---|---|
User creation (user_type: 1) | "Male", "Female" |
Order ship_to.gender | "Male", "Female", "Others" (case-insensitive) |
A patient registered with the Users API cannot have a gender of "Others", but the same patient as an order recipient can. This creates data inconsistency.
Recommendation: Align gender values across all endpoints. For a healthcare platform, consider adopting HL7 FHIR administrative gender codes: male, female, other, unknown.
Severity: MEDIUM
Files: src/validations/order.ts, src/constants/common.ts
| Context | Pattern | Example Match |
|---|---|---|
Orders (ship_to.phone) | ^\+\d{10,15}$ | +12125551234 (11-16 digits total) |
| Users/Fax | ^\+\d{1,3}\d{8,10}$ | +12125551234 (10-14 digits total) |
A phone number valid under one pattern may be rejected by the other. This causes confusion when partners create a user and then place an order for the same person with the same phone number.
Recommendation: Standardize on a single phone validation pattern across all endpoints. Consider using a phone validation library (e.g., libphonenumber) for proper international number validation.
order_Id Response Field Uses Inconsistent CasingSeverity: MEDIUM
Files: src/helpers/createOrder.ts
The order creation response returns order_Id (capital I) while every other field and endpoint uses snake_case (order_id, account_id, user_id).
Impact: Integrators who auto-map snake_case fields will miss this, causing silent bugs.
Recommendation: Change to order_id for consistency. This is a breaking change, so it should be communicated to existing partners.
declined Status Uses Lowercase While All Others Use Title CaseSeverity: MEDIUM
Files: src/constants/status.ts
| Status | Casing |
|---|---|
Processing | Title Case |
Approved | Title Case |
Snoozed | Title Case |
declined | lowercase |
In Transit | Title Case |
Impact: Integrators doing case-sensitive string comparisons against status values will miss declined orders if they expect Declined.
Recommendation: Change to Declined for consistency. Update any status comparison logic to be case-insensitive during the transition.
Severity: MEDIUM
Files: src/routes/users.route.ts, src/routes/groups.route.ts
DELETE /api/user and DELETE /api/group expect the resource identifier in the request body:
DELETE /api/user
{ "account_id": {...}, "user": { "gdt_id": 123 } }
Per HTTP specification (RFC 7231), a DELETE request body has no defined semantics. Many HTTP clients, proxies, CDNs, and API gateways strip or ignore bodies on DELETE requests.
Recommendation: Move to URL-parameter-based deletion:
DELETE /api/user/:id?account_id=LAB-001DELETE /api/group/:id?account_id=LAB-001user_type Enum Skips Values (No 4, 6, 9)Severity: MEDIUM
Files: src/validations/user.ts
Valid user_type values: 1, 2, 3, 5, 7, 8, 10 – with gaps at 4, 6, and 9.
Impact: Integrators reading the docs will wonder what types 4, 6, and 9 are. If these were deprecated roles, their IDs should be documented as reserved. If they never existed, the numbering is confusing.
Recommendation: Document the gaps explicitly (e.g., “Values 4, 6, 9 are reserved”) or provide named constants that partners should use instead of raw numbers.
Severity: MEDIUM
Files: src/controller/webhook.controller.ts
Outbound webhooks include a Bearer token in the Authorization header, but there is no HMAC signature on the request body. Partners cannot verify that the payload was actually sent by Diagnostic.ly and wasn’t tampered with in transit.
Recommendation:
X-Signature-256 header containing HMAC-SHA256(webhook_secret, request_body)gdt_client_id Required on Test Results EndpointSeverity: LOW
Files: src/validations/testResults.ts
The GET /api/test-results endpoint requires gdt_client_id as a query parameter. This is an internal system identifier that external integrators shouldn’t need to know or provide – it should be resolved from their API key or account.
Recommendation: Resolve gdt_client_id from the authenticated account context instead of requiring it as an explicit parameter.
Severity: LOW
Files: src/middlewares/validate.ts
When a request has multiple validation errors, only the first error message is returned. Partners must fix and re-submit repeatedly to discover all issues.
Recommendation: Return all validation errors at once:
{
"type": "ClientError",
"statusCode": 400,
"message": "Validation failed",
"errors": [
{ "field": "ship_to.email", "message": "The field email is required" },
{ "field": "bundles[0].sku", "message": "Either sku or external_bundle_id is required" }
]
}
Severity: LOW
The API has no version prefix (/v1/, /v2/). The V2 order creation is a different controller behind the same route. Breaking changes would affect all partners simultaneously.
Recommendation:
/v1/ prefix for current endpoints/v2/ endpoints while keeping /v1/ alive with a deprecation timelineAPI-Version response headerstarting_order_status Commented Out in Joi But Validated at RuntimeSeverity: LOW
Files: src/validations/order.ts, src/helpers/createOrder.ts
The starting_order_status field is commented out of the Joi validation schema but is still validated at runtime in the order creation helper. Invalid values pass Joi but fail later with less clear error messages.
Recommendation: Either uncomment and properly validate in Joi, or remove the runtime validation if the field is not intended for external use.
api-key Header NameSeverity: LOW
Files: src/middlewares/auth.ts
The API uses a custom api-key header instead of the standard Authorization: Bearer pattern.
Impact: Minor friction for integrators who expect standard auth headers. Some API tools and gateways have built-in support for Authorization but not custom headers.
Recommendation: Long-term, migrate to Authorization: Bearer or Authorization: ApiKey . Support both headers during transition.
Severity: LOW
Files: src/controller/getTestResult.controller.ts
When a date range exceeds 90 days, the API returns a 200 response with a warning field and empty data instead of a proper error response. Partners may not notice the warning and think there are simply no results.
Recommendation: Return a 400 error with a clear message: "Date range cannot exceed 90 days. Please narrow your search." This is unambiguous and forces correct usage.
This audit was performed by static analysis of the diagnostic.ly-nodejs source code. Runtime testing and penetration testing are recommended to identify additional issues.