Skip to content

fix(security): comprehensive security hardening - auth, encryption, injection prevention#1

Open
locphamnguyen wants to merge 6 commits intoprajeesh-chavan:mainfrom
locphamnguyen:dev
Open

fix(security): comprehensive security hardening - auth, encryption, injection prevention#1
locphamnguyen wants to merge 6 commits intoprajeesh-chavan:mainfrom
locphamnguyen:dev

Conversation

@locphamnguyen
Copy link
Copy Markdown

@locphamnguyen locphamnguyen commented Apr 8, 2026

Summary

This PR addresses 9 critical and high severity security vulnerabilities found during a comprehensive security audit of the codebase.

Changes

  • Remove hardcoded credentials from all docker-compose files and init scripts. Secrets are now required via environment variables with ${VAR:?error} syntax
  • Add JWT authentication middleware to protect all API endpoints (/api/logs, /api/replay, /api/test, /api/providers, /api/analytics). Auth gracefully bypasses when JWT_SECRET is not set (dev mode)
  • Encrypt API keys at rest in MongoDB using AES-256-GCM via Mongoose hooks. Backward compatible with existing plaintext values
  • Fix NoSQL injection by escaping regex special characters in search queries
  • Fix CSV formula injection in log export by prefixing dangerous characters (=, +, -, @)
  • Hide error details in production responses (only show in development)
  • Bind Mongo-Express to localhost only (was exposed on 0.0.0.0:8081)
  • Add WebSocket authentication via socket.io middleware
  • Add /settings route to frontend (page existed but was not mounted)

New Files

  • backend/middlewares/auth.js — JWT verification middleware
  • backend/utils/encryption.js — AES-256-GCM encrypt/decrypt utility

Breaking Changes

  • A .env file with required secrets must be created before running docker-compose (see updated .env.example)
  • API endpoints now return 401 when JWT_SECRET is set but no valid token is provided

Test Plan

  • Verify docker-compose fails without .env file (expected: error about missing env vars)
  • Verify API returns 401 when JWT_SECRET is set and no token provided
  • Verify API works normally when JWT_SECRET is not set (dev bypass)
  • Verify Ollama provider detection still works
  • Verify log search with special regex characters (e.g., .*) doesn't crash
  • Verify CSV export escapes formula characters
  • Verify Settings page loads at /settings

🤖 Generated with Claude Code

locphamnguyen and others added 6 commits April 8, 2026 05:49
…cripts

- Replace hardcoded MongoDB passwords (admin/password123) with env vars
  in docker-compose.yml, docker-compose-with-ollama.yml, docker-compose.prod.yml
- Use ${VAR:?error} syntax to enforce required secrets at startup
- Update init-mongo.js to read credentials from environment variables
- Bind mongo-express to 127.0.0.1 only (was exposed on 0.0.0.0:8081)
- Update .env.example with all required variables and generation instructions
- Remove weak JWT secret fallback from docker-compose.prod.yml

