Skip to content

Add RAG sensitive data exposure#19

Open
luks-santos wants to merge 6 commits into
SasanLabs:mainfrom
luks-santos:rag-sensitive-data-exposure
Open

Add RAG sensitive data exposure#19
luks-santos wants to merge 6 commits into
SasanLabs:mainfrom
luks-santos:rag-sensitive-data-exposure

Conversation

@luks-santos
Copy link
Copy Markdown

@luks-santos luks-santos commented May 17, 2026

Overview

This PR implements a RAG Sensitive Data Exposure lab.

The implementation uses a RagDataExposureVectorStore, which combines FAISS with SQLite to store vectors by namespace (one namespace per level) and persist them for later retrieval.

In the interface, it adds a facade that displays the retrieved segments and validates the leaked secret against the model's responses.

How It Works

  1. Populate FAISS and SQLite with the content of each chunk, saving in SQLite the vector index (vector_id) for each namespace.
  2. Based on the user prompt, search FAISS by similarity to find the most likely indexes.
  3. Use those indexes to fetch the matching chunks from SQLite and pass them to the model to answer.
  4. Each level explores how a piece of sensitive data, exposed incorrectly through this pipeline, can cause a leak.

Levels

Each level builds on the same pipeline, changing only the defense that sits in front of retrieval.

Level 1 — Direct Retrieval

No defense in front of retrieval.

A direct query embeds close to the sensitive chunk, FAISS retrieves it, and the model leaks the secret straight back.

Level 2 — Semantic Paraphrase Bypass

A lexical denylist blocks the words password, secret, and admin before retrieval.

The denylist is purely lexical, but retrieval is semantic — so a paraphrase (e.g. "internal recovery value", "privileged access") still lands on the sensitive chunk and leaks it.

Level 3 — Misclassified Low-Sensitivity Document

Retrieval applies a metadata filter (sensitivity = low) with over-fetch (search more, filter, return top results).

A document tagged as low still contains one chunk with a secret, so document-level tags are too coarse and the sensitive chunk passes the filter.

Closes #8

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new "RAG Sensitive Data Exposure" security lab with three progressive difficulty levels
    • Added interactive UI allowing users to test retrieval scenarios, submit queries, and verify secret exposure
    • Implemented vector-based document retrieval system with metadata filtering for realistic RAG attack simulations
  • Documentation

    • Added localization support for RAG vulnerability descriptions and attack scenarios

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

📝 Walkthrough

Walkthrough

A new RAG Sensitive Data Exposure lab is introduced with a FAISS+SQLite vector store, three vulnerability levels simulating data leakage, FastAPI endpoints, and a complete frontend UI for interactive testing and secret validation.

Changes

RAG Sensitive Data Exposure Lab

Layer / File(s) Summary
Framework type system and registry wiring
src/framework/decorators.py, src/framework/registry.py, locale/messages_us.properties, src/app.py
VulnerabilityType.RAG_SENSITIVE_DATA_EXPOSURE is registered, the secret-validation dispatcher routes rag-sensitive-data-exposure to the new handler, US-English strings are added for the vulnerability name and attack/payload descriptions, and the controller is imported in the app.
Service module exports
src/service/vulnerabilities/__init__.py
The vulnerabilities service re-exports lab components (evaluate_rag_data_exposure_level, validate_rag_data_exposure_secret, RAG_DATA_EXPOSURE_LEVELS) under public names.
Vector store and lab evaluation pipeline
src/service/vulnerabilities/rag_data_exposure_lab.py
Dataclasses define RagDataExposureLevel (config), RagQueryResult (validation outcome), and RagDataExposureVectorStore (thread-safe FAISS+SQLite indexing/retrieval). Per-level configuration includes namespaces, secret tokens, system prompts, denylist terms, and optional metadata filters. Functions validate user input, ensure indexes are built, embed/retrieve documents, call the LLM with formatted context, detect secret leaks by substring match, and validate submitted secrets via constant-time HMAC compare.
Test document corpus
src/service/vulnerabilities/docs/RAG_DATA_EXPOSURE/LEVEL{1,2,3}/documents.json
Level 1, 2, and 3 document datasets with three documents each, containing sensitivity labels and chunked content describing operational scenarios (escalation notes, recovery flows, engineering guides).
FastAPI controller
src/controllers/rag_data_exposure_controller.py
HTTP POST endpoints for /level1, /level2, /level3 parse JSON requests and delegate to a shared handler that supports both action=generate (evaluation flow) and action=validate (secret verification), returning structured error responses on ValueError and HTTP errors.
Frontend UI
src/static/facade/rag_data_exposure_template.html, src/static/facade/rag_data_exposure_template.css, src/static/facade/rag_data_exposure_template.js
HTML scaffold includes prompt textarea with suggested-prompt control, secret-check inputs, assistant output display, and collapsible retrieved-documents panel. CSS provides container/typography/grid layout, panel/input/button styling, document metadata rendering, and responsive breakpoint for <= 760px screens. JavaScript wires UI controls to level endpoints, handles character counter and max-length enforcement, parses JSON/text responses, renders retrieved-docs list with escaped content fields, and manages hint/feedback/collapsed state.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Browser / User
  participant Controller as RAG Controller
  participant Lab as Lab Pipeline
  participant Store as Vector Store
  participant Embedder as Embedding Service
  participant LLM as LLM Chat
  
  Browser->>Controller: POST /level{N} with user_input
  Controller->>Lab: evaluate_level(level, user_input, model)
  Lab->>Lab: validate input length & denylist
  Lab->>Store: ensure_level_indexed(level)
  Store->>Embedder: embed all doc chunks
  Embedder->>Store: embeddings + metadata
  Store->>Store: index via FAISS
  Lab->>Embedder: embed user_input
  Lab->>Store: search(embedding, top_k)
  Store->>Lab: ranked matches + metadata
  Lab->>Lab: format context from matches
  Lab->>LLM: call with system_prompt + context
  LLM->>Lab: model output
  Lab->>Lab: detect secret in output
  Lab->>Controller: response with retrieved_docs, leak reason
  Controller->>Browser: JSON: {retrieved_docs, input_accepted, secret_exposed}
  Browser->>Browser: render docs list, show leak feedback
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

The PR introduces a substantial new lab with vector store implementation (FAISS+SQLite), multi-level configuration with per-level secrets and denylist behavior, embedding/retrieval logic with metadata filtering, and a complete frontend UI. While individual files are well-scoped, the changes span framework extensions, service layer, API controller, test data, and frontend, requiring review of dense logic in the lab module, controller error handling, and frontend state management across multiple files.

Possibly related PRs

  • SasanLabs/LLMForge#15: Adds a different vulnerability lab via the same pattern—updates VulnerabilityType enum, extends registry dispatcher, and wires a controller with frontend UI.
  • SasanLabs/LLMForge#17: Another RAG-focused lab that follows the same framework and controller architecture but implements a separate vulnerability scenario.

Poem

🐰 A vector store hops through the fog,
FAISS indexes dance in the log,
Secrets hide in the embedded sea,
But RAG labs expose them, plain to see!
Safety lessons bloom from the breach—
Now the learners grasp what lies in reach.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add RAG sensitive data exposure' directly and accurately describes the main change: implementing a new RAG Sensitive Data Exposure lab with three levels, vector store, controller, and frontend components.
Linked Issues check ✅ Passed The PR implements Levels 1-3 of the RAG vulnerability lab as specified in issue #8: Level 1 (direct retrieval), Level 2 (semantic bypass via denylist), and Level 3 (metadata filter bypass via over-fetch). Levels 4-5 are future work.
Out of Scope Changes check ✅ Passed All changes are directly in scope: RAG lab implementation (vector store, controller, evaluations), locale strings, decorators, registry updates, frontend UI/styling, and data fixtures. No unrelated refactoring or changes detected.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@luks-santos luks-santos changed the title Add RAG sensitive data exposure L1 Add RAG sensitive data exposure May 17, 2026
Lexical denylist (password, secret, admin) bypassed by semantic
paraphrase retrieval.

- Add L2 corpus and denylist input handler
- Add level2 endpoint and locale entries
- Make the facade level-aware
@luks-santos luks-santos force-pushed the rag-sensitive-data-exposure branch from d3b5626 to 5ef3d01 Compare May 24, 2026 23:14
@luks-santos luks-santos marked this pull request as ready for review May 24, 2026 23:16
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/controllers/rag_data_exposure_controller.py`:
- Around line 47-53: The controller currently treats any non-"validate" action
as "generate"; add an explicit allowlist check for action values ("validate" or
"generate") and reject others with an error response. In the handler around the
action variable, validate that action is one of {"validate","generate"} and
return a 4xx error (or raise the appropriate HTTPException) for invalid values;
keep existing calls to validate_rag_data_exposure_secret when action ==
"validate" and to evaluate_rag_data_exposure_level when action == "generate"
(use the same user_input/model extraction). Ensure you reference the action
variable, validate_rag_data_exposure_secret, and
evaluate_rag_data_exposure_level when implementing this guard so bad clients
fail fast instead of following the wrong path.
- Around line 42-44: In the _handle_level method move the JSON parsing (await
request.json()) inside the existing try/except so malformed JSON can't bubble
out; specifically, call await request.json() within the try block at the start
of _handle_level, validate/normalize action = str(data.get("action",
"generate")).strip().lower() there, and in the except handle
JSONDecodeError/ValueError by returning a controlled error dict (and/or
appropriate status payload) instead of letting an unhandled 500 occur.

In `@src/framework/registry.py`:
- Around line 99-100: The branch comparing controller_name uses a hyphenated
string ("rag-sensitive-data-exposure") but other dispatches expect underscored
names (e.g., "rag_sensitive_data_exposure"), causing mismatches; update the
dispatch logic in the function that handles controller_name (the branch that
calls validate_rag_data_exposure_secret) to normalize controller_name (e.g.,
controller_name = controller_name.replace('-', '_') or compare against both
forms) before the if checks so the compare will match and
validate_rag_data_exposure_secret(level_number, candidate_secret) is reached for
registrations named rag_sensitive_data_exposure.

In `@src/service/vulnerabilities/rag_data_exposure_lab.py`:
- Around line 195-231: The current flow appends vectors to FAISS before
inserting rows into SQLite which can cause FAISS/DB divergence on
unique-constraint failures; change the order so you build the DB rows and
perform the INSERT (using self._connect() and handling
sqlite3.IntegrityError/unique-constraint races by skipping or deduping duplicate
rows) before mutating FAISS, then compute start_vector_id = int(index.ntotal),
call faiss.normalize_L2(matrix) and index.add(matrix); apply the same
swap-and-tolerate-duplicate-insert logic to the other identical block referenced
around lines 461-471 (use symbols: index.add, faiss.normalize_L2,
start_vector_id, self._connect, rows, documents).

In `@src/static/facade/rag_data_exposure_template.html`:
- Around line 11-21: The textarea (id="ragExposurePrompt") and the secret input
lack programmatic labels; add accessible labels by either inserting <label
for="ragExposurePrompt">Prompt</label> tied to the textarea and a corresponding
<label for="..."> for the secret input, or by adding clear aria-label attributes
to those elements, and if needed use a visually-hidden utility class to keep
visual layout unchanged; ensure the labels reference the exact element ids used
in this template so screen readers can announce the fields.

In `@src/static/facade/rag_data_exposure_template.js`:
- Around line 72-78: The UI currently accepts any integer level >=1 (from the
level parsing code) but endpointForLevel only implements level1..level3, causing
404s for higher levels; clamp the parsed level to the supported range (1..3) or
clamp inside endpointForLevel so that any incoming level is reduced to
Math.min(Math.max(level, 1), 3) before building the URL. Update the level
parsing function or endpointForLevel (referencing endpointForLevel and the
level-parsing code that sets const level = Number(match[1])) to enforce this
clamp so requests always target /level1, /level2, or /level3.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 14462f52-df10-4037-8223-dc7e7b71afe4

📥 Commits

Reviewing files that changed from the base of the PR and between eb06cf7 and 5ef3d01.

📒 Files selected for processing (13)
  • locale/messages_us.properties
  • src/app.py
  • src/controllers/rag_data_exposure_controller.py
  • src/framework/decorators.py
  • src/framework/registry.py
  • src/service/vulnerabilities/__init__.py
  • src/service/vulnerabilities/docs/RAG_DATA_EXPOSURE/LEVEL1/documents.json
  • src/service/vulnerabilities/docs/RAG_DATA_EXPOSURE/LEVEL2/documents.json
  • src/service/vulnerabilities/docs/RAG_DATA_EXPOSURE/LEVEL3/documents.json
  • src/service/vulnerabilities/rag_data_exposure_lab.py
  • src/static/facade/rag_data_exposure_template.css
  • src/static/facade/rag_data_exposure_template.html
  • src/static/facade/rag_data_exposure_template.js

Comment on lines +42 to +44
async def _handle_level(self, level: int, request: Request) -> dict:
data = await request.json()
action = str(data.get("action", "generate")).strip().lower()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle malformed JSON inside guarded error flow.

On Line 43, await request.json() runs before the try block, so invalid JSON can escape your handler and return an unhandled 500. Move parsing into try and return a controlled error payload.

Proposed fix
 async def _handle_level(self, level: int, request: Request) -> dict:
-    data = await request.json()
-    action = str(data.get("action", "generate")).strip().lower()
-
     try:
+        data = await request.json()
+        action = str(data.get("action", "generate")).strip().lower()
         if action == "validate":
             candidate_secret = str(data.get("candidate_secret", ""))
             return validate_rag_data_exposure_secret(level, candidate_secret)
@@
         except ValueError as exc:
             return {"error": str(exc)}
+        except Exception:
+            return {"error": "Invalid JSON payload"}
         except httpx.HTTPError:
             return {"error": "Model or embedding service unavailable"}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/controllers/rag_data_exposure_controller.py` around lines 42 - 44, In the
_handle_level method move the JSON parsing (await request.json()) inside the
existing try/except so malformed JSON can't bubble out; specifically, call await
request.json() within the try block at the start of _handle_level,
validate/normalize action = str(data.get("action", "generate")).strip().lower()
there, and in the except handle JSONDecodeError/ValueError by returning a
controlled error dict (and/or appropriate status payload) instead of letting an
unhandled 500 occur.

Comment on lines +47 to +53
if action == "validate":
candidate_secret = str(data.get("candidate_secret", ""))
return validate_rag_data_exposure_secret(level, candidate_secret)

user_input = str(data.get("user_input", ""))
model = data.get("model")
return await evaluate_rag_data_exposure_level(level, user_input, model=model)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject unsupported action values explicitly.

Right now, any non-validate action is treated as generate. On Line 47, add an allowlist check (generate / validate) so bad clients fail fast instead of silently executing the wrong path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/controllers/rag_data_exposure_controller.py` around lines 47 - 53, The
controller currently treats any non-"validate" action as "generate"; add an
explicit allowlist check for action values ("validate" or "generate") and reject
others with an error response. In the handler around the action variable,
validate that action is one of {"validate","generate"} and return a 4xx error
(or raise the appropriate HTTPException) for invalid values; keep existing calls
to validate_rag_data_exposure_secret when action == "validate" and to
evaluate_rag_data_exposure_level when action == "generate" (use the same
user_input/model extraction). Ensure you reference the action variable,
validate_rag_data_exposure_secret, and evaluate_rag_data_exposure_level when
implementing this guard so bad clients fail fast instead of following the wrong
path.

Comment thread src/framework/registry.py
Comment on lines +99 to +100
if controller_name == "rag-sensitive-data-exposure":
return validate_rag_data_exposure_secret(level_number, candidate_secret)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize controller-name format in secret dispatch.

Line 99 uses a hyphenated controller id, while dispatch in this function otherwise relies on underscored names. If registration.name is rag_sensitive_data_exposure, this branch won’t match and verify-secret returns 404.

Suggested fix
 def _verify_secret(controller_name: str, level_number: int, secret_token: str | None, candidate_secret: str) -> dict:
-    if controller_name == "prompt_injection":
+    normalized_controller_name = controller_name.replace("-", "_")
+    if normalized_controller_name == "prompt_injection":
         return verify_level_secret(level_number, secret_token, candidate_secret)
-    if controller_name == "indirect_prompt_injection":
+    if normalized_controller_name == "indirect_prompt_injection":
         return verify_indirect_level_secret(level_number, secret_token, candidate_secret)
-    if controller_name == "rag-sensitive-data-exposure":
+    if normalized_controller_name == "rag_sensitive_data_exposure":
         return validate_rag_data_exposure_secret(level_number, candidate_secret)
     raise HTTPException(status_code=404, detail="Unsupported controller")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/framework/registry.py` around lines 99 - 100, The branch comparing
controller_name uses a hyphenated string ("rag-sensitive-data-exposure") but
other dispatches expect underscored names (e.g., "rag_sensitive_data_exposure"),
causing mismatches; update the dispatch logic in the function that handles
controller_name (the branch that calls validate_rag_data_exposure_secret) to
normalize controller_name (e.g., controller_name = controller_name.replace('-',
'_') or compare against both forms) before the if checks so the compare will
match and validate_rag_data_exposure_secret(level_number, candidate_secret) is
reached for registrations named rag_sensitive_data_exposure.

Comment on lines +195 to +231
start_vector_id = int(index.ntotal)
faiss.normalize_L2(matrix)
index.add(matrix)

self._ensure_schema()
rows = []
for offset, document in enumerate(documents):
metadata = dict(document.get("metadata") or {})
rows.append(
{
"namespace": namespace,
"vector_id": start_vector_id + offset,
"doc_id": document["doc_id"],
"chunk_id": document["chunk_id"],
"title": document["title"],
"source": document["source"],
"content": document["text"],
"sensitivity": document["sensitivity"],
"metadata_json": json.dumps(metadata, ensure_ascii=True, sort_keys=True),
}
)

with self._connect() as connection:
connection.executemany(
"""
INSERT INTO rag_data_exposure_chunks (
namespace, vector_id, doc_id, chunk_id, title, source,
content, sensitivity, metadata_json
)
VALUES (
:namespace, :vector_id, :doc_id, :chunk_id, :title, :source,
:content, :sensitivity, :metadata_json
)
""",
rows,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Prevent FAISS/SQLite divergence during concurrent first-time indexing.

Two concurrent calls can both pass ensure_level_indexed pre-check. In add, vectors are appended to FAISS before SQLite insert; if SQLite then hits the unique constraint, FAISS and SQLite drift out of sync.

💡 Suggested fix (insert first, then mutate FAISS; tolerate duplicate insert race)
@@
-            start_vector_id = int(index.ntotal)
-            faiss.normalize_L2(matrix)
-            index.add(matrix)
-
             self._ensure_schema()
+            start_vector_id = int(index.ntotal)
             rows = []
@@
-            with self._connect() as connection:
-                connection.executemany(
-                    """
-                    INSERT INTO rag_data_exposure_chunks (
-                        namespace, vector_id, doc_id, chunk_id, title, source,
-                        content, sensitivity, metadata_json
-                    )
-                    VALUES (
-                        :namespace, :vector_id, :doc_id, :chunk_id, :title, :source,
-                        :content, :sensitivity, :metadata_json
-                    )
-                    """,
-                    rows,
-                )
+            try:
+                with self._connect() as connection:
+                    connection.executemany(
+                        """
+                        INSERT INTO rag_data_exposure_chunks (
+                            namespace, vector_id, doc_id, chunk_id, title, source,
+                            content, sensitivity, metadata_json
+                        )
+                        VALUES (
+                            :namespace, :vector_id, :doc_id, :chunk_id, :title, :source,
+                            :content, :sensitivity, :metadata_json
+                        )
+                        """,
+                        rows,
+                    )
+            except sqlite3.IntegrityError:
+                return {
+                    "added": 0,
+                    "total_documents": self.namespace_count(namespace),
+                    "dimension": self._dimensions[namespace],
+                    "namespace": namespace,
+                }
+
+            faiss.normalize_L2(matrix)
+            index.add(matrix)

Also applies to: 461-471

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/service/vulnerabilities/rag_data_exposure_lab.py` around lines 195 - 231,
The current flow appends vectors to FAISS before inserting rows into SQLite
which can cause FAISS/DB divergence on unique-constraint failures; change the
order so you build the DB rows and perform the INSERT (using self._connect() and
handling sqlite3.IntegrityError/unique-constraint races by skipping or deduping
duplicate rows) before mutating FAISS, then compute start_vector_id =
int(index.ntotal), call faiss.normalize_L2(matrix) and index.add(matrix); apply
the same swap-and-tolerate-duplicate-insert logic to the other identical block
referenced around lines 461-471 (use symbols: index.add, faiss.normalize_L2,
start_vector_id, self._connect, rows, documents).

Comment on lines +11 to +21
<h3>Prompt</h3>
<span id="queryCounter" class="counter">0 / 240</span>
</div>

<textarea
id="ragExposurePrompt"
class="rag-input"
maxlength="240"
rows="4"
spellcheck="false"
></textarea>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add explicit labels for form controls (a11y).

The prompt textarea and secret input rely on surrounding text/placeholder, but neither has a programmatic <label> (or aria-label). Screen-reader users may not get reliable field names.

Proposed fix
-        <h3>Prompt</h3>
+        <h3><label for="ragExposurePrompt">Prompt</label></h3>
@@
-      <input
+      <label for="ragExposureSecret" class="sr-only">Secret to verify</label>
+      <input
         id="ragExposureSecret"

Also applies to: 40-48

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/static/facade/rag_data_exposure_template.html` around lines 11 - 21, The
textarea (id="ragExposurePrompt") and the secret input lack programmatic labels;
add accessible labels by either inserting <label
for="ragExposurePrompt">Prompt</label> tied to the textarea and a corresponding
<label for="..."> for the secret input, or by adding clear aria-label attributes
to those elements, and if needed use a visually-hidden utility class to keep
visual layout unchanged; ensure the labels reference the exact element ids used
in this template so screen readers can announce the fields.

Comment on lines +72 to +78
const level = Number(match[1]);
return Number.isInteger(level) && level >= 1 ? level : 1;
}

function endpointForLevel(level) {
return apiPrefix + "/api/v1/vulnerabilities/rag-sensitive-data-exposure/level" + level;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clamp level to implemented endpoints to avoid guaranteed 404s.

On Line 73, any LEVEL_n (n >= 1) is accepted, but this UI only implements level1..level3. If LEVEL_4/LEVEL_5 is selected upstream, requests go to missing endpoints.

Proposed fix
+  const SUPPORTED_LEVELS = [1, 2, 3];
@@
   function levelFromGlobalState() {
@@
     const level = Number(match[1]);
-    return Number.isInteger(level) && level >= 1 ? level : 1;
+    return Number.isInteger(level) && SUPPORTED_LEVELS.includes(level) ? level : 1;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/static/facade/rag_data_exposure_template.js` around lines 72 - 78, The UI
currently accepts any integer level >=1 (from the level parsing code) but
endpointForLevel only implements level1..level3, causing 404s for higher levels;
clamp the parsed level to the supported range (1..3) or clamp inside
endpointForLevel so that any incoming level is reduced to
Math.min(Math.max(level, 1), 3) before building the URL. Update the level
parsing function or endpointForLevel (referencing endpointForLevel and the
level-parsing code that sets const level = Number(match[1])) to enforce this
clamp so requests always target /level1, /level2, or /level3.

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.

RAG Vulnerability

1 participant