Skip to content

send communication resource to EHR#182

Open
smalho01 wants to merge 9 commits intodevfrom
communincation-resource
Open

send communication resource to EHR#182
smalho01 wants to merge 9 commits intodevfrom
communincation-resource

Conversation

@smalho01
Copy link
Collaborator

Describe your changes

Check the ETASU status at the dispense authorization endpoint, basic fill in for NCPDP messages using JSON for now (will change to NCPDP XML once we receive spec) and send a communication resource to the FHIR EHR server for the prescriber to see.

Issue ticket number and Jira link

REMS-856

Checklist before requesting a review

  • I have performed a self-review of my code
  • Ensure the target / base branch for any feature PR is set to dev not main (the only exception to this is releases from dev and hotfix branches)

Checklist for conducting a review

  • Review the code changes and make sure they all make sense and are necessary.
  • Pull the PR branch locally and test by running through workflow and making sure everything works as it is supposed to.

Workflow

Owner of the Pull Request will be responsible for merge after all requirements are met, including approval from at least one reviewer. Additional changes made after a review will dismiss any approvals and require re-review of the additional updates. Auto merging can be enabled below if additional changes are likely not to be needed. The bot will auto assign reviewers to your Pull Request for you.

…om user-controlled sources

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
plarocque4
plarocque4 previously approved these changes Feb 5, 2026
…tection (#183)

* added advanced case history and prescriber/pharmacy change detection

* run lint / prettier

* ncpdp endpoint implimentation

* update routing

* add package ndc code tracking for ncpdp messages

* fix ndc logic

* updated models for patient id tracking

* include response type in ncpdp messages

* use saved medication on rems case creation to get ndc code from rxnorm

* don't show patient status until patient enrolled

* rxfill

* proper reason code handling

* singular denial reason

* single denial reason
plarocque4
plarocque4 previously approved these changes Feb 5, 2026

logger.info(`Looking up case: ${caseId}`);

const remsCase = await remsCaseCollection.findOne({ case_number: caseId });

Check failure

Code scanning / CodeQL

Database query built from user-controlled sources High

This query object depends on a
user-provided value
.

Copilot Autofix

AI about 10 hours ago

In general, to fix this kind of issue you must ensure that user-controlled values used in Mongo/NoSQL queries are interpreted only as literal values, not as query objects or operators. This can be done either by (1) validating/coercing the input so that only expected primitive types (string/number) are allowed, or (2) using an equality operator such as $eq so that even if an object is supplied, it is treated as a literal rather than merged into the query selector.

The best minimal-change fix here is to restrict caseId to a primitive string and reject requests that do not supply a valid string ID. That keeps the existing semantics of “lookup this case by its ID” while preventing query-shaping attacks. Concretely, within handleRemsRequest in src/ncpdp/script.ts, after extracting caseId, add a type check typeof caseId !== 'string' (and optionally a simple sanity check like .trim().length === 0) and return a “denied” response if the value is not valid. Then use caseId as before in findOne. This keeps the control flow and responses consistent with the existing “no case ID” / “case not found” branches and requires no new imports or external libraries.

More specifically:

  • In handleRemsRequest, after const caseId = ... and before logging “Extracted case ID”, insert a check ensuring caseId is a string (and not an object), returning a denied response (status 200, like the other validation error) if validation fails.
  • Optionally normalize caseId (e.g., const normalizedCaseId = caseId.trim();) and use the normalized version in the query; but to minimize behavior changes we will only check type and emptiness.
  • Leave the findOne({ case_number: caseId }) call intact so existing behavior is unchanged for valid string IDs.

No imports or additional methods are needed; all changes are within handleRemsRequest in src/ncpdp/script.ts.

Suggested changeset 1
src/ncpdp/script.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/ncpdp/script.ts b/src/ncpdp/script.ts
--- a/src/ncpdp/script.ts
+++ b/src/ncpdp/script.ts
@@ -64,6 +64,14 @@
         .send(buildDeniedResponse(header, remsRequest, 'EC', 'Case ID not provided'));
     }
 
+    if (typeof caseId !== 'string' || caseId.trim().length === 0) {
+      logger.error('Invalid Case ID type or value in request');
+      res.type('application/xml');
+      return res
+        .status(200)
+        .send(buildDeniedResponse(header, remsRequest, 'EC', 'Invalid case ID'));
+    }
+
     logger.info(`Looking up case: ${caseId}`);
 
     const remsCase = await remsCaseCollection.findOne({ case_number: caseId });
EOF
@@ -64,6 +64,14 @@
.send(buildDeniedResponse(header, remsRequest, 'EC', 'Case ID not provided'));
}

if (typeof caseId !== 'string' || caseId.trim().length === 0) {
logger.error('Invalid Case ID type or value in request');
res.type('application/xml');
return res
.status(200)
.send(buildDeniedResponse(header, remsRequest, 'EC', 'Invalid case ID'));
}

logger.info(`Looking up case: ${caseId}`);

