Skip to content

Commit 0c5f35c

Browse files
mongodbenBen Perlmutter
andauthored
(EAI-929): Redact connection URIs from messages (#685)
redaction action Co-authored-by: Ben Perlmutter <mongodben@mongodb.com>
1 parent 87e0559 commit 0c5f35c

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed

packages/chatbot-server-mongodb-public/src/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { blockGetRequests } from "./middleware/blockGetRequests";
2727
import { getRequestId, logRequest } from "./utils";
2828
import { systemPrompt } from "./systemPrompt";
2929
import { addReferenceSourceType } from "./processors/makeMongoDbReferences";
30+
import { redactConnectionUri } from "./middleware/redactConnectionUri";
3031
import path from "path";
3132
import express from "express";
3233
import { wrapOpenAI, wrapTraced } from "mongodb-rag-core/braintrust";
@@ -243,6 +244,7 @@ export const config: AppConfig = {
243244
requireRequestOrigin(),
244245
useSegmentIds(),
245246
cookieParser(),
247+
redactConnectionUri(),
246248
],
247249
createConversationCustomData: !isProduction
248250
? createConversationCustomDataWithAuthUser
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { redactConnectionUri } from "./redactConnectionUri";
2+
import {
3+
createConversationsMiddlewareReq,
4+
createConversationsMiddlewareRes,
5+
} from "../test/middlewareTestHelpers";
6+
7+
const baseReq = {
8+
body: { message: "Hello, world!" },
9+
params: { conversationId: "conversation-1234" },
10+
query: { stream: "true" },
11+
headers: { "req-id": "request-1234" },
12+
};
13+
14+
describe("redactConnectionUri", () => {
15+
it("passes through requests with no MongoDB connection URIs", async () => {
16+
const req = createConversationsMiddlewareReq();
17+
const res = createConversationsMiddlewareRes();
18+
const next = jest.fn();
19+
20+
const middleware = redactConnectionUri();
21+
req.body = {
22+
message: "This is a normal message without any connection URI",
23+
};
24+
req.params = baseReq.params;
25+
req.query = baseReq.query;
26+
req.headers = baseReq.headers;
27+
28+
await middleware(req, res, next);
29+
30+
expect(next).toHaveBeenCalledTimes(1);
31+
expect((req.body as { message: string }).message).toBe(
32+
"This is a normal message without any connection URI"
33+
);
34+
});
35+
36+
it("redacts MongoDB connection URIs in message", async () => {
37+
const req = createConversationsMiddlewareReq();
38+
const res = createConversationsMiddlewareRes();
39+
const next = jest.fn();
40+
41+
const middleware = redactConnectionUri();
42+
const messageWithUri =
43+
"I'm using this connection string: mongodb://user123:password456@localhost:27017/mydb";
44+
req.body = { message: messageWithUri };
45+
req.params = baseReq.params;
46+
req.query = baseReq.query;
47+
req.headers = baseReq.headers;
48+
49+
await middleware(req, res, next);
50+
51+
expect(next).toHaveBeenCalledTimes(1);
52+
expect((req.body as { message: string }).message).toBe(
53+
"I'm using this connection string: mongodb://<USERNAME>:<PASSWORD>@localhost:27017/mydb"
54+
);
55+
expect((req.body as { message: string }).message).not.toContain("user123");
56+
expect((req.body as { message: string }).message).not.toContain(
57+
"password456"
58+
);
59+
});
60+
61+
it("redacts multiple MongoDB connection URIs in message", async () => {
62+
const req = createConversationsMiddlewareReq();
63+
const res = createConversationsMiddlewareRes();
64+
const next = jest.fn();
65+
66+
const middleware = redactConnectionUri();
67+
const messageWithUris =
68+
"I have two connection strings: " +
69+
"mongodb://user1:pass1@localhost:27017/db1 and " +
70+
"mongodb+srv://user2:pass2@cluster0.mongodb.net/db2";
71+
req.body = { message: messageWithUris };
72+
req.params = baseReq.params;
73+
req.query = baseReq.query;
74+
req.headers = baseReq.headers;
75+
76+
await middleware(req, res, next);
77+
78+
expect(next).toHaveBeenCalledTimes(1);
79+
expect((req.body as { message: string }).message).toBe(
80+
"I have two connection strings: " +
81+
"mongodb://<USERNAME>:<PASSWORD>@localhost:27017/db1 and " +
82+
"mongodb+srv://<USERNAME>:<PASSWORD>@cluster0.mongodb.net/db2"
83+
);
84+
expect((req.body as { message: string }).message).not.toContain("user1");
85+
expect((req.body as { message: string }).message).not.toContain("pass1");
86+
expect((req.body as { message: string }).message).not.toContain("user2");
87+
expect((req.body as { message: string }).message).not.toContain("pass2");
88+
});
89+
90+
it("handles requests with no body", async () => {
91+
const req = createConversationsMiddlewareReq();
92+
const res = createConversationsMiddlewareRes();
93+
const next = jest.fn();
94+
95+
const middleware = redactConnectionUri();
96+
req.body = undefined;
97+
req.params = baseReq.params;
98+
req.query = baseReq.query;
99+
req.headers = baseReq.headers;
100+
101+
await middleware(req, res, next);
102+
103+
expect(next).toHaveBeenCalledTimes(1);
104+
});
105+
106+
it("handles requests with no message", async () => {
107+
const req = createConversationsMiddlewareReq();
108+
const res = createConversationsMiddlewareRes();
109+
const next = jest.fn();
110+
111+
const middleware = redactConnectionUri();
112+
req.body = { someOtherField: "value" };
113+
req.params = baseReq.params;
114+
req.query = baseReq.query;
115+
req.headers = baseReq.headers;
116+
117+
await middleware(req, res, next);
118+
119+
expect(next).toHaveBeenCalledTimes(1);
120+
expect((req.body as { someOtherField: string }).someOtherField).toBe(
121+
"value"
122+
);
123+
});
124+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { ConversationsMiddleware } from "mongodb-chatbot-server";
2+
import { getRequestId, logRequest } from "../utils";
3+
import { redactMongoDbConnectionUri } from "mongodb-rag-core/executeCode";
4+
5+
/**
6+
Middleware that redacts MongoDB connection URIs in request bodies to prevent
7+
sensitive information from being logged or stored.
8+
9+
This middleware looks for MongoDB connection URIs in the request body and
10+
replaces the username and password with placeholders.
11+
*/
12+
export function redactConnectionUri(): ConversationsMiddleware {
13+
return (req, res, next) => {
14+
const reqId = getRequestId(req);
15+
16+
// Skip if there's no body
17+
if (!req.body) {
18+
next();
19+
return;
20+
}
21+
22+
// Process the message for addMessageToConversations if it exists
23+
if (req.body.message && typeof req.body.message === "string") {
24+
req.body.message = redactMongoDbConnectionUri(req.body.message);
25+
}
26+
27+
logRequest({
28+
reqId,
29+
message: "Redacted MongoDB connection URIs in request body",
30+
});
31+
32+
next();
33+
};
34+
}

0 commit comments

Comments
 (0)