BREAKING CHANGE: A .env file with required secrets must now be created
before running docker-compose. See .env.example for template.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create auth.js middleware with JWT token verification
- Apply authenticate middleware to all /api/* routes (logs, replay, test,
  providers, analytics) via routes/index.js
- Keep /api/health and /api/info as public endpoints
- Add WebSocket authentication via socket.io middleware in app.js
- Auth is gracefully bypassed when JWT_SECRET is not set (dev mode)
- Remove weak fallback JWT secret from config/env.js
- Add JWT_SECRET length validation (32+ chars) in production
- Support comma-separated CORS origins in FRONTEND_URL env var
- Update route JSDoc from @access Public to @access Private

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create encryption.js utility with encrypt/decrypt using AES-256-GCM
- Add Mongoose pre-save and pre-findOneAndUpdate hooks to auto-encrypt
  apiKey field before writing to database
- Add getDecryptedApiKey() instance method for runtime decryption
- Update providerController to use getDecryptedApiKey() instead of
  reading plaintext apiKey directly
- Support ENCRYPTION_KEY env var (64 hex chars) or derive from JWT_SECRET
- Backward compatible: unencrypted legacy values are returned as-is

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Escape regex special characters in search parameter before using in
  MongoDB $regex queries (logController.js)
- Hide error.message details in production responses across all controllers
  (logController, providerController, replayController)
- Only expose error details when NODE_ENV=development
- Add CSV formula injection prevention in log export (escape =+@- prefixes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Import SettingsPage component (already existed but was not mounted)
- Add Route for /settings path in standalone pages section

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create /v1/chat/completions endpoint that accepts standard OpenAI format
- Auto-detect provider from model name (gpt-* → openai, llama* → ollama, etc.)
- Support both non-streaming and SSE streaming responses
- Log all proxied requests to MongoDB with token count and cost tracking
- Broadcast new logs via WebSocket for real-time dashboard updates
- Add /v1/models endpoint aggregating models from all providers
- Add gemini and grok to Log model provider enum
- No JWT auth on proxy routes (clients use provider API keys directly)

Usage: Point any OpenAI-compatible client to http://<host>:3001/v1
  curl -X POST http://localhost:3001/v1/chat/completions \
    -d '{"model":"llama3","messages":[{"role":"user","content":"Hello"}]}'

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to harden the app against multiple security findings by requiring secrets via environment variables, enforcing JWT authentication for API/WebSockets, encrypting provider API keys at rest, and mitigating injection vectors in search/export paths.

Changes:

  • Require runtime secrets via .env/compose env var validation and tighten production config validation.
  • Add JWT-based authentication middleware for /api/* routes and Socket.IO connections.
  • Encrypt provider API keys in MongoDB and add injection protections (regex escaping for search, CSV formula escaping for export).

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
scripts/init-mongo.js Switch app-user creation to env-provided credentials during Mongo initialization.
frontend/src/App.jsx Mount /settings route in the frontend router.
docker/docker-compose.prod.yml Require key env vars (Mongo creds, MONGODB_URI, JWT_SECRET) in production compose.
docker/docker-compose-with-ollama.yml Remove hardcoded passwords, bind mongo-express to localhost, require env secrets.
docker-compose.yml Remove hardcoded passwords, bind mongo-express to localhost, require MONGODB_URI.
backend/utils/encryption.js Add AES-256-GCM encryption utilities for at-rest secret storage.
backend/routes/test.js Update route docs to reflect JWT protection.
backend/routes/replay.js Update route docs to reflect JWT protection.
backend/routes/proxy.js Add OpenAI-compatible /v1/* proxy routes.
backend/routes/providers.js Update route docs to reflect JWT protection.
backend/routes/logs.js Update route docs to reflect JWT protection.
backend/routes/index.js Apply JWT middleware to protected /api/* route groups.
backend/models/ProviderSettings.js Encrypt apiKey on write; add decryption helper method.
backend/models/Log.js Extend provider enum to include gemini and grok.
backend/middlewares/auth.js Add JWT authentication middleware + token generator helper.
backend/controllers/replayController.js Hide error details in non-development environments.
backend/controllers/proxyController.js Implement OpenAI-compatible proxy controller with logging + SSE streaming.
backend/controllers/providerController.js Decrypt stored provider API key for runtime use; hide error details in prod.
backend/controllers/logController.js Escape regex in search; escape CSV fields to prevent formula injection; hide error details in prod.
backend/config/env.js Remove JWT fallback secret, support multi-origin CORS, validate prod env var presence/strength.
backend/app.js Mount /v1 proxy routes and add Socket.IO JWT authentication middleware.
.env.example Expand example env vars for Mongo, JWT, encryption key, and mongo-express basic auth.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/init-mongo.js
Comment on lines +3 to 7
// Credentials are read from environment variables set in docker-compose

// Switch to the openllm-monitor database
db = db.getSiblingDB("openllm-monitor");

Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The init script always switches to openllm-monitor even though docker-compose exposes MONGO_INITDB_DATABASE. If someone changes MONGO_INITDB_DATABASE, this script will still create collections/users in openllm-monitor, which can lead to confusing auth/db state. Consider reading the DB name from env (with a safe default) so the init behavior matches compose configuration.

Copilot uses AI. Check for mistakes.
Comment on lines +301 to +307
return res.status(500).json({
error: {
message: error.message || "Internal server error",
type: "server_error",
code: "proxy_error",
},
});
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proxy error response returns error.message to clients unconditionally. Other controllers in this PR gate details behind config.isDevelopment to avoid leaking internal/provider error information in production; the proxy should follow the same pattern (and consider normalizing errors to OpenAI-style without revealing upstream messages).

Suggested change
return res.status(500).json({
error: {
message: error.message || "Internal server error",
type: "server_error",
code: "proxy_error",
},
});
const isDevelopment = process.env.NODE_ENV !== "production";
const errorResponse = {
error: {
message: "An internal error occurred while processing the request",
type: "server_error",
code: "proxy_error",
},
};
if (isDevelopment && error.message) {
errorResponse.error.details = error.message;
}
return res.status(500).json(errorResponse);

Copilot uses AI. Check for mistakes.
*/
ProviderSettingsSchema.methods.getDecryptedApiKey = function () {
if (!this.apiKey) return null;
return decrypt(this.apiKey);
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDecryptedApiKey() calls decrypt() directly; if decryption fails (e.g., malformed stored value, ENCRYPTION_KEY/JWT_SECRET changed, or a plaintext value that happens to match the delimiter pattern), it will throw and can break provider endpoints. Consider catching decryption errors here and returning null/original value (and logging) so a single bad row can't take down provider config reads.

Suggested change
return decrypt(this.apiKey);
if (!isEncrypted(this.apiKey)) {
return this.apiKey;
}
try {
return decrypt(this.apiKey);
} catch (error) {
console.warn(
`Failed to decrypt apiKey for provider "${this.provider}": ${error.message}`
);
return null;
}

Copilot uses AI. Check for mistakes.
Comment thread backend/routes/proxy.js
Comment on lines +15 to +22
/**
* @route POST /v1/chat/completions
* @desc OpenAI-compatible chat completion (routes to detected provider)
* @access Public (no JWT - clients authenticate via provider API keys)
*/
router.post("/chat/completions", (req, res) =>
proxyController.chatCompletion(req, res)
);
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The route docs say the proxy is public because "clients authenticate via provider API keys", but the handler does not authenticate the caller or require a client-supplied provider key; it will use the server-side configured provider credentials instead. If this is intended to be public, it needs an explicit auth mechanism (e.g., require an API key header and validate/limit it); otherwise it should be protected by JWT like the rest of the API.

Copilot uses AI. Check for mistakes.
Comment thread backend/app.js
@@ -158,6 +163,24 @@ class App {
/**
* Initialize WebSocket for real-time updates
*/ initializeWebSocket() {
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc block for initializeWebSocket is malformed (*/ initializeWebSocket() { is on the same line), which can break doc tooling/linters. Move initializeWebSocket() onto its own line after the comment block terminator.

Suggested change
*/ initializeWebSocket() {
*/
initializeWebSocket() {

Copilot uses AI. Check for mistakes.
Comment thread scripts/init-mongo.js
Comment on lines +13 to +28
if (!appPassword) {
print("WARNING: MONGO_APP_PASSWORD not set. Skipping app user creation.");
print("Set MONGO_APP_PASSWORD environment variable for secure setup.");
} else {
db.createUser({
user: appUser,
pwd: appPassword,
roles: [
{
role: "readWrite",
db: "openllm-monitor",
},
],
});
print("Created application user: " + appUser);
}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When MONGO_APP_PASSWORD is unset you skip user creation, and when it is set you may create a user with a non-default username. However the script later prints fixed "Created user: openllm-user" messages, which can be misleading in both cases. Consider printing the actual created username only when creation succeeds, and avoiding unconditional success messages.

Copilot uses AI. Check for mistakes.
Comment on lines 11 to 15
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: password123
MONGO_INITDB_DATABASE: openllm-monitor
MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME:-admin}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD:?Set MONGO_INITDB_ROOT_PASSWORD in .env}
MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE:-openllm-monitor}
volumes:
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as docker-compose.yml: the mounted init-mongo.js reads MONGO_APP_USERNAME/MONGO_APP_PASSWORD, but those variables are not passed into the mongodb service here, so the init script will skip app-user creation. If the goal is least-privilege credentials, add those env vars to mongodb.environment and update MONGODB_URI to use the app user instead of the root user.

Copilot uses AI. Check for mistakes.
Comment on lines 11 to 17
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME:-admin}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD:-password123}
MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE:-openllm-monitor}
MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME:?Set MONGO_INITDB_ROOT_USERNAME in .env}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD:?Set MONGO_INITDB_ROOT_PASSWORD in .env}
MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE:?Set MONGO_INITDB_DATABASE in .env}
volumes:
- mongodb_data:/data/db
- ./scripts/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The init script is mounted but MONGO_APP_USERNAME/MONGO_APP_PASSWORD are not provided to the mongodb container, so the script will skip app-user creation and the backend will continue to rely on whatever credentials are embedded in MONGODB_URI (often the root user). If you intend to remove root-user usage in production, pass the app-user env vars into mongodb.environment and switch MONGODB_URI to that user.

Copilot uses AI. Check for mistakes.
Comment thread backend/app.js
Comment on lines +166 to +181
// WebSocket authentication middleware
const jwt = require("jsonwebtoken");
if (config.jwtSecret) {
this.io.use((socket, next) => {
const token = socket.handshake.auth?.token;
if (!token) {
return next(new Error("Authentication required"));
}
try {
const decoded = jwt.verify(token, config.jwtSecret);
socket.user = decoded;
next();
} catch (err) {
return next(new Error("Invalid token"));
}
});
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Socket.IO auth middleware requires socket.handshake.auth.token whenever JWT_SECRET is set. The current frontend WebSocket client (frontend/src/services/websocket.js) does not send an auth token in the handshake, so WebSocket connections will fail in environments where JWT auth is enabled (including the provided prod compose). Consider either passing a JWT from the client during io(...) initialization, or making WS auth consistent with your API auth story (e.g., cookie-based session) to avoid breaking the dashboard.

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +66
// Check if the value looks encrypted (has the iv:authTag:ciphertext format)
const parts = encryptedText.split(":");
if (parts.length !== 3) {
// Not encrypted (legacy plaintext value), return as-is
return encryptedText;
}

const key = getEncryptionKey();
const [ivHex, authTagHex, ciphertext] = parts;

const iv = Buffer.from(ivHex, "hex");
const authTag = Buffer.from(authTagHex, "hex");
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(authTag);
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decrypt() treats any value containing exactly two ':' characters as encrypted. If a plaintext API key contains two colons (or other non-encrypted 3-part values), this will attempt to hex-decode and will throw, potentially crashing callers. Consider checking isEncrypted(encryptedText) (IV length + hex validation) before decrypting, and wrapping decrypt logic in a try/catch that falls back to returning the original value (or a controlled error).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 8 comments.

Comments suppressed due to low confidence (3)

docker-compose.yml:51

  • The backend container doesn’t receive JWT_SECRET or ENCRYPTION_KEY env vars. That means: (1) JWT auth will always be bypassed inside the container even if you set JWT_SECRET in .env; (2) API key encryption may throw if neither key is available. Pass through JWT_SECRET (and ideally ENCRYPTION_KEY) in backend.environment so the hardening features actually take effect in docker-compose dev.
    environment:
      MONGODB_URI: ${MONGODB_URI:?Set MONGODB_URI in .env}
      NODE_ENV: development
      PORT: 3001
      FRONTEND_URL: http://localhost:3000
      # Using host.docker.internal to connect to Ollama running on host
      OLLAMA_BASE_URL: http://host.docker.internal:11434
    depends_on:

docker/docker-compose.prod.yml:18

  • ./scripts/init-mongo.js is resolved relative to this compose file’s directory (docker/). There is no docker/scripts/init-mongo.js, so MongoDB init will fail when running docker compose -f docker/docker-compose.prod.yml .... Use a correct relative path (e.g. ../scripts/init-mongo.js) or an absolute path.
    volumes:
      - mongodb_data:/data/db
      - ./scripts/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
      - ./logs/mongodb:/var/log/mongodb

docker/docker-compose-with-ollama.yml:64

  • The backend container doesn’t receive JWT_SECRET or ENCRYPTION_KEY env vars. Even if they’re set in .env, they won’t be available inside the container unless explicitly passed through here. Add JWT_SECRET (and preferably ENCRYPTION_KEY) under backend.environment so auth and API-key encryption work as intended.
    environment:
      MONGODB_URI: ${MONGODB_URI:?Set MONGODB_URI in .env}
      NODE_ENV: development
      PORT: 3001
      FRONTEND_URL: http://localhost:3000
      OLLAMA_BASE_URL: http://ollama:11434
    depends_on:

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE:-openllm-monitor}
volumes:
- mongodb_data:/data/db
- ./scripts/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

./scripts/init-mongo.js is resolved relative to this compose file’s directory (docker/). There is no docker/scripts/init-mongo.js, so MongoDB init will fail when running docker compose -f docker/docker-compose-with-ollama.yml .... Use a correct relative path (e.g. ../scripts/init-mongo.js) or an absolute path.

Suggested change
- ./scripts/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
- ../scripts/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro

Copilot uses AI. Check for mistakes.
ME_CONFIG_BASICAUTH_PASSWORD: admin
ME_CONFIG_MONGODB_ADMINUSERNAME: ${MONGO_INITDB_ROOT_USERNAME:-admin}
ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_INITDB_ROOT_PASSWORD:?Set MONGO_INITDB_ROOT_PASSWORD in .env}
ME_CONFIG_MONGODB_URL: mongodb://${MONGO_INITDB_ROOT_USERNAME:-admin}:${MONGO_INITDB_ROOT_PASSWORD:?err}@mongodb:27017/
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ME_CONFIG_MONGODB_URL interpolates the raw root password into a MongoDB URI. If the password contains reserved URI characters (e.g. @, :, /, ?, #), mongo-express may fail to connect. Prefer providing a pre-escaped ME_CONFIG_MONGODB_URL via an env var (or constrain/encode the password for the URI).

Suggested change
ME_CONFIG_MONGODB_URL: mongodb://${MONGO_INITDB_ROOT_USERNAME:-admin}:${MONGO_INITDB_ROOT_PASSWORD:?err}@mongodb:27017/
ME_CONFIG_MONGODB_URL: ${ME_CONFIG_MONGODB_URL:?Set ME_CONFIG_MONGODB_URL in .env}

Copilot uses AI. Check for mistakes.
Comment thread backend/routes/proxy.js
Comment on lines +15 to +22
/**
* @route POST /v1/chat/completions
* @desc OpenAI-compatible chat completion (routes to detected provider)
* @access Public (no JWT - clients authenticate via provider API keys)
*/
router.post("/chat/completions", (req, res) =>
proxyController.chatCompletion(req, res)
);
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The route docs state clients authenticate via provider API keys, but these routes don't enforce any client authentication (no JWT, no proxy key, no per-request provider key handling). As implemented, access is effectively anonymous. Either require auth middleware here, or implement explicit client auth/authorization for the proxy.

Copilot uses AI. Check for mistakes.
Comment on lines +301 to +307
return res.status(500).json({
error: {
message: error.message || "Internal server error",
type: "server_error",
code: "proxy_error",
},
});
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This response returns error.message to the client on proxy failures. The PR aims to hide error details in production, but the proxy path still leaks upstream/provider error messages. Use config.isDevelopment gating (similar to other controllers) and return a generic message in production while logging the detailed error server-side.

Copilot uses AI. Check for mistakes.
MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE:-openllm-monitor}
MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME:?Set MONGO_INITDB_ROOT_USERNAME in .env}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD:?Set MONGO_INITDB_ROOT_PASSWORD in .env}
MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE:?Set MONGO_INITDB_DATABASE in .env}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This compose file doesn’t pass MONGO_APP_USERNAME / MONGO_APP_PASSWORD into the MongoDB container, but scripts/init-mongo.js expects them to create the application user. Add these to mongodb.environment (and consider making them required in prod) so the app can use a least-privileged DB user instead of the root account.

Suggested change
MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE:?Set MONGO_INITDB_DATABASE in .env}
MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE:?Set MONGO_INITDB_DATABASE in .env}
MONGO_APP_USERNAME: ${MONGO_APP_USERNAME:?Set MONGO_APP_USERNAME in .env}
MONGO_APP_PASSWORD: ${MONGO_APP_PASSWORD:?Set MONGO_APP_PASSWORD in .env}

Copilot uses AI. Check for mistakes.
FRONTEND_URL: ${FRONTEND_URL:-http://localhost:3000}
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://host.docker.internal:11434}
JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-key}
JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET in .env}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend container enables API-key encryption via backend/utils/encryption.js, and .env.example now introduces ENCRYPTION_KEY, but this env var is not passed into the backend container. If you want a dedicated data-encryption key (instead of deriving from JWT secret), pass ENCRYPTION_KEY here and consider validating it in backend/config/env.js for production.

Suggested change
JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET in .env}
JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET in .env}
ENCRYPTION_KEY: ${ENCRYPTION_KEY:-}

Copilot uses AI. Check for mistakes.
Comment thread scripts/init-mongo.js
Comment on lines 5 to +11
// Switch to the openllm-monitor database
db = db.getSiblingDB("openllm-monitor");

// Create a user for the application
db.createUser({
user: "openllm-user",
pwd: "openllm-password",
roles: [
{
role: "readWrite",
db: "openllm-monitor",
},
],
});
// Uses env vars: MONGO_APP_USERNAME, MONGO_APP_PASSWORD (set via docker-compose)
const appUser = process.env.MONGO_APP_USERNAME || "openllm-user";
const appPassword = process.env.MONGO_APP_PASSWORD;
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The init script hardcodes the database name (openllm-monitor) instead of using MONGO_INITDB_DATABASE. If the compose env sets a different database, the user/index initialization will run against the wrong DB. Consider reading process.env.MONGO_INITDB_DATABASE (with a safe default) and using it consistently for getSiblingDB(...) and role assignment.

Copilot uses AI. Check for mistakes.
Comment thread docker-compose.yml
ME_CONFIG_BASICAUTH_PASSWORD: admin
ME_CONFIG_MONGODB_ADMINUSERNAME: ${MONGO_INITDB_ROOT_USERNAME:-admin}
ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_INITDB_ROOT_PASSWORD:?Set MONGO_INITDB_ROOT_PASSWORD in .env}
ME_CONFIG_MONGODB_URL: mongodb://${MONGO_INITDB_ROOT_USERNAME:-admin}:${MONGO_INITDB_ROOT_PASSWORD:?err}@mongodb:27017/
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ME_CONFIG_MONGODB_URL interpolates the raw root password into a MongoDB URI. If the password contains reserved URI characters (e.g. @, :, /, ?, #), mongo-express may fail to connect. Prefer providing a pre-escaped ME_CONFIG_MONGODB_URL via an env var (or constrain/encode the password for the URI).

Suggested change
ME_CONFIG_MONGODB_URL: mongodb://${MONGO_INITDB_ROOT_USERNAME:-admin}:${MONGO_INITDB_ROOT_PASSWORD:?err}@mongodb:27017/
ME_CONFIG_MONGODB_URL: ${ME_CONFIG_MONGODB_URL:?Set ME_CONFIG_MONGODB_URL in .env}

Copilot uses AI. Check for mistakes.
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