Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/env.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
},
"INTERMEDIARY_URL": {
"type": "string",
"default": "http://localhost:8090/ncpdp/script"
"default": "http://localhost:3003"
},
"EHR_NCPDP_URL": {
"type": "string",
Expand Down
25 changes: 11 additions & 14 deletions backend/src/ncpdpScriptBuilder/buildScript.v2017071.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { XMLBuilder } from 'fast-xml-parser';
import { v4 as uuidv4 } from 'uuid';

const MOCK_VALUE = 'MOCK_VALUE';
const PICKED_UP = 'Picked up';

const XML_BUILDER_OPTIONS = {
ignoreAttributes: false,
Expand Down Expand Up @@ -97,10 +96,10 @@ export function buildRxError(newRxMessageConvertedToJSON, errorMessage) {
* Per NCPDP spec: Sent when medication is dispensed/picked up
* Must be sent to both EHR and REMS Admin for REMS drugs
*/
export const buildRxFill = newRx => {
export const buildRxFill = (newRx, toEhr, dispensed, note) => {
const { Message } = JSON.parse(newRx.serializedJSON);
const { Header, Body } = Message;
console.log('Building RxFill per NCPDP SCRIPT');
console.log('Building RxFill per NCPDP SCRIPT, ' + (toEhr ? 'To: EHR' : 'To: REMS') + ', dispensed: ' + dispensed + ', note: ' + note);
const time = new Date();

// Extract medication data from NewRx
Expand Down Expand Up @@ -145,8 +144,8 @@ export const buildRxFill = newRx => {
Header: [
{
To: {
'#text': Header.From._,
'@@Qualifier': Header.From.$.Qualifier
'#text': (toEhr ? Header.From._: 'REMS Administrator'),
'@@Qualifier': (toEhr ? Header.From.$.Qualifier : 'REMS')
}
},
{
Expand All @@ -164,11 +163,9 @@ export const buildRxFill = newRx => {
Body: [
{
RxFill: {
FillStatus: {
Dispensed: {
Note: PICKED_UP
}
},
FillStatus:
dispensed ? { Dispensed: { Note: note }} : { NotDispensed: { Note: note }}
,
Patient: Body.NewRx.Patient,
Pharmacy: {
Identification: {
Expand Down Expand Up @@ -235,13 +232,13 @@ export const buildREMSInitiationRequest = newRx => {
{
To: {
'#text': ndcCode,
'@@Qualifier': 'ZZZ'
'@@Qualifier': 'REMS'
}
},
{
From: {
'#text': Header.To._ || 'PIMS Pharmacy',
'@@Qualifier': 'REMS'
'@@Qualifier': 'P'
}
},
{ MessageID: uuidv4() },
Expand Down Expand Up @@ -318,13 +315,13 @@ export const buildREMSRequest = (newRx, caseNumber) => {
{
To: {
'#text': ndcCode,
'@@Qualifier': 'ZZZ'
'@@Qualifier': 'REMS'
}
},
{
From: {
'#text': Header.To._ || 'PIMS Pharmacy',
'@@Qualifier': 'REMS'
'@@Qualifier': 'P'
}
},
{ MessageID: uuidv4() },
Expand Down
143 changes: 92 additions & 51 deletions backend/src/routes/doctorOrders.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// XML Parsing Middleware used for NCPDP SCRIPT
import bodyParser from 'body-parser';
import bpx from 'body-parser-xml';
import { parseStringPromise } from "xml2js";
import { parseStringPromise } from 'xml2js';
import env from 'var';
import {
buildRxStatus,
Expand All @@ -16,7 +16,7 @@
} from '../ncpdpScriptBuilder/buildScript.v2017071.js';
import { NewRx } from '../database/schemas/newRx.js';
import { medicationRequestToRemsAdmins } from '../database/data.js';
import { getConfig, updateConfig, getNCPDPEndpoint, getETASUEndpoint, getRxFillEndpoint } from '../lib/pharmacyConfig.js';
import { getConfig, updateConfig, getNCPDPEndpoint, getRxFillEndpoint } from '../lib/pharmacyConfig.js';

bpx(bodyParser);
router.use(
Expand Down Expand Up @@ -152,53 +152,80 @@
* Route: 'doctorOrders/api/updateRx/:id'
* Description : 'Updates prescription based on mongo id, sends NCPDP REMSRequest for authorization'
*/
router.patch('/api/updateRx/:id', async (req, res) => {
try {
const order = await doctorOrder.findById(req.params.id).exec();
console.log('Found doctor order by id! --- ', order);

let prescriberOrderNumber = order.prescriberOrderNumber;

// Non-REMS drugs auto-approve
if (!isRemsDrug(order)) {
const newOrder = await doctorOrder.findOneAndUpdate(
{ _id: req.params.id },
{ dispenseStatus: 'Approved' },
{ new: true }
);
res.send(newOrder);
console.log('Non-REMS drug - auto-approved');
return;
}

// REMS drugs - send NCPDP REMSRequest per spec
console.log('REMS drug - sending REMSRequest for authorization per NCPDP workflow');
const ncpdpResponse = await sendREMSRequest(order);

if (!ncpdpResponse) {
res.send(order);
console.log('NCPDP REMSRequest failed');
return;
}

// Update based on NCPDP response
const updateData = {
dispenseStatus: getDispenseStatus(order, ncpdpResponse)
};

let rxFillNote;

if (ncpdpResponse.status === 'APPROVED') {
updateData.authorizationNumber = ncpdpResponse.authorizationNumber;
updateData.authorizationExpiration = ncpdpResponse.authorizationExpiration;
updateData.caseNumber = ncpdpResponse.caseId;

// Format approval note with ETASU summary
let approvalNote = `APPROVED - Authorization: ${ncpdpResponse.authorizationNumber}, Expires: ${ncpdpResponse.authorizationExpiration}`;
updateData.remsNote = approvalNote;
updateData.denialReasonCode = null;
console.log('APPROVED:', ncpdpResponse.authorizationNumber);

rxFillNote = 'REMS Requirements Verified';
} else if (ncpdpResponse.status === 'DENIED') {
updateData.denialReasonCode = ncpdpResponse.reasonCode;
updateData.remsNote = ncpdpResponse.remsNote;
updateData.caseNumber = ncpdpResponse.caseId;
console.log('DENIED:', ncpdpResponse.reasonCode);

rxFillNote = updateData.remsNote;
}

// Send the RxFill message to the EHR
if (rxFillNote) {
try {
const newRx = await NewRx.findOne({
prescriberOrderNumber: prescriberOrderNumber
Comment on lines 199 to -302

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI 12 days ago

In general, the problem is fixed by introducing a rate-limiting middleware for routes that perform expensive operations (database / network calls). In an Express app, the typical solution is to use a well-known middleware like express-rate-limit, configure sensible limits (e.g., max requests per IP per time window), and apply it either globally to the router or to specific sensitive routes.

For this file, the least intrusive, most effective fix is to import express-rate-limit, define a limiter tailored for doctor order update operations, and apply it specifically to the /api/updateRx/:id PATCH route (and optionally similar DB-heavy routes if desired). This avoids changing any existing functionality of the handler itself while satisfying the requirement that the route be rate-limited. Concretely:

  • Add import rateLimit from 'express-rate-limit'; near the top of backend/src/routes/doctorOrders.js without altering existing imports.
  • Define a limiter constant, e.g., const updateRxLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }); after the router/middleware setup.
  • Change the route declaration from router.patch('/api/updateRx/:id', async (req, res) => { ... }) to router.patch('/api/updateRx/:id', updateRxLimiter, async (req, res) => { ... }), so each client IP can only hit this endpoint a bounded number of times per window.
    This single limiter on the route will cover all three database accesses within the handler because the middleware runs before the handler executes.
Suggested changeset 2
backend/src/routes/doctorOrders.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/src/routes/doctorOrders.js b/backend/src/routes/doctorOrders.js
--- a/backend/src/routes/doctorOrders.js
+++ b/backend/src/routes/doctorOrders.js
@@ -17,6 +17,7 @@
 import { NewRx } from '../database/schemas/newRx.js';
 import { medicationRequestToRemsAdmins } from '../database/data.js';
 import { getConfig, updateConfig, getNCPDPEndpoint, getRxFillEndpoint } from '../lib/pharmacyConfig.js';
+import rateLimit from 'express-rate-limit';
 
 bpx(bodyParser);
 router.use(
@@ -30,6 +31,11 @@
 );
 router.use(bodyParser.urlencoded({ extended: false }));
 
+const updateRxLimiter = rateLimit({
+  windowMs: 15 * 60 * 1000, // 15 minutes
+  max: 100, // limit each IP to 100 updateRx requests per windowMs
+});
+
 const XML2JS_OPTS = {
   explicitArray: false,
   trim: true,
@@ -152,7 +158,7 @@
  * Route: 'doctorOrders/api/updateRx/:id'
  * Description : 'Updates prescription based on mongo id, sends NCPDP REMSRequest for authorization'
  */
-router.patch('/api/updateRx/:id', async (req, res) => {
+router.patch('/api/updateRx/:id', updateRxLimiter, async (req, res) => {
   try {
     const order = await doctorOrder.findById(req.params.id).exec();
     console.log('Found doctor order by id! --- ', order);
EOF
@@ -17,6 +17,7 @@
import { NewRx } from '../database/schemas/newRx.js';
import { medicationRequestToRemsAdmins } from '../database/data.js';
import { getConfig, updateConfig, getNCPDPEndpoint, getRxFillEndpoint } from '../lib/pharmacyConfig.js';
import rateLimit from 'express-rate-limit';

bpx(bodyParser);
router.use(
@@ -30,6 +31,11 @@
);
router.use(bodyParser.urlencoded({ extended: false }));

const updateRxLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 updateRx requests per windowMs
});

const XML2JS_OPTS = {
explicitArray: false,
trim: true,
@@ -152,7 +158,7 @@
* Route: 'doctorOrders/api/updateRx/:id'
* Description : 'Updates prescription based on mongo id, sends NCPDP REMSRequest for authorization'
*/
router.patch('/api/updateRx/:id', async (req, res) => {
router.patch('/api/updateRx/:id', updateRxLimiter, async (req, res) => {
try {
const order = await doctorOrder.findById(req.params.id).exec();
console.log('Found doctor order by id! --- ', order);
backend/package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/package.json b/backend/package.json
--- a/backend/package.json
+++ b/backend/package.json
@@ -13,7 +13,8 @@
     "mongoose": "^8.9.5",
     "var": "^0.4.0",
     "web-vitals": "^2.1.4",
-    "xml2js": "^0.6.0"
+    "xml2js": "^0.6.0",
+    "express-rate-limit": "^8.2.1"
   },
   "devDependencies": {
     "@types/chai": "^4.3.4",
EOF
@@ -13,7 +13,8 @@
"mongoose": "^8.9.5",
"var": "^0.4.0",
"web-vitals": "^2.1.4",
"xml2js": "^0.6.0"
"xml2js": "^0.6.0",
"express-rate-limit": "^8.2.1"
},
"devDependencies": {
"@types/chai": "^4.3.4",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.2.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
});

if (!newRx) {
console.log('NewRx not found for RxFill');
return;
}

await sendRxFill(newRx, prescriberOrderNumber, false, rxFillNote);

} catch (error) {
console.log('Error in RxFill workflow:', error);
}
}

const newOrder = await doctorOrder.findOneAndUpdate(
Expand Down Expand Up @@ -275,52 +302,8 @@
return;
}

const rxFill = buildRxFill(newRx);
console.log('Sending RxFill per NCPDP workflow');

const config = getConfig();

if (config.useIntermediary) {
// Send to intermediary - it will forward to both EHR and REMS Admin
const endpoint = getRxFillEndpoint();
console.log(`Sending RxFill to intermediary: ${endpoint}`);
await axios.post(endpoint, rxFill, {
headers: { 'Content-Type': 'application/xml' }
});
} else {
// Send to EHR
try {
const ehrStatus = await axios.post(env.EHR_RXFILL_URL, rxFill, {
headers: {
Accept: 'application/xml',
'Content-Type': 'application/xml'
}
});
console.log('Sent RxFill to EHR, received status:', ehrStatus.data);
} catch (ehrError) {
console.log('Failed to send RxFill to EHR:', ehrError.message);
}
await sendRxFill(newRx, prescriberOrderNumber, true, 'Picked up');

// Send to REMS Admin (required by NCPDP spec for REMS drugs)
const order = await doctorOrder.findOne({ prescriberOrderNumber });
if (isRemsDrug(order)) {
try {
const remsAdminStatus = await axios.post(
env.REMS_ADMIN_NCPDP,
rxFill,
{
headers: {
Accept: 'application/xml',
'Content-Type': 'application/xml'
}
}
);
console.log('Sent RxFill to REMS Admin, received status:', remsAdminStatus.data);
} catch (remsError) {
console.log('Failed to send RxFill to REMS Admin:', remsError.message);
}
}
}
} catch (error) {
console.log('Error in RxFill workflow:', error);
}
Expand Down Expand Up @@ -508,6 +491,66 @@
}
};

/**
* Send NCPDP RxFill to the EHR and REMS Administrator
*/
const sendRxFill = async (newRx, prescriberOrderNumber, dispensed, note) => {
console.log('Sending RxFill per NCPDP workflow');

const config = getConfig();
const useIntermediary = config.useIntermediary;

// send to the EHR
const rxFillEhr = buildRxFill(newRx, true, dispensed, note);
let endpoint = env.EHR_RXFILL_URL;

if (useIntermediary) {
endpoint = getRxFillEndpoint();
}

try {
console.log(`Sending RxFill to EHR${(useIntermediary ? ' (through Intermediary)' : '')}: ${endpoint}`);
const ehrStatus = await axios.post(endpoint, rxFillEhr, {
headers: {
Accept: 'application/xml',
'Content-Type': 'application/xml'
}
});
Comment on lines 513 to 518

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI 12 days ago

Generally, to fix this kind of SSRF risk you must ensure that any configuration affecting outbound URLs cannot be set to arbitrary values by untrusted input. This can be done by (a) validating user-provided configuration against strict rules (e.g., allow only https, disallow private IPs/hosts, optional allow-list) and/or (b) preventing certain sensitive fields (like endpoint base URLs) from being set via user-facing APIs at all.

For this codebase, the least intrusive, functionality-preserving fix is to harden updateConfig in backend/src/lib/pharmacyConfig.js so that only a controlled subset of fields can be updated, and only with safe values. Specifically:

  • Define a small allow-list of configurable keys; for example, allow boolean useIntermediary but block arbitrary updates to URL fields from req.body.
  • Optionally add format validation for any URL fields you really do need to update at runtime (e.g., require https:// and a host that is not localhost or a private IP). Because we can’t assume other project parts, the safest is to block URL changes from the API completely and rely on environment variables for these.
  • Implement this by having updateConfig construct a sanitized safeConfig from newConfig, copying only allowed keys, and ignoring the rest.

Concretely:

  • Edit backend/src/lib/pharmacyConfig.js, lines 17–21, to:
    • Introduce a list of allowed keys (e.g., only useIntermediary).
    • Build a sanitizedConfig object with only those keys and with minimal type coercion (e.g., convert "true"/"false" strings to booleans).
    • Merge sanitizedConfig into config instead of merging newConfig directly.
  • Leave doctorOrders.js’s sendRxFill function unchanged; it still calls getRxFillEndpoint(), but now that endpoint can no longer be redirected arbitrarily by untrusted configuration updates.

This preserves existing behavior for safe fields (like toggling useIntermediary via the API) while preventing a user from changing intermediaryUrl, remsAdminUrl, or ehrUrl to arbitrary attacker-controlled hosts.

Suggested changeset 1
backend/src/lib/pharmacyConfig.js
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/src/lib/pharmacyConfig.js b/backend/src/lib/pharmacyConfig.js
--- a/backend/src/lib/pharmacyConfig.js
+++ b/backend/src/lib/pharmacyConfig.js
@@ -15,7 +15,26 @@
 
 
 export function updateConfig(newConfig) {
-  config = { ...config, ...newConfig };
+  // Only allow specific, safe fields to be updated from external input.
+  // URL fields (intermediaryUrl, remsAdminUrl, ehrUrl) remain controlled by environment/configuration,
+  // not by the API, to avoid SSRF risks.
+  const sanitizedConfig = {};
+
+  if (Object.prototype.hasOwnProperty.call(newConfig, 'useIntermediary')) {
+    // Coerce to boolean where possible
+    const value = newConfig.useIntermediary;
+    if (typeof value === 'boolean') {
+      sanitizedConfig.useIntermediary = value;
+    } else if (typeof value === 'string') {
+      if (value.toLowerCase() === 'true') {
+        sanitizedConfig.useIntermediary = true;
+      } else if (value.toLowerCase() === 'false') {
+        sanitizedConfig.useIntermediary = false;
+      }
+    }
+  }
+
+  config = { ...config, ...sanitizedConfig };
   console.log('Configuration updated:', config);
   return { ...config };
 }
EOF
@@ -15,7 +15,26 @@


export function updateConfig(newConfig) {
config = { ...config, ...newConfig };
// Only allow specific, safe fields to be updated from external input.
// URL fields (intermediaryUrl, remsAdminUrl, ehrUrl) remain controlled by environment/configuration,
// not by the API, to avoid SSRF risks.
const sanitizedConfig = {};

if (Object.prototype.hasOwnProperty.call(newConfig, 'useIntermediary')) {
// Coerce to boolean where possible
const value = newConfig.useIntermediary;
if (typeof value === 'boolean') {
sanitizedConfig.useIntermediary = value;
} else if (typeof value === 'string') {
if (value.toLowerCase() === 'true') {
sanitizedConfig.useIntermediary = true;
} else if (value.toLowerCase() === 'false') {
sanitizedConfig.useIntermediary = false;
}
}
}

config = { ...config, ...sanitizedConfig };
console.log('Configuration updated:', config);
return { ...config };
}
Copilot is powered by AI and may make mistakes. Always verify output.
console.log(`Sent RxFill to EHR${(useIntermediary ? ' (through Intermediary)' : '')}, received status: `);
console.log(ehrStatus.data);
} catch (ehrError) {
console.log(`Failed to send RxFill to EHR: ${ehrError.message}`);
}

// if dispensed, send to both EHR and REMS Admin
if (dispensed) {
// Send to REMS Admin (required by NCPDP spec for REMS drugs)
const order = await doctorOrder.findOne({ prescriberOrderNumber });
if (isRemsDrug(order)) {
const rxFillRems = buildRxFill(newRx, false, dispensed, note);
endpoint = env.REMS_ADMIN_NCPDP;

if (useIntermediary) {
endpoint = getRxFillEndpoint();
}

try {
console.log(`Sending RxFill to REMS Admin${(useIntermediary ? ' (through Intermediary)' : '')}: ${endpoint}`);
const remsAdminStatus = await axios.post(endpoint, rxFillRems, {
headers: {
Accept: 'application/xml',
'Content-Type': 'application/xml'
}
});
Comment on lines 539 to 544

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI 12 days ago

In general, to fix SSRF caused by user‑controlled configuration, you must prevent untrusted clients from setting arbitrary URLs used in outbound HTTP calls. That can be done by: (1) restricting which configuration fields can be updated at runtime, (2) validating URL fields carefully (scheme, host allow‑list, no localhost/metadata IPs, etc.), or (3) removing dynamic configuration of such URLs entirely and sourcing them only from trusted environment variables or static config.

The best minimal fix here, without changing intended functionality, is to validate and sanitize the newConfig object in updateConfig before merging it into config. Specifically: ensure useIntermediary is just a boolean; for each URL field (intermediaryUrl, remsAdminUrl, ehrUrl) either ignore updates from the client or strictly validate them so they cannot point to internal/loopback/metadata addresses, and require http or https scheme and a normal hostname. To keep behavior flexible but safe, we can: (a) only allow updates to a small subset of fields from /api/config; and (b) for URLs, parse them with Node’s standard URL class, reject invalid URLs or ones with disallowed hosts, and possibly restrict host to an allow‑list pattern (e.g., only specific domains). Since we must not assume external code, we will implement a small internal validator in backend/src/lib/pharmacyConfig.js and call it inside updateConfig to produce a sanitized merged config object.

Concretely:

  • In backend/src/lib/pharmacyConfig.js, add a helper like sanitizeConfigUpdate(newConfig) that:
    • Creates a copy of the existing config.
    • If newConfig.useIntermediary is present, coerces it to a boolean if it’s "true"/"false" or a boolean.
    • Option A (safest and simplest): completely ignore client updates to intermediaryUrl, remsAdminUrl, and ehrUrl so they remain environment‑defined and not attacker‑controlled.
    • Option B (more flexible, but more logic): if we choose to allow URL updates, parse them with new URL(value), ensure protocol is http: or https:, and ensure the hostname is not localhost, not in 127.0.0.0/8, not ::1, and not in the 169.254.169.254 metadata pattern or private ranges. To keep the patch small and robust, I’ll use Option A and simply refuse to override the URL fields from req.body, which fully breaks the taint flow to getRxFillEndpoint().
  • Change updateConfig(newConfig) to call sanitizeConfigUpdate(newConfig), assign the sanitized result to config, and return a copy.

No changes are needed in doctorOrders.js beyond this, because once config.intermediaryUrl (and the other URLs) can’t be set from req.body, getRxFillEndpoint() no longer depends on untrusted input and the SSRF sink is neutralized.


Suggested changeset 1
backend/src/lib/pharmacyConfig.js
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/src/lib/pharmacyConfig.js b/backend/src/lib/pharmacyConfig.js
--- a/backend/src/lib/pharmacyConfig.js
+++ b/backend/src/lib/pharmacyConfig.js
@@ -7,15 +7,48 @@
   ehrUrl: process.env.EHR_NCPDP_URL
 };
 
+/**
+ * Sanitize incoming configuration updates so that untrusted clients
+ * cannot override sensitive URL fields used for outbound requests.
+ */
+function sanitizeConfigUpdate(newConfig) {
+  if (!newConfig || typeof newConfig !== 'object') {
+    return { ...config };
+  }
 
+  const updated = { ...config };
 
+  // Only allow updating useIntermediary; coerce typical string forms to boolean.
+  if (Object.prototype.hasOwnProperty.call(newConfig, 'useIntermediary')) {
+    const val = newConfig.useIntermediary;
+    if (typeof val === 'boolean') {
+      updated.useIntermediary = val;
+    } else if (typeof val === 'string') {
+      if (val.toLowerCase() === 'true') {
+        updated.useIntermediary = true;
+      } else if (val.toLowerCase() === 'false') {
+        updated.useIntermediary = false;
+      }
+    }
+  }
+
+  // Ignore any attempts to update URL fields from the request body to
+  // avoid server-side request forgery through dynamic configuration.
+  //   - intermediaryUrl
+  //   - remsAdminUrl
+  //   - ehrUrl
+
+  return updated;
+}
+
+
 export function getConfig() {
   return { ...config };
 }
 
 
 export function updateConfig(newConfig) {
-  config = { ...config, ...newConfig };
+  config = sanitizeConfigUpdate(newConfig);
   console.log('Configuration updated:', config);
   return { ...config };
 }
EOF
@@ -7,15 +7,48 @@
ehrUrl: process.env.EHR_NCPDP_URL
};

/**
* Sanitize incoming configuration updates so that untrusted clients
* cannot override sensitive URL fields used for outbound requests.
*/
function sanitizeConfigUpdate(newConfig) {
if (!newConfig || typeof newConfig !== 'object') {
return { ...config };
}

const updated = { ...config };

// Only allow updating useIntermediary; coerce typical string forms to boolean.
if (Object.prototype.hasOwnProperty.call(newConfig, 'useIntermediary')) {
const val = newConfig.useIntermediary;
if (typeof val === 'boolean') {
updated.useIntermediary = val;
} else if (typeof val === 'string') {
if (val.toLowerCase() === 'true') {
updated.useIntermediary = true;
} else if (val.toLowerCase() === 'false') {
updated.useIntermediary = false;
}
}
}

// Ignore any attempts to update URL fields from the request body to
// avoid server-side request forgery through dynamic configuration.
// - intermediaryUrl
// - remsAdminUrl
// - ehrUrl

return updated;
}


export function getConfig() {
return { ...config };
}


export function updateConfig(newConfig) {
config = { ...config, ...newConfig };
config = sanitizeConfigUpdate(newConfig);
console.log('Configuration updated:', config);
return { ...config };
}
Copilot is powered by AI and may make mistakes. Always verify output.
console.log(`Sent RxFill to REMS Admin${(useIntermediary ? ' (through Intermediary)' : '')}, received status: `);
console.log(remsAdminStatus.data);
} catch (remsError) {
console.log('Failed to send RxFill to REMS Admin:', remsError.message);
}
}
}
};

/**
* Send NCPDP REMSInitiationRequest to REMS Admin
* Per NCPDP spec: Sent when prescription arrives to check REMS case status
Expand All @@ -526,7 +569,7 @@
const initiationRequest = buildREMSInitiationRequest(newRx);
console.log('Sending REMSInitiationRequest to REMS Admin');

console.log(initiationRequest)
console.log(initiationRequest);

const endpoint = getNCPDPEndpoint();
console.log(`Sending REMSInitiationRequest to: ${endpoint}`);
Expand Down Expand Up @@ -577,7 +620,7 @@

const remsRequest = buildREMSRequest(newRx, order.caseNumber);
console.log('Sending REMSRequest to REMS Admin for case:', order.caseNumber);
console.log(remsRequest)
console.log(remsRequest);

const endpoint = getNCPDPEndpoint();
console.log(`Sending REMSRequest to: ${endpoint}`);
Expand Down Expand Up @@ -673,8 +716,6 @@
return null;
}

const request = remsResponse.request;

const response = remsResponse.response;
const responseStatus = response?.responsestatus;

Expand Down
Loading