Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
34dc3a2
fix(security): bind Ray dashboard to localhost by default (C3)
EnjoyBacon7 Jun 15, 2026
63a857a
fix(security): SSRF guard on document image-URL captioning (H2)
EnjoyBacon7 Jun 15, 2026
0515f70
fix(security): require MinIO credentials via env, drop minioadmin def…
EnjoyBacon7 Jun 15, 2026
8914dfb
fix(security): run app and Ray containers as non-root (H4)
EnjoyBacon7 Jun 15, 2026
47b8cd3
fix(security): fail-fast on missing CHAINLIT_AUTH_SECRET (H7)
EnjoyBacon7 Jun 15, 2026
e3c7eac
fix(security): neutralize control tokens in RAG context (H8)
EnjoyBacon7 Jun 15, 2026
0164b82
fix(security): remove weak default DB password and AUTH_TOKEN (M2/M3)
EnjoyBacon7 Jun 15, 2026
0e6e783
fix(security): add path-tiered request rate limiting (M6)
EnjoyBacon7 Jun 15, 2026
818d544
fix(security): stop leaking stack traces / FS paths to clients (M7)
EnjoyBacon7 Jun 15, 2026
221f8ed
fix(security): bound parser fan-out/recursion/page counts (M8)
EnjoyBacon7 Jun 15, 2026
97c624e
fix(security): require exp + jti and block replay on back-channel log…
EnjoyBacon7 Jun 15, 2026
1bfca31
fix(security): default logs to INFO and drop user queries from logs (…
EnjoyBacon7 Jun 15, 2026
5416590
fix(security): enforce token limit in RAG mode and bound n/best_of (M12)
EnjoyBacon7 Jun 15, 2026
d66cf02
fix(security): cap partitions a non-admin user may create (M13)
EnjoyBacon7 Jun 15, 2026
c2fde13
fix(security): block cross-site logout CSRF (N3)
EnjoyBacon7 Jun 15, 2026
6bc898e
fix(security): scope surrounding-chunk lookup to the source partition…
EnjoyBacon7 Jun 15, 2026
24efaa6
fix(security): move Helm DB password to Secret, drop weak default (N7…
EnjoyBacon7 Jun 15, 2026
c8f2d47
fix(security): add default-deny NetworkPolicy to Helm chart (N12, C3 …
EnjoyBacon7 Jun 15, 2026
b8002ef
fix(security): drop seccomp:unconfined from Milvus containers (N11)
EnjoyBacon7 Jun 15, 2026
645128d
fix(security): don't bind-mount source / auto-reload in prod (N8)
EnjoyBacon7 Jun 15, 2026
5caec50
fix(security): restrict metrics stack exposure (N9)
EnjoyBacon7 Jun 15, 2026
229503b
fix(security): don't put session token in UI file URLs under OIDC (N13)
EnjoyBacon7 Jun 15, 2026
2b34a0d
fix(security): add clock-skew leeway and nbf check to OIDC JWT verifi…
EnjoyBacon7 Jun 15, 2026
d7fc313
fix(security): pin image tags instead of latest (N10)
EnjoyBacon7 Jun 15, 2026
74de823
fix(security): bump Starlette to 0.47.3 and FastAPI to 0.116.2 (deps)
EnjoyBacon7 Jun 15, 2026
8ea723c
fix(security): make SVG rendering external-fetch guard explicit (deps…
EnjoyBacon7 Jun 15, 2026
355a630
style: fix import ordering in new security modules (ruff I001)
EnjoyBacon7 Jun 15, 2026
cdb3edc
test(security): add required exp to back-channel logout test token (M9)
EnjoyBacon7 Jun 15, 2026
ee86fd4
style: apply ruff format to app_front.py
EnjoyBacon7 Jun 15, 2026
4d8bca0
build(security): depend on limits directly instead of slowapi (M6)
EnjoyBacon7 Jun 15, 2026
563907a
docs: trim verbose comments in security changes
EnjoyBacon7 Jun 15, 2026
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
23 changes: 22 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ VLM_MODEL=

# Set to true, it will mount chainlit chat ui to the fastapi app (Default: true)
## WITH_CHAINLIT_UI=true
# Signs Chainlit UI session cookies. Required when the UI is enabled (unless
# ALLOW_NO_AUTH=true). Generate: python -c "import secrets; print(secrets.token_hex(32))"
# CHAINLIT_AUTH_SECRET=

# UI language for Chainlit and Indexer UI (e.g. en-US, fr).
# When unset, the browser language is used, with en-US as the final fallback.
Expand All @@ -51,6 +54,14 @@ RERANKER_MODEL=Alibaba-NLP/gte-multilingual-reranker-base # or jinaai/jina-reran
# API accepts natively. Override to restrict (e.g. to ".wav" only for vLLM deployments).
# TRANSCRIBER_DIRECT_UPLOAD_SUFFIXES=.wav|.flac|.ogg|.mp3|.mp4|.m4a|.webm|.mpeg|.mpga

# MinIO (used by Milvus) — required, no default. e.g. `openssl rand -hex 16`.
MINIO_ACCESS_KEY=
MINIO_SECRET_KEY=

# PostgreSQL — required, no default. e.g. `openssl rand -hex 16`.
POSTGRES_PASSWORD=
# POSTGRES_USER=root

# Prompts
PROMPTS_DIR=../prompts/example1

Expand Down Expand Up @@ -83,8 +94,18 @@ API_BASE_URL='http://X.X.X.X:APP_PORT' # Base URL of your FastAPI backe
# WEBSEARCH_TOP_K=5 # Number of web results to include (default: 5)
# WEBSEARCH_LANG=fr-FR # Search language/market (default: fr-FR)

# Max partitions a non-admin user may own (-1 = unlimited; admins bypass).
# MAX_PARTITIONS_PER_USER=100

# Rate limiting (per-worker moving window, keyed on user id then client IP)
# RATE_LIMIT_ENABLED=true
# RATE_LIMIT_DEFAULT=300/minute # all paths except those below
# RATE_LIMIT_AUTH=20/minute # /auth/* (login/callback/logout)
# RATE_LIMIT_CHAT=60/minute # /v1/* (chat completions, tools)

# LOGGING
LOG_LEVEL=DEBUG # See possible values https://loguru.readthedocs.io/en/stable/api/logger.html
# INFO by default; DEBUG persists user queries and request data to logs.
LOG_LEVEL=INFO # See possible values https://loguru.readthedocs.io/en/stable/api/logger.html

# SERVER
# Set the preferred URL scheme for generated URLs (e.g., task_status_url).
Expand Down
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ ENV HF_HUB_CACHE=${HF_HUB_CACHE:-/app/model_weights/hub}
# Set workdir for uv
WORKDIR /app

# Set HOME before installing so uv's Python and cache land under /app (owned by
# the non-root user below), not /root.
ENV HOME=/app

# Install uv & setup venv
COPY pyproject.toml uv.lock ./
RUN pip3 install uv && \
Expand All @@ -46,4 +50,13 @@ COPY prompts/ /app/prompts/
COPY conf/ /app/conf/
ENV PYTHONPATH=/app/openrag/
ENV APP_iPORT=${APP_iPORT:-8080}

# Run as non-root. The app writes under /app (venv, data, logs, model_weights),
# so the user owns /app.
RUN groupadd --gid 10001 app \
&& useradd --uid 10001 --gid 10001 --home-dir /app --no-create-home app \
&& mkdir -p /app/data /app/logs /app/model_weights \
&& chown -R 10001:10001 /app
USER 10001:10001

ENTRYPOINT ../entrypoint.sh
14 changes: 13 additions & 1 deletion Dockerfile.ray
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ ENV HF_HUB_CACHE=${HF_HUB_CACHE:-/app/model_weights/hub}
# Set workdir for uv
WORKDIR /app

# Set HOME before installing so uv's Python and cache land under /app (owned by
# the non-root user below), not /root.
ENV HOME=/app

# Install uv & setup venv
COPY pyproject.toml uv.lock ./
RUN pip3 install uv && \
Expand All @@ -52,4 +56,12 @@ COPY conf/ /app/conf/

RUN ln -s /app/.venv/bin/ray /usr/local/bin/ray

ENV PYTHONPATH=/app/openrag/
ENV PYTHONPATH=/app/openrag/

# Run as non-root. The app writes under /app (venv, data, logs, model_weights),
# so the user owns /app.
RUN groupadd --gid 10001 app \
&& useradd --uid 10001 --gid 10001 --home-dir /app --no-create-home app \
&& mkdir -p /app/data /app/logs /app/model_weights \
&& chown -R 10001:10001 /app
USER 10001:10001
29 changes: 29 additions & 0 deletions charts/openrag-stack/templates/networkpolicy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{{- if .Values.networkPolicy.enabled }}
# Default-deny ingress for all pods, allowing only same-namespace traffic and
# the public HTTP ports. Keeps the Ray dashboard (8265), Postgres, Milvus, etc.
# off-limits from outside the namespace.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "openrag-stack.fullname" . }}-default-deny
labels:
{{- include "openrag-stack.labels" . | nindent 4 }}
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
# 1. Allow all traffic originating from pods in this namespace.
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: {{ .Release.Namespace }}
# 2. Allow the public HTTP ports from any source (e.g. the Ingress controller).
{{- with .Values.networkPolicy.externalPorts }}
- ports:
{{- range . }}
- port: {{ . }}
protocol: TCP
{{- end }}
{{- end }}
{{- end }}
31 changes: 26 additions & 5 deletions charts/openrag-stack/values.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# === Network isolation ===
# Default-deny ingress; allow only same-namespace traffic and the public HTTP
# ports below. Isolates the Ray dashboard, Postgres, Milvus, etc.
networkPolicy:
enabled: true
# Ports reachable from outside the namespace (e.g. via the Ingress controller).
externalPorts:
- 8080 # openrag API/UI
- 3000 # indexer-ui

# === Global shared persistence ===
persistence:
enabled: true
Expand All @@ -20,14 +30,16 @@ ray:
gpu-role: serving
image:
repository: ghcr.io/linagora/openrag-ray
tag: latest
# Pin to a release tag (ideally a digest) for reproducible deploys.
tag: "1.1.11"

# === PostgreSQL (bitnami) ===
postgresql:
enabled: true
auth:
username: &pgUser root
password: &pgPass root_password
# Override at install, e.g. --set postgresql.auth.password=$(openssl rand -hex 16)
password: &pgPass "CHANGE_ME_STRONG_PASSWORD"
primary:
persistence:
enabled: true
Expand Down Expand Up @@ -72,6 +84,8 @@ vllm:
llmModelName: &llmModel "RedHatAI/Mistral-Small-3.1-24B-Instruct-2503-quantized.w8a8"
servingEngineSpec:
enableEngine: true
# Pin the per-model `tag: "latest"` entries below to specific releases before
# production (see vlm, pinned to v0.11.2).
modelSpec:
- name: "embedder"
repository: "vllm/vllm-openai"
Expand Down Expand Up @@ -182,6 +196,8 @@ reranker:
servicePort: &rerankerPort 7997
image:
repository: michaelf34/infinity
# Operator-supplied serving image: pin to a specific infinity release tag
# (or digest) before production use rather than tracking latest.
tag: latest
nodeSelector:
gpu-role: serving
Expand Down Expand Up @@ -209,7 +225,7 @@ reranker:
indexerUi:
enabled: true

image: "linagoraai/indexer-ui:latest"
image: "linagoraai/indexer-ui:1.1.11"
imagePullPolicy: IfNotPresent

replicaCount: 1
Expand All @@ -232,7 +248,8 @@ indexerUi:
openrag:
image:
repository: linagoraai/openrag
tag: latest
# Pin to a release tag (ideally a digest) for reproducible deploys.
tag: "1.1.11"
service:
type: ClusterIP
port: 8080
Expand Down Expand Up @@ -287,7 +304,8 @@ env:
POSTGRES_HOST: "{{ .Release.Name }}-postgresql"
POSTGRES_PORT: *pgPort
POSTGRES_USER: *pgUser
POSTGRES_PASSWORD: *pgPass
# POSTGRES_PASSWORD is a secret — defined under env.secrets, not here in the
# (world-readable) ConfigMap.

# Embedder
EMBEDDER_MODEL_NAME: *embedderModel
Expand Down Expand Up @@ -331,3 +349,6 @@ env:
TRANSCRIBER_API_KEY: "EMPTY"
AUTH_TOKEN: "sk-xxxx" # API KEY for OpenRAG
HF_TOKEN: "hf_xxxx" # HuggingFace token
# DB password lives in the Secret (not the ConfigMap). Reuses the same
# value as postgresql.auth.password via the *pgPass anchor.
POSTGRES_PASSWORD: *pgPass
3 changes: 2 additions & 1 deletion cluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ auth:

head_start_ray_commands:
- uv run ray stop
- uv run ray start --head --dashboard-host 0.0.0.0 --dashboard-port ${RAY_DASHBOARD_PORT:-8265} --node-ip-address ${HEAD_NODE_IP} --autoscaling-config=~/ray_bootstrap_config.yaml
# Dashboard is unauthenticated (CVE-2023-48022); bind to localhost, override with RAY_DASHBOARD_HOST.
- uv run ray start --head --dashboard-host ${RAY_DASHBOARD_HOST:-127.0.0.1} --dashboard-port ${RAY_DASHBOARD_PORT:-8265} --node-ip-address ${HEAD_NODE_IP} --autoscaling-config=~/ray_bootstrap_config.yaml
worker_start_ray_commands:
- uv run ray stop
- uv run ray start --address ${HEAD_NODE_IP:-10.0.0.1}:6379
16 changes: 13 additions & 3 deletions conf/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ rdb:
host: rdb
port: 5432
user: root
password: "root_password"
# No default; set via the POSTGRES_PASSWORD env var.
password: ""
default_file_quota: -1

# --- Reranker ---
Expand All @@ -90,8 +91,9 @@ map_reduce:

# --- Logging ---
# Env: LOG_LEVEL
# INFO by default; DEBUG persists user queries and request data to logs.
verbose:
level: DEBUG
level: INFO

# --- Server ---
# Env: PREFERRED_URL_SCHEME
Expand Down Expand Up @@ -126,9 +128,17 @@ prompts:
loader:
# Env: IMAGE_CAPTIONING, IMAGE_CAPTIONING_URL, SAVE_MARKDOWN
image_captioning: true
image_captioning_url: true
# Off by default (captioning remote image URLs is an SSRF surface). When on,
# fetches are SSRF-guarded and proxied in-process. See loaders/base.py.
image_captioning_url: false
save_markdown: false

# Bound fan-out/recursion/page counts during ingestion (parser-bomb guard).
max_attachments: 50 # attachments processed per .eml
max_eml_depth: 3 # nested .eml-in-.eml recursion depth
max_archive_entries: 2000 # embedded parts iterated per docx/pptx (zip fan-out)
max_pdf_pages: 2000 # pages processed per PDF

mimetypes:
text/plain: .txt
text/markdown: .md
Expand Down
11 changes: 6 additions & 5 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ include:
- ${TRANSCRIBER_COMPOSE:-extern/dummy.yaml}

x-openrag: &openrag_template
image: linagoraai/openrag:latest
image: linagoraai/openrag:1.1.11
build:
context: .
dockerfile: Dockerfile
Expand All @@ -22,9 +22,10 @@ x-openrag: &openrag_template
- ${DATA_VOLUME:-./data}:/app/data
- ./i8n:/app/openrag/.chainlit/translations
- ${MODEL_WEIGHTS_VOLUME:-~/.cache/huggingface}:/app/model_weights # Model weights for RAG
- ./openrag:/app/openrag # For dev mode
# Dev only: mounts source over the image. Keep off in production.
# - ./openrag:/app/openrag # For dev mode
- /$SHARED_ENV:/ray_mount/.env # Shared environment variables
- ./logs:/app/logs # For dev mode
- ./logs:/app/logs
ports:
- ${APP_PORT:-8080}:${APP_iPORT:-8080}
# Bind the Ray dashboard to localhost only: it has no authentication and
Expand Down Expand Up @@ -75,7 +76,7 @@ x-vllm: &vllm_template
services:
# OpenRAG Indexer UI
indexer-ui:
image: linagoraai/indexer-ui:latest
image: linagoraai/indexer-ui:1.1.11
build:
context: ./extern/indexer-ui
dockerfile: Dockerfile
Expand Down Expand Up @@ -135,7 +136,7 @@ services:
rdb:
image: postgres:15
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-root_password}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in your .env}
- POSTGRES_USER=${POSTGRES_USER:-root}
volumes:
- ${DB_VOLUME:-./db}:/var/lib/postgresql/data
Expand Down
11 changes: 6 additions & 5 deletions docs/assets/compose_ollama_cpu.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ x-openrag: &openrag_template
- ./ray_mount/logs:/app/logs
ports:
- 8090:8080
- 8265:8265 # Disable when in cluster mode
# Localhost only: Ray dashboard/Jobs API is unauthenticated (CVE-2023-48022). Disable when in cluster mode
- 127.0.0.1:${RAY_DASHBOARD_PORT:-8265}:8265
networks:
default:
aliases:
Expand All @@ -16,7 +17,7 @@ x-openrag: &openrag_template
- .env
environment:
- APP_PORT=8090
- AUTH_TOKEN=OpenRAG
- AUTH_TOKEN=${AUTH_TOKEN:?Set a strong AUTH_TOKEN in your .env}
- RERANKER_ENABLED=false
- MARKER_MAX_PROCESSES=1
- INDEXERUI_COMPOSE_FILE=true # Does not serve any purpose but needs to be enabled until PR is merged
Expand All @@ -38,7 +39,7 @@ services:
rdb:
image: postgres:15
environment:
- POSTGRES_PASSWORD=root
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in your .env}
- POSTGRES_USER=root
volumes:
- ./db:/var/lib/postgresql/data
Expand Down Expand Up @@ -72,8 +73,8 @@ services:
minio:
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
environment:
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:?Set MINIO_ACCESS_KEY in your .env}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?Set MINIO_SECRET_KEY in your .env}
volumes:
- ./volumes/minio:/minio_data
command: minio server /minio_data --console-address ":9001"
Expand Down
15 changes: 11 additions & 4 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
#!/bin/bash
ENV_ARG=""
ENV_ARGS=()
if [[ -n "${SHARED_ENV}" ]]; then
ENV_ARG="--env-file=${SHARED_ENV}"
ENV_ARGS+=("--env-file=${SHARED_ENV}")
fi

if [[ "${ENABLE_RAY_SERVE}" == "true" ]]; then
echo "🔁 Starting with Ray Serve..."
uv run $ENV_ARG api.py
uv run "${ENV_ARGS[@]}" api.py
else
echo "🚀 Starting with Uvicorn..."
uv run --no-dev $ENV_ARG uvicorn api:app --host 0.0.0.0 --port ${APP_iPORT:-8080} --reload --workers ${API_NUM_WORKERS:-1}
# --reload is dev-only (set UVICORN_RELOAD=true) and needs a single worker.
RELOAD_ARGS=()
WORKERS="${API_NUM_WORKERS:-1}"
if [[ "${UVICORN_RELOAD}" == "true" ]]; then
RELOAD_ARGS+=("--reload")
WORKERS="1"
fi
uv run --no-dev "${ENV_ARGS[@]}" uvicorn api:app --host 0.0.0.0 --port "${APP_iPORT:-8080}" "${RELOAD_ARGS[@]}" --workers "${WORKERS}"
fi
9 changes: 7 additions & 2 deletions openrag/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@
from fastapi.openapi.utils import get_openapi
from fastapi.responses import JSONResponse, RedirectResponse

ray.init(dashboard_host="0.0.0.0")
# Bind the Ray dashboard to localhost; it's unauthenticated (CVE-2023-48022).
# Override with RAY_DASHBOARD_HOST (e.g. behind an auth proxy).
ray.init(dashboard_host=os.environ.get("RAY_DASHBOARD_HOST", "127.0.0.1"))

# Apply noqa: E402 to ignore "module level import not at top of file" cause ray.init has to be called first

# flake8: noqa: E402


from components.auth.middleware import AuthMiddleware
from components.rate_limit import RateLimitMiddleware
from routers.actors import router as actors_router
from routers.auth import router as auth_router
from routers.download import router as download_router
Expand Down Expand Up @@ -213,7 +216,9 @@ async def dispatch(self, request: Request, call_next):
return response


# Register middlewares (order matters - last added runs first)
# Order matters (last added = outermost). RateLimitMiddleware is added first so
# it runs after AuthMiddleware sets request.state.user and can key on the user.
app.add_middleware(RateLimitMiddleware)
app.add_middleware(AuthMiddleware, get_vectordb=get_vectordb)
app.add_middleware(TokenRedactingMiddleware)
app.add_middleware(MonitoringMiddleware)
Expand Down
Loading
Loading