const remsCase = await remsCaseCollection.findOne({ case_number: caseId });
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +204 to +209
const remsCase = await remsCaseCollection.findOne({
patientFirstName: patient?.names?.name?.firstname,
patientLastName: patient?.names?.name?.lastname,
patientDOB: patient?.dateofbirth?.date,
drugNdcCode: drugNdcCode
});

Check failure

Code scanning / CodeQL

Database query built from user-controlled sources High

This query object depends on a
user-provided value
.

Copilot Autofix

AI about 10 hours ago

In general, to fix NoSQL injection when building query objects from untrusted data, either (1) enforce that the values used in queries are primitives of the expected types (e.g., strings, dates) and not arbitrary objects, or (2) wrap them with an operator such as $eq so they are always interpreted as literals and not as part of the query structure. Input validation is usually preferable, because it also catches malformed requests earlier.

For this specific code, the safest, minimal-change approach is:

  • Extract the relevant fields (patientFirstName, patientLastName, patientDOB, drugNdcCode) into local variables.
  • Validate that each of these is of the expected primitive type (string) and not an object.
  • If validation fails, respond with a 400 Bad Request and an appropriate error message (as XML, consistent with the rest of the handler) instead of querying the database.
  • Pass only the validated primitive values into remsCaseCollection.findOne. Because we now ensure they are strings (or at least non-object primitives), the query object cannot contain malicious operators.

All changes are confined to src/ncpdp/script.ts within the handleRemsInitiation function, around lines 193–215 and just before the catch at line 273. No new imports are required; we can reuse the existing buildErrorResponse, logger, and res handling patterns.

Concretely:

  • Modify the section that builds requestInfo and constructs the findOne query to first define patientFirstName, patientLastName, patientDOB, and drugNdcCode variables.
  • Add a small validation block that checks typeof each field (and that it is present); on failure, logs and returns a 400 response with buildErrorResponse and content type application/xml.
  • Use the validated variables (not direct property access on patient) in the findOne filter.
Suggested changeset 1
src/ncpdp/script.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/ncpdp/script.ts b/src/ncpdp/script.ts
--- a/src/ncpdp/script.ts
+++ b/src/ncpdp/script.ts
@@ -200,16 +200,32 @@
     //const pharmacy = initRequest.pharmacy;
     const drugNdcCode = initRequest.medicationprescribed?.product?.drugcoded?.ndc;
 
+    const patientFirstName = patient?.names?.name?.firstname;
+    const patientLastName = patient?.names?.name?.lastname;
+    const patientDOB = patient?.dateofbirth?.date;
+
     const requestInfo = {
-      patientName: `${patient?.names?.name?.firstname} ${patient?.names?.name?.lastname}`,
+      patientName: `${patientFirstName} ${patientLastName}`,
       drugNdcCode: drugNdcCode
     };
     logger.info(`REMS Initiation request: ${JSON.stringify(requestInfo)}`);
 
+    // Validate that query fields are simple primitive values to avoid NoSQL injection
+    if (
+      typeof patientFirstName !== 'string' ||
+      typeof patientLastName !== 'string' ||
+      typeof patientDOB !== 'string' ||
+      typeof drugNdcCode !== 'string'
+    ) {
+      logger.error('Invalid REMS initiation request: patient/drug identifiers must be strings');
+      res.type('application/xml');
+      return res.status(400).send(buildErrorResponse('Invalid REMS initiation request'));
+    }
+
     const remsCase = await remsCaseCollection.findOne({
-      patientFirstName: patient?.names?.name?.firstname,
-      patientLastName: patient?.names?.name?.lastname,
-      patientDOB: patient?.dateofbirth?.date,
+      patientFirstName: patientFirstName,
+      patientLastName: patientLastName,
+      patientDOB: patientDOB,
       drugNdcCode: drugNdcCode
     });
 
EOF
@@ -200,16 +200,32 @@
//const pharmacy = initRequest.pharmacy;
const drugNdcCode = initRequest.medicationprescribed?.product?.drugcoded?.ndc;

const patientFirstName = patient?.names?.name?.firstname;
const patientLastName = patient?.names?.name?.lastname;
const patientDOB = patient?.dateofbirth?.date;

const requestInfo = {
patientName: `${patient?.names?.name?.firstname} ${patient?.names?.name?.lastname}`,
patientName: `${patientFirstName} ${patientLastName}`,
drugNdcCode: drugNdcCode
};
logger.info(`REMS Initiation request: ${JSON.stringify(requestInfo)}`);

// Validate that query fields are simple primitive values to avoid NoSQL injection
if (
typeof patientFirstName !== 'string' ||
typeof patientLastName !== 'string' ||
typeof patientDOB !== 'string' ||
typeof drugNdcCode !== 'string'
) {
logger.error('Invalid REMS initiation request: patient/drug identifiers must be strings');
res.type('application/xml');
return res.status(400).send(buildErrorResponse('Invalid REMS initiation request'));
}

const remsCase = await remsCaseCollection.findOne({
patientFirstName: patient?.names?.name?.firstname,
patientLastName: patient?.names?.name?.lastname,
patientDOB: patient?.dateofbirth?.date,
patientFirstName: patientFirstName,
patientLastName: patientLastName,
patientDOB: patientDOB,
drugNdcCode: drugNdcCode
});

Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 223 to 225
const medication = await medicationCollection.findOne({
ndcCode: drugNdcCode
});

Check failure

Code scanning / CodeQL

Database query built from user-controlled sources High

This query object depends on a
user-provided value
.

Copilot Autofix

AI about 10 hours ago

In general, untrusted values used in MongoDB queries should either be (a) wrapped in an operator like $eq so MongoDB always treats them as literal values, or (b) validated to ensure they are primitive types (e.g., string) and not objects containing query operators before being included in a query object.

For this code, the minimal, safe fix without changing functionality is to change the medicationCollection.findOne query (lines 227–229) to use the $eq operator for ndcCode. That keeps the same semantics (equality match on NDC code) but prevents a crafted object from being interpreted as a query operator. Concretely, we will modify the query in handleRemsInitiation from:

const medication = await medicationCollection.findOne({
  ndcCode: drugNdcCode
});

to:

const medication = await medicationCollection.findOne({
  ndcCode: { $eq: drugNdcCode }
});

This mirrors the "GOOD: using $eq operator" pattern in the background material. No additional imports or helper functions are required; the change is localized to the existing query in src/ncpdp/script.ts.

Suggested changeset 1
src/ncpdp/script.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/ncpdp/script.ts b/src/ncpdp/script.ts
--- a/src/ncpdp/script.ts
+++ b/src/ncpdp/script.ts
@@ -225,7 +225,7 @@
 
     // Case exists - check requirements
     const medication = await medicationCollection.findOne({
-      ndcCode: drugNdcCode
+      ndcCode: { $eq: drugNdcCode }
     });
 
     if (!medication) {
EOF
@@ -225,7 +225,7 @@

// Case exists - check requirements
const medication = await medicationCollection.findOne({
ndcCode: drugNdcCode
ndcCode: { $eq: drugNdcCode }
});

if (!medication) {
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +311 to +316
remsCase = await remsCaseCollection.findOne({
patientFirstName: patientInfo.firstName,
patientLastName: patientInfo.lastName,
patientDOB: patientInfo.dob,
drugNdcCode: drugNdcCode
});

Check failure

Code scanning / CodeQL

Database query built from user-controlled sources High

This query object depends on a
user-provided value
.

Copilot Autofix

AI about 10 hours ago

In general, to fix this type of issue in MongoDB-style queries, you must ensure that any user-controlled values embedded in a query filter are interpreted as literal values, not as query objects. This can be done by either (a) wrapping them with $eq so MongoDB treats them as equality comparisons on a literal, or (b) validating/coercing the values to the expected primitive types (e.g., strings, dates) and rejecting or sanitizing objects.

For this specific case, the simplest change that preserves existing functionality is to make each user-derived field in the findOne filter explicitly use $eq. That way, even if an attacker sends an object like { $ne: null } as drugNdcCode, it will be used as the literal value of the equality comparison instead of being interpreted as an operator. We can keep the rest of the logic intact: still look up by first name, last name, DOB, and NDC when drugNdcCode is present. Concretely, in src/ncpdp/script.ts, in the handleRxFill logic around line 308–314, we should change the findOne call to:

remsCase = await remsCaseCollection.findOne({
  patientFirstName: { $eq: patientInfo.firstName },
  patientLastName: { $eq: patientInfo.lastName },
  patientDOB: { $eq: patientInfo.dob },
  drugNdcCode: { $eq: drugNdcCode }
});

This does not require new imports or helper functions and doesn’t alter the logical behavior for legitimate scalar inputs; it simply hardens the query against operator injection.

Suggested changeset 1
src/ncpdp/script.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/ncpdp/script.ts b/src/ncpdp/script.ts
--- a/src/ncpdp/script.ts
+++ b/src/ncpdp/script.ts
@@ -307,10 +307,10 @@
 
     if (drugNdcCode) {
       remsCase = await remsCaseCollection.findOne({
-        patientFirstName: patientInfo.firstName,
-        patientLastName: patientInfo.lastName,
-        patientDOB: patientInfo.dob,
-        drugNdcCode: drugNdcCode
+        patientFirstName: { $eq: patientInfo.firstName },
+        patientLastName: { $eq: patientInfo.lastName },
+        patientDOB: { $eq: patientInfo.dob },
+        drugNdcCode: { $eq: drugNdcCode }
       });
     }
 
EOF
@@ -307,10 +307,10 @@

if (drugNdcCode) {
remsCase = await remsCaseCollection.findOne({
patientFirstName: patientInfo.firstName,
patientLastName: patientInfo.lastName,
patientDOB: patientInfo.dob,
drugNdcCode: drugNdcCode
patientFirstName: { $eq: patientInfo.firstName },
patientLastName: { $eq: patientInfo.lastName },
patientDOB: { $eq: patientInfo.dob },
drugNdcCode: { $eq: drugNdcCode }
});
}

Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants