Skip to content

Commit 8f04323

Browse files
committed
Fix AIVO security findings
1 parent 8d614e1 commit 8f04323

31 files changed

Lines changed: 738 additions & 84 deletions

‎.project/spec.yaml‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ execution:
101101
- name: Chat Frontend
102102
type: custom
103103
class: webapp
104-
start_command: export PROXY_PREFIX && cd /project/code && uvicorn --log-level info --reload frontend.server:app --reload-dir frontend --port 7070 --host 0.0.0.0 --timeout-graceful-shutdown 10
104+
start_command: export PROXY_PREFIX NIM_ANYWHERE_API_KEY && cd /project/code && uvicorn --log-level info --reload frontend.server:app --reload-dir frontend --port 7070 --host 0.0.0.0 --timeout-graceful-shutdown 10
105105
health_check_command: curl localhost:7070/healthz > /dev/null
106106
stop_command: ps aux | grep frontend.server:app | grep -v grep | awk '{print $2}' | xargs kill
107107
user_msg: ""
@@ -117,7 +117,7 @@ execution:
117117
- name: Chain Server
118118
type: custom
119119
class: webapp
120-
start_command: export PROXY_PREFIX && export NGC_API_KEY && cd /project/code && APP_LOG_LEVEL=DEBUG uvicorn --log-level info chain_server.server:app --port 3030 --host 0.0.0.0 --timeout-graceful-shutdown 10 --reload-exclude 'frontend/*' --reload-exclude 'evaluation/*' --reload-include '*.html' --reload-include '*.css' --reload-include '*.yaml' --reload-include '*.js' --reload --reload-include reload
120+
start_command: export PROXY_PREFIX NGC_API_KEY NIM_ANYWHERE_API_KEY && cd /project/code && APP_LOG_LEVEL=DEBUG uvicorn --log-level info chain_server.server:app --port 3030 --host 0.0.0.0 --timeout-graceful-shutdown 10 --reload-exclude 'frontend/*' --reload-exclude 'evaluation/*' --reload-include '*.html' --reload-include '*.css' --reload-include '*.yaml' --reload-include '*.js' --reload --reload-include reload
121121
health_check_command: ps aux |grep -v grep | grep chain_server.server:app
122122
stop_command: ps aux | grep chain_server.server:app | grep -v grep | awk '{print $2}' | xargs kill
123123
user_msg: ""
@@ -135,7 +135,7 @@ execution:
135135
class: webapp
136136
start_command: |-
137137
cd /project/code/tutorial_app && \
138-
export PROXY_PREFIX && \
138+
export PROXY_PREFIX NIM_ANYWHERE_API_KEY && \
139139
streamlit run app.py --server.baseUrlPath=$PROXY_PREFIX
140140
health_check_command: curl -I http://localhost:8501
141141
stop_command: |
@@ -157,6 +157,8 @@ execution:
157157
secrets:
158158
- variable: NGC_API_KEY
159159
description: NGC Personal Key from https://org.ngc.nvidia.com/setup/personal-keys
160+
- variable: NIM_ANYWHERE_API_KEY
161+
description: A random 32+ character secret used to authenticate the apps and Redis (for example, openssl rand -hex 32)
160162
mounts:
161163
- type: project
162164
target: /project/

‎README.md‎

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ to help our partners and customers build effective RAG pipeline with
2222
ease.
2323

2424
NIM Anywhere contains all the tooling required to start integrating NIMs
25-
for RAG. It natively scales out to full-sized labs and up to production
25+
for RAG. It natively scales out to full-sized labs and up to production
2626
environments. This is great news for building a RAG architecture and
2727
easily adding NIMs as needed. If you're unfamiliar with RAG, it
2828
dynamically retrieves relevant external information during inference
@@ -399,14 +399,15 @@ section.
399399

400400
## Configure this project
401401

402-
The project must be configured to use your NGC personal key.
402+
The project must be configured with an application secret and your NGC
403+
personal key.
403404

404405
<details>
405406
<summary>
406407
<b>Expand this section for a details on configuring this project.</b>
407408
</summary>
408409

409-
1. Before running for the first time, your NGC personal key must be
410+
1. Before running for the first time, the project secrets must be
410411
configured in Workbench. This is done using the *Environment* tab
411412
from the left-hand panel.
412413

@@ -416,6 +417,15 @@ The project must be configured to use your NGC personal key.
416417
entry. Press *Configure* and provide the personal key for NGC that
417418
was generated earlier.
418419

420+
3. Configure *NIM_ANYWHERE_API_KEY* with a random secret of at least 32
421+
characters. For example, generate one with `openssl rand -hex 32`.
422+
This key protects the frontend, chain server, tutorial, sessions,
423+
and Redis. Do not commit it to the repository.
424+
425+
The frontend uses HTTP Basic authentication with `nim-anywhere` as
426+
the username and this secret as the password. The tutorial asks for
427+
the secret directly.
428+
419429
</details>
420430

421431
## Start This Project
@@ -504,8 +514,12 @@ without GPUs.
504514
505515
5. Once the *Chain Server* is up, the *Chat Frontend* can be started.
506516
Starting the interface will automatically open it in a browser
507-
window. If you are running any local NIMs, you can edit the config
508-
to connect to them via the *Chat Frontend*
517+
window. Sign in with `nim-anywhere` as the username and your
518+
*NIM_ANYWHERE_API_KEY* as the password. If you are running any local
519+
NIMs, you can select their preconfigured model names through the
520+
*Chat Frontend*. Service URLs, credentials, and data-store addresses
521+
can only be changed in the project configuration, not through the
522+
web UI.
509523
510524
![NIM Anywhere Frontend](.static/_static/na_frontend.png)
511525
@@ -654,6 +668,11 @@ milvus:
654668
# Type: string
655669
collection_name: collection_1
656670
671+
# Milvus authentication token in username:password format.
672+
# ENV Variables: APP_MILVUS__TOKEN
673+
# Type: string, null
674+
token: ~
675+
657676
658677
log_level:
659678

‎code/chain_server/chain.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from langchain_core.runnables.history import RunnableWithMessageHistory
2727
from langchain_milvus.vectorstores.milvus import Milvus
2828
from langchain_nvidia_ai_endpoints import ChatNVIDIA, NVIDIAEmbeddings, NVIDIARerank
29+
from nim_anywhere_security import validate_session_id
2930
from pydantic import BaseModel
3031

3132
from . import prompts
@@ -40,7 +41,7 @@
4041
)
4142
vector_store = Milvus(
4243
embedding_function=embedding_model,
43-
connection_args={"uri": app_config.milvus.url},
44+
connection_args=app_config.milvus.connection_args(),
4445
collection_name=app_config.milvus.collection_name,
4546
auto_id=True,
4647
)
@@ -125,9 +126,15 @@ class ChainInputs(BaseModel):
125126
ChainOutputs = str
126127

127128

129+
def _get_message_history(session_id: str) -> RedisChatMessageHistory:
130+
"""Return history only for an unmodified server-signed session id."""
131+
validated_session_id = validate_session_id(session_id)
132+
return RedisChatMessageHistory(validated_session_id, url=str(app_config.redis_dsn))
133+
134+
128135
my_chain = RunnableWithMessageHistory(
129136
my_chain,
130-
lambda session_id: RedisChatMessageHistory(session_id, url=str(app_config.redis_dsn)),
137+
_get_message_history,
131138
input_messages_key="question",
132139
output_messages_key="output",
133140
history_messages_key="history",

‎code/chain_server/configuration.py‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
Values specified as environment variables will take precedence over all values from files.
4949
5050
"""
51+
5152
import logging
5253
import os
5354
from enum import Enum
@@ -86,6 +87,17 @@ class MilvusConfig(BaseModel):
8687
description="The host machine running Milvus vector DB.",
8788
)
8889
collection_name: str = Field("collection_1", description="The name of the Milvus collection.")
90+
token: Optional[str] = Field(
91+
None,
92+
description="Milvus authentication token in username:password format.",
93+
)
94+
95+
def connection_args(self) -> dict[str, str]:
96+
"""Build authenticated Milvus connection arguments."""
97+
arguments = {"uri": self.url}
98+
if self.token:
99+
arguments["token"] = self.token
100+
return arguments
89101

90102

91103
class LLMModelConfig(BaseModel):
@@ -145,7 +157,7 @@ class EmbeddingModelConfig(BaseModel):
145157
]
146158

147159

148-
class Configuration(BaseConfig):
160+
class Configuration(BaseConfig): # type: ignore[metaclass]
149161
"""Configuration for this microservice."""
150162

151163
# configuration fields

‎code/chain_server/prompts.py‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,14 @@
2121
PromptTemplate,
2222
)
2323

24-
CONDENSE_QUESTION_TEMPLATE = PromptTemplate.from_template(
25-
"""Given a chat history and the latest user question
24+
CONDENSE_QUESTION_TEMPLATE = PromptTemplate.from_template("""Given a chat history and the latest user question
2625
which might reference context in the chat history, formulate a standalone question
2726
which can be understood without the chat history. Do NOT answer the question,
2827
just reformulate it if needed and otherwise return it as is. Don't frame your response.
2928
Chat History:
3029
{history}
3130
Follow Up question: {question}
32-
Standalone question:"""
33-
)
31+
Standalone question:""")
3432

3533

3634
# Primary Chat Prompt template

‎code/chain_server/server.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
from langserve import add_routes
2424
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
2525

26+
from nim_anywhere_security import AuthenticationMiddleware
27+
2628
from . import errors
2729
from .chain import my_chain # type: ignore
2830

@@ -34,6 +36,7 @@
3436
root_path=PROXY_PREFIX or "",
3537
middleware=[Middleware(errors.ErrorHandlerMiddleware)],
3638
)
39+
app.add_middleware(AuthenticationMiddleware)
3740

3841

3942
add_routes(

‎code/frontend/_assets/index.html‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
<head>
2020
<title>NVIDIA NIM Anywhere</title>
2121
<link rel="stylesheet" href="static/css/style.css">
22-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css">
23-
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.9.0/dist/mermaid.min.js"></script>
2422
<script src="static/js/page_router.js"></script>
2523
<script src="static/js/events.js"></script>
2624
</head>

‎code/frontend/configuration.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class LogLevels(Enum):
3737
CRITICAL = "CRITICAL"
3838

3939

40-
class Configuration(BaseConfig):
40+
class Configuration(BaseConfig): # type: ignore[metaclass]
4141
"""Configuration for this microservice."""
4242

4343
# configuration fields

‎code/frontend/mermaid.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@
1818
import gradio as gr
1919

2020
HEAD = """
21-
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.9.0/dist/mermaid.min.js"></script>
22-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css">
21+
<script
22+
src="https://cdn.jsdelivr.net/npm/mermaid@10.9.0/dist/mermaid.min.js"
23+
integrity="sha384-6F4Ibv/ylL12O35KFWTeGTHuBKDz5L6yjKsgv3QHQ8s4NTqlDXq7kMlYXGs7MHFc"
24+
crossorigin="anonymous"
25+
referrerpolicy="no-referrer"
26+
></script>
2327
"""
2428

2529
_ONLOAD = """

‎code/frontend/pages/chat/__init__.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515

1616
"""The gradio chat app in the frontend."""
1717

18-
import uuid
1918
from pathlib import Path
2019
from typing import AsyncGenerator, Any
2120

2221
import gradio as gr
2322
from httpx import ConnectError, HTTPStatusError
2423
from langserve import RemoteRunnable
24+
from nim_anywhere_security import bearer_headers, create_session_id
2525

2626
from ...common import IMG_DIR, THEME, USE_KB_INITIAL, USE_RERANKER_INITIAL
2727
from ...configuration import config
@@ -38,7 +38,7 @@
3838
with open(_JS_FILE, "r", encoding="UTF-8") as js_file:
3939
_HEAD = "<script>" + js_file.read() + "</script>"
4040
_ONLOAD = "async()=>{ registerHandlers(); chatResize(); }"
41-
_CHAIN: RemoteRunnable[dict[str, Any], str] = RemoteRunnable(str(config.chain_url))
41+
_CHAIN: RemoteRunnable[dict[str, Any], str] = RemoteRunnable(str(config.chain_url), headers=bearer_headers())
4242

4343

4444
# web ui definition
@@ -65,8 +65,8 @@
6565

6666
@page.load(outputs=session_id)
6767
def sid_generator() -> str:
68-
"""Generate a new session id."""
69-
return str(uuid.uuid4())
68+
"""Generate a new signed session id."""
69+
return create_session_id()
7070

7171
@gr.on(
7272
triggers=[msg.submit, submit.click],

0 commit comments

Comments
 (0)