feat(add form highlight):

This commit is contained in:
Alexander Myasoedov
2025-01-07 12:12:05 +02:00
parent 27f7ed693b
commit 8857842e40
5 changed files with 132 additions and 49 deletions
+7 -3
View File
@@ -66,7 +66,7 @@ async def perform_single_shot_scan(
stop_event: asyncio.Event = None,
) -> AsyncGenerator[str, None]:
"""Perform a standard security scan."""
max_budget = max_budget * 100_000_000
selected_datasets = [m for m in datasets if m["selected"]]
try:
yield ScanResult.status_msg("Loading datasets...")
@@ -148,8 +148,12 @@ async def perform_single_shot_scan(
should_stop = True
break
if total_tokens > max_budget:
logger.info("Scan ran out of budget and stopped.")
yield ScanResult.status_msg("Scan ran out of budget and stopped.")
logger.info(
f"Scan ran out of budget and stopped. {total_tokens=} {max_budget=}"
)
yield ScanResult.status_msg(
f"Scan ran out of budget and stopped. {total_tokens=} {max_budget=}"
)
should_stop = True
break
+65 -44
View File
@@ -5,6 +5,10 @@ from functools import lru_cache
import httpx
import pandas as pd
from cache_to_disk import cache_to_disk
from datasets import load_dataset
from loguru import logger
from agentic_security.probe_data import stenography_fn
from agentic_security.probe_data.models import ProbeDataset
from agentic_security.probe_data.modules import (
@@ -13,10 +17,56 @@ from agentic_security.probe_data.modules import (
garak_tool,
inspect_ai_tool,
)
from cache_to_disk import cache_to_disk
from datasets import load_dataset
from loguru import logger
@cache_to_disk()
def load_dataset_general(
dataset_name,
dataset_split="train",
column_mappings=None,
filter_fn=None,
custom_url=None,
additional_metadata=None,
):
"""
Generalized function to load datasets with flexible configurations.
:param dataset_name: Name of the dataset or URL for custom CSVs
:param dataset_split: Split to load from the dataset (e.g., "train")
:param column_mappings: Dictionary mapping dataset columns to expected keys, e.g., {'prompt': 'query'}
:param filter_fn: A filtering function that takes a row and returns True/False
:param custom_url: URL for custom CSV datasets
:param additional_metadata: Additional metadata to include in the ProbeDataset
:return: A ProbeDataset object with the processed data
"""
if custom_url:
logger.info(f"Loading custom CSV dataset from {custom_url}")
r = httpx.get(custom_url)
content = r.content
df = pd.read_csv(io.StringIO(content.decode("utf-8")))
else:
logger.info(f"Loading dataset {dataset_name} from Hugging Face datasets")
dataset = load_dataset(dataset_name)
df = pd.DataFrame(dataset[dataset_split])
# Apply column mappings if provided
if column_mappings:
df.rename(columns=column_mappings, inplace=True)
# Filter rows if filter_fn is provided
if filter_fn:
df = df[df.apply(filter_fn, axis=1)]
# Extract prompts
prompts = df[column_mappings.get("prompt", "prompt")].tolist()
return ProbeDataset(
dataset_name=dataset_name,
metadata=additional_metadata or {},
prompts=prompts,
tokens=count_words_in_list(prompts),
approx_cost=0.0,
)
def count_words_in_list(str_list):
@@ -31,66 +81,37 @@ def count_words_in_list(str_list):
@cache_to_disk()
def load_dataset_v1():
dataset = load_dataset("ShawnMenz/DAN_jailbreak")
dp = dataset["train"]["prompt"]
dj = dataset["train"]["jailbreak"]
# good_prompts = [p for p, j in zip(dp, dj) if not j]
bad_prompts = [p for p, j in zip(dp, dj) if j]
return ProbeDataset(
return load_dataset_general(
dataset_name="ShawnMenz/DAN_jailbreak",
metadata={},
prompts=bad_prompts,
tokens=count_words_in_list(bad_prompts),
approx_cost=0.0,
column_mappings={"prompt": "prompt", "jailbreak": "jailbreak"},
filter_fn=lambda row: row["jailbreak"],
)
@cache_to_disk()
def load_dataset_v2():
dataset = load_dataset("deepset/prompt-injections")
dp = dataset["train"]["text"]
dj = dataset["train"]["label"]
# good_prompts = [p for p, j in zip(dp, dj) if not j]
bad_prompts = [p for p, j in zip(dp, dj) if j]
return ProbeDataset(
return load_dataset_general(
dataset_name="deepset/prompt-injections",
metadata={},
prompts=bad_prompts,
tokens=count_words_in_list(bad_prompts),
approx_cost=0.0,
column_mappings={"prompt": "text", "jailbreak": "label"},
filter_fn=lambda row: row["label"],
)
@cache_to_disk()
def load_dataset_v4():
dataset = dataset = load_dataset("notrichardren/refuse-to-answer-prompts")
dp = dataset["train"]["claim"]
dj = dataset["train"]["label"]
# good_prompts = [p for p, j in zip(dp, dj) if not j]
bad_prompts = [p for p, j in zip(dp, dj) if j]
return ProbeDataset(
return load_dataset_general(
dataset_name="notrichardren/refuse-to-answer-prompts",
metadata={},
prompts=bad_prompts,
tokens=count_words_in_list(bad_prompts),
approx_cost=0.0,
column_mappings={"prompt": "claim", "jailbreak": "label"},
filter_fn=lambda row: row["label"],
)
@cache_to_disk()
def load_dataset_v3():
dataset = load_dataset("rubend18/ChatGPT-Jailbreak-Prompts")
bad_prompts = dataset["train"]["Prompt"]
return ProbeDataset(
return load_dataset_general(
dataset_name="rubend18/ChatGPT-Jailbreak-Prompts",
metadata={},
prompts=bad_prompts,
tokens=count_words_in_list(bad_prompts),
approx_cost=0.0,
column_mappings={"prompt": "Prompt"},
filter_fn=lambda row: row["label"],
)
@@ -65,7 +65,7 @@ class Module:
return {}
async def fetch_prompts(self) -> list[str]:
api_url = "https://msoedov--agesec-backend-fastapi-app.modal.run/infer"
api_url = "https://edge.metaheuristic.co/infer"
headers = {
"Authorization": f"Bearer {AUTH_TOKEN}",
"Content-Type": "application/json",
+10 -1
View File
@@ -75,13 +75,22 @@
</div>
<div v-show="showLLMSpec" class="mt-4">
<label for="llm-spec" class="block text-sm font-medium mb-2">
<label v-if="isFocused" for="llm-spec"
class="block text-sm font-medium mb-2">
LLM API Spec, PROMPT variable will be replaced with the testing
prompt
</label>
<div
v-if="!isFocused"
class="w-full bg-dark-bg text-dark-accent-orange border border-gray-600 rounded-lg p-3 cursor-text"
@click="focusTextarea"
v-html="highlightedText"></div>
<textarea
v-else
class="w-full bg-dark-bg text-dark-accent-orange border border-gray-600 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-dark-accent-green"
id="llm-spec"
@blur="unfocusTextarea"
v-model="modelSpec"
@input="adjustHeight"
rows="5"
+49
View File
@@ -4,6 +4,7 @@ var app = new Vue({
progressWidth: '0%',
modelSpec: LLM_SPECS[0],
budget: 50,
isFocused: false, // Tracks if the textarea is focused
showParams: false,
showResetConfirmation: false,
enableChartDiagram: true,
@@ -62,8 +63,55 @@ var app = new Vue({
hasFileSpec() {
return has_files(this.modelSpec) || has_image(this.modelSpec);
},
highlightedText() {
// First highlight <<VAR>> pattern
let text = this.modelSpec.replace(
/<<([^>]+)>>/g,
`<span class="px-2 py-0.5 rounded-full bg-dark-accent-yellow text-dark-bg font-medium">&lt;&lt;$1&gt;&gt;</span>`
);
// Then highlight $VARIABLE pattern
text = text.replace(
/(\$[A-Z_]+)/g,
`<span class="px-2 py-0.5 rounded-full bg-yellow-100 text-dark-bg font-medium">$1</span>`
);
// Finally wrap everything in gray text
return `<span class="text-gray-500">${text}</span>`;
},
highlightedText2() {
// First apply the highlighting for variables
const highlightedText = this.modelSpec.replace(
/<<([^>]+)>>/g,
`<span class="px-2 py-0.5 rounded-full bg-dark-accent-yellow text-dark-bg font-medium">&lt;&lt;$1&gt;&gt;</span>`
);
// Wrap the entire text in a span to make non-highlighted parts dim gray
return `<span class="text-gray-500">${highlightedText}</span>`;
}
},
methods: {
focusTextarea() {
this.isFocused = true;
self = this.$refs;
this.$nextTick(() => {
// Focus the textarea after rendering
self.textarea.focus();
this.adjustHeight({ target: self.textarea });
});
document.addEventListener("mousedown", this.handleClickOutside);
},
handleOutsideClick(event) {
if (!this.$refs.container.contains(event.target)) {
this.isFocused = false;
document.removeEventListener("mousedown", this.handleClickOutside);
}
},
unfocusTextarea() {
this.isFocused = false;
},
acceptConsent() {
this.showConsentModal = false; // Close the modal
localStorage.setItem('consentGiven', 'true'); // Save consent to local storage
@@ -128,6 +176,7 @@ var app = new Vue({
this.showLLMSpec = !this.showLLMSpec;
},
adjustHeight(event) {
const textarea = event.target;
event.target.style.height = 'auto';
event.target.style.height = event.target.scrollHeight + 'px';
},