diff --git a/apps/dashboard/src/main/java/com/akto/action/GuardrailPoliciesAction.java b/apps/dashboard/src/main/java/com/akto/action/GuardrailPoliciesAction.java
index 9539dd8146..192848822a 100644
--- a/apps/dashboard/src/main/java/com/akto/action/GuardrailPoliciesAction.java
+++ b/apps/dashboard/src/main/java/com/akto/action/GuardrailPoliciesAction.java
@@ -20,6 +20,7 @@
import java.util.List;
import java.util.Map;
+
public class GuardrailPoliciesAction extends UserAction {
private static final LoggerMaker loggerMaker = new LoggerMaker(GuardrailPoliciesAction.class, LogDb.DASHBOARD);
@@ -55,6 +56,7 @@ public String fetchGuardrailPolicies() {
}
}
+
public String createGuardrailPolicy() {
try {
User user = getSUser();
@@ -99,6 +101,7 @@ public String createGuardrailPolicy() {
updates.add(Updates.set("regexPatternsV2", policy.getRegexPatternsV2()));
updates.add(Updates.set("contentFiltering", policy.getContentFiltering()));
updates.add(Updates.set("llmRule", policy.getLlmRule()));
+ updates.add(Updates.set("basePromptRule", policy.getBasePromptRule()));
updates.add(Updates.set("selectedMcpServers", policy.getSelectedMcpServers()));
updates.add(Updates.set("selectedAgentServers", policy.getSelectedAgentServers()));
updates.add(Updates.set("selectedMcpServersV2", policy.getSelectedMcpServersV2()));
diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/GuardrailPolicies.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/GuardrailPolicies.jsx
index 02183b389b..261a7876f1 100644
--- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/GuardrailPolicies.jsx
+++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/GuardrailPolicies.jsx
@@ -446,6 +446,8 @@ function GuardrailPolicies() {
contentFiltering: guardrailData.contentFilters || {},
// Add LLM policy if present
...(guardrailData.llmRule ? { llmRule: guardrailData.llmRule } : {}),
+ // Add Base Prompt Rule if present
+ ...(guardrailData.basePromptRule ? { basePromptRule: guardrailData.basePromptRule } : {}),
applyOnResponse: guardrailData.applyOnResponse || false,
applyOnRequest: guardrailData.applyOnRequest || false,
url: guardrailData.url || '',
diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/CreateGuardrailModal.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/CreateGuardrailModal.jsx
index 936e3445d9..46e04f6a9e 100644
--- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/CreateGuardrailModal.jsx
+++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/CreateGuardrailModal.jsx
@@ -27,6 +27,8 @@ import {
SensitiveInfoConfig,
LlmPromptStep,
LlmPromptConfig,
+ BasePromptStep,
+ BasePromptConfig,
ExternalModelStep,
ExternalModelConfig,
ServerSettingsStep,
@@ -76,24 +78,28 @@ const CreateGuardrailModal = ({ isOpen, onClose, onSave, editingPolicy = null, i
const [llmPrompt, setLlmPrompt] = useState("");
const [llmConfidenceScore, setLlmConfidenceScore] = useState(0.5);
- // Step 7: External model based evaluation
+ // Step 7: Base Prompt Based Validation (AI Agents)
+ const [enableBasePromptRule, setEnableBasePromptRule] = useState(false);
+ const [basePromptConfidenceScore, setBasePromptConfidenceScore] = useState(0.5);
+
+ // Step 8: External model based evaluation
const [url, setUrl] = useState("");
const [confidenceScore, setConfidenceScore] = useState(25); // Start with 25 (first checkpoint)
- // Step 8: Server settings
+ // Step 9: Server settings
const [selectedMcpServers, setSelectedMcpServers] = useState([]);
const [selectedAgentServers, setSelectedAgentServers] = useState([]);
const [applyOnResponse, setApplyOnResponse] = useState(false);
const [applyOnRequest, setApplyOnRequest] = useState(false);
-
+
// Collections data
const [mcpServers, setMcpServers] = useState([]);
const [agentServers, setAgentServers] = useState([]);
const [collectionsLoading, setCollectionsLoading] = useState(false);
-
+
// Get collections from PersistStore
const allCollections = PersistStore(state => state.allCollections);
-
+
// Create validation state object
const getStoredStateData = () => ({
// Step 1
@@ -113,9 +119,12 @@ const CreateGuardrailModal = ({ isOpen, onClose, onSave, editingPolicy = null, i
llmPrompt,
llmConfidenceScore,
// Step 7
+ enableBasePromptRule,
+ basePromptConfidenceScore,
+ // Step 8
url,
confidenceScore,
- // Step 8
+ // Step 9
selectedMcpServers,
selectedAgentServers,
mcpServers,
@@ -164,6 +173,12 @@ const CreateGuardrailModal = ({ isOpen, onClose, onSave, editingPolicy = null, i
summary: LlmPromptConfig.getSummary(storedStateData),
...LlmPromptConfig.validate(storedStateData)
},
+ {
+ number: BasePromptConfig.number,
+ title: BasePromptConfig.title,
+ summary: BasePromptConfig.getSummary(storedStateData),
+ ...BasePromptConfig.validate(storedStateData)
+ },
{
number: ExternalModelConfig.number,
title: ExternalModelConfig.title,
@@ -269,6 +284,8 @@ const CreateGuardrailModal = ({ isOpen, onClose, onSave, editingPolicy = null, i
setNewRegexPattern("");
setLlmPrompt("");
setLlmConfidenceScore(0.5);
+ setEnableBasePromptRule(false);
+ setBasePromptConfidenceScore(0.5);
setUrl("");
setConfidenceScore(25);
setSelectedMcpServers([]);
@@ -282,7 +299,7 @@ const CreateGuardrailModal = ({ isOpen, onClose, onSave, editingPolicy = null, i
setDescription(policy.description || "");
setBlockedMessage(policy.blockedMessage || "");
setApplyToResponses(policy.applyToResponses || false);
-
+
// Content filters
if (policy.contentFiltering) {
if (policy.contentFiltering.harmfulCategories) {
@@ -301,19 +318,19 @@ const CreateGuardrailModal = ({ isOpen, onClose, onSave, editingPolicy = null, i
setPromptAttackLevel(policy.contentFiltering.promptAttacks.level || "HIGH");
}
}
-
+
// Denied topics
setDeniedTopics(policy.deniedTopics || []);
-
+
// Word filters
setWordFilters({
profanity: policy.wordFilters?.profanity || false,
custom: policy.wordFilters?.custom || []
});
-
+
// PII filters
setPiiTypes(policy.piiTypes || []);
-
+
// Regex patterns - prefer V2 format with behavior, fallback to old format
if (policy.regexPatternsV2 && policy.regexPatternsV2.length > 0) {
setRegexPatterns(policy.regexPatternsV2);
@@ -337,6 +354,15 @@ const CreateGuardrailModal = ({ isOpen, onClose, onSave, editingPolicy = null, i
setLlmConfidenceScore(0.5);
}
+ // Base Prompt Based Validation (AI Agents)
+ if (policy.basePromptRule) {
+ setEnableBasePromptRule(policy.basePromptRule.enabled || false);
+ setBasePromptConfidenceScore(policy.basePromptRule.confidenceScore !== undefined ? policy.basePromptRule.confidenceScore : 0.5);
+ } else {
+ setEnableBasePromptRule(false);
+ setBasePromptConfidenceScore(0.5);
+ }
+
// External model based evaluation
setUrl(policy.url || "");
// Map existing confidence score to nearest checkpoint
@@ -435,6 +461,12 @@ const CreateGuardrailModal = ({ isOpen, onClose, onSave, editingPolicy = null, i
confidenceScore: llmConfidenceScore
}
} : {}),
+ ...(enableBasePromptRule ? {
+ basePromptRule: {
+ enabled: true,
+ confidenceScore: basePromptConfidenceScore
+ }
+ } : {}),
url: url || null,
confidenceScore: confidenceScore,
selectedMcpServers: selectedMcpServers, // Old format (just IDs)
@@ -629,6 +661,15 @@ const CreateGuardrailModal = ({ isOpen, onClose, onSave, editingPolicy = null, i
/>
);
case 7:
+ return (
+
+ );
+ case 8:
return (
);
- case 8:
+ case 9:
return (
step.isValid);
- return {
- content: isEditMode ? "Update Guardrail" : "Create Guardrail",
- onAction: handleSave,
- loading: loading,
+ return {
+ content: isEditMode ? "Update Guardrail" : "Create Guardrail",
+ onAction: handleSave,
+ loading: loading,
disabled: !allStepsValid
};
};
@@ -710,7 +751,7 @@ const CreateGuardrailModal = ({ isOpen, onClose, onSave, editingPolicy = null, i
{renderAllSteps()}
-
+
>
diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/BasePromptStep.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/BasePromptStep.jsx
new file mode 100644
index 0000000000..3fb3ebedad
--- /dev/null
+++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/BasePromptStep.jsx
@@ -0,0 +1,58 @@
+import { VerticalStack, Text, RangeSlider, FormLayout, Checkbox, Box } from "@shopify/polaris";
+
+export const BasePromptConfig = {
+ number: 7,
+ title: "Intent verification using base prompt (AI Agents)",
+
+ validate: () => {
+ return { isValid: true, errorMessage: null };
+ },
+
+ getSummary: ({ enableBasePromptRule, basePromptConfidenceScore }) => {
+ if (!enableBasePromptRule) return null;
+ return `Auto-detect from traffic, Confidence: ${basePromptConfidenceScore.toFixed(2)}`;
+ }
+};
+
+const BasePromptStep = ({
+ enableBasePromptRule,
+ setEnableBasePromptRule,
+ basePromptConfidenceScore,
+ setBasePromptConfidenceScore
+}) => {
+ return (
+
+
+ Verify if agent requests match the intent of the base prompt. The base prompt is automatically detected from traffic, and user inputs filling placeholders like {`{var}`} or {`{}`} are checked against this intent.
+
+
+
+
+
+ {enableBasePromptRule && (
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default BasePromptStep;
+
diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/ExternalModelStep.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/ExternalModelStep.jsx
index 28b09bcc50..98f86b478b 100644
--- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/ExternalModelStep.jsx
+++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/ExternalModelStep.jsx
@@ -8,7 +8,7 @@ const validateUrl = (url) => {
};
export const ExternalModelConfig = {
- number: 7,
+ number: 8,
title: "External model based evaluation",
validate: ({ url }) => {
diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/ServerSettingsStep.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/ServerSettingsStep.jsx
index 16779aa818..3d2b2ccc25 100644
--- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/ServerSettingsStep.jsx
+++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/ServerSettingsStep.jsx
@@ -2,7 +2,7 @@ import { VerticalStack, Text, FormLayout, Box, Checkbox } from "@shopify/polaris
import DropdownSearch from "../../../../components/shared/DropdownSearch";
export const ServerSettingsConfig = {
- number: 8,
+ number: 9,
title: "Server and application settings",
validate: () => {
diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/index.js b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/index.js
index 59a0e9157f..33e0fc4056 100644
--- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/index.js
+++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/guardrails/components/steps/index.js
@@ -5,5 +5,6 @@ export { default as DeniedTopicsStep, DeniedTopicsConfig } from './DeniedTopicsS
export { default as WordFiltersStep, WordFiltersConfig } from './WordFiltersStep';
export { default as SensitiveInfoStep, SensitiveInfoConfig } from './SensitiveInfoStep';
export { default as LlmPromptStep, LlmPromptConfig } from './LlmPromptStep';
+export { default as BasePromptStep, BasePromptConfig } from './BasePromptStep';
export { default as ExternalModelStep, ExternalModelConfig } from './ExternalModelStep';
export { default as ServerSettingsStep, ServerSettingsConfig } from './ServerSettingsStep';
diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiDetails.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiDetails.jsx
index 0bb810cfe8..a41db3431f 100644
--- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiDetails.jsx
+++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiDetails.jsx
@@ -4,6 +4,7 @@ import FlyLayout from "../../../components/layouts/FlyLayout";
import GithubCell from "../../../components/tables/cells/GithubCell";
import ApiGroups from "../../../components/shared/ApiGroups";
import SampleDataList from "../../../components/shared/SampleDataList";
+import SampleData from "../../../components/shared/SampleData";
import { useEffect, useState, useRef } from "react";
import api from "../api";
import ApiSchema from "./ApiSchema";
@@ -71,6 +72,7 @@ function ApiDetails(props) {
const [loading, setLoading] = useState(false)
const [showMoreActions, setShowMoreActions] = useState(false)
const setSelectedSampleApi = PersistStore(state => state.setSelectedSampleApi)
+ const allCollections = PersistStore(state => state.allCollections)
const [disabledTabs, setDisabledTabs] = useState([])
const [description, setDescription] = useState("")
const [headersWithData, setHeadersWithData] = useState([])
@@ -87,6 +89,7 @@ function ApiDetails(props) {
const apiDistributionAvailableRef = useRef(false);
const [selectedTabId, setSelectedTabId] = useState('values');
const [showForbidden, setShowForbidden] = useState(false);
+ const [detectedBasePrompt, setDetectedBasePrompt] = useState(null);
const statusFunc = getStatus ? getStatus : (x) => {
try {
@@ -250,6 +253,25 @@ function ApiDetails(props) {
const { apiCollectionId, endpoint, method, description } = apiDetail
setSelectedUrl({ url: endpoint, method: method })
+ // Fetch ApiInfo to get detectedBasePrompt
+ try {
+ const apiInfoResp = await api.fetchEndpoint({
+ url: endpoint,
+ method: method,
+ apiCollectionId: apiCollectionId
+ });
+ // Check both apiInfoResp.data and apiInfoResp.data.apiInfo (depending on response structure)
+ const detectedPrompt = apiInfoResp?.data?.detectedBasePrompt || apiInfoResp?.detectedBasePrompt;
+ if (detectedPrompt && detectedPrompt.trim().length > 0) {
+ setDetectedBasePrompt(detectedPrompt);
+ } else {
+ setDetectedBasePrompt(null);
+ }
+ } catch (error) {
+ console.error("Error fetching ApiInfo:", error);
+ setDetectedBasePrompt(null);
+ }
+
try {
await api.checkIfDependencyGraphAvailable(apiCollectionId, endpoint, method).then((resp) => {
if (!resp.dependencyGraphExists) {
@@ -384,6 +406,8 @@ function ApiDetails(props) {
fetchData();
setHeadersWithData([])
+ // Reset detectedBasePrompt when apiDetail changes
+ setDetectedBasePrompt(null);
}, [apiDetail])
useEffect(() => {
@@ -574,6 +598,47 @@ function ApiDetails(props) {
/>
}
+
+ const BasePromptTab = {
+ id: 'base-prompt',
+ content: "Prompt Template",
+ component:
+
+
+
+
+ Detected Prompt Template
+
+ {detectedBasePrompt ? (
+ <>
+
+
+
+
+ Auto-detected prompt template with placeholders from agent traffic. This template represents the common structure of prompts sent to this endpoint.
+
+ >
+ ) : (
+
+
+ No prompt template detected yet. The base prompt template will be automatically detected from agent traffic when requests are made to this endpoint.
+
+
+ )}
+
+
+
+
+ }
const ValuesTab = {
id: 'values',
content: "Values",
@@ -775,6 +840,20 @@ function ApiDetails(props) {
)
+ // Check if collection has gen-ai tag (same pattern as CreateGuardrailModal.jsx)
+ const hasGenAiTag = () => {
+ if (!apiDetail?.apiCollectionId || !allCollections) return false;
+ const collection = allCollections.find(c => c.id === apiDetail.apiCollectionId);
+ if (!collection) return false;
+
+ return collection.envType && collection.envType.some(envType =>
+ envType.keyName === 'gen-ai'
+ );
+ };
+
+ // Always show BasePromptTab for AI agents (collections with gen-ai tag)
+ const shouldShowBasePromptTab = hasGenAiTag();
+
const components = showForbidden
? []
: [
@@ -784,6 +863,7 @@ function ApiDetails(props) {
tabs={[
ValuesTab,
SchemaTab,
+ ...(shouldShowBasePromptTab ? [BasePromptTab] : []),
...(hasIssues ? [IssuesTab] : []),
ApiCallStatsTab,
DependencyTab
diff --git a/apps/dashboard/web/polaris_web/web/src/util/func.js b/apps/dashboard/web/polaris_web/web/src/util/func.js
index 48a2e5af30..531f4b1008 100644
--- a/apps/dashboard/web/polaris_web/web/src/util/func.js
+++ b/apps/dashboard/web/polaris_web/web/src/util/func.js
@@ -2329,6 +2329,42 @@ showConfirmationModal(modalContent, primaryActionContent, primaryAction) {
},
isLimitedAccount(){
return window?.ACTIVE_ACCOUNT === 1753372418
+ },
+ /**
+ * Find all placeholder positions in a text string (e.g., {}, {var}, {variable})
+ * Returns an array of objects with start, end, and phrase properties for highlighting
+ * @param {string} text - The text to search for placeholders
+ * @returns {Array} Array of placeholder objects with {start, end, phrase}
+ */
+ findPlaceholders: function(text) {
+ if (!text) return [];
+ const placeholders = [];
+ // Find all individual {} pairs, including overlapping ones like {{}}
+ // We search from each position to find the nearest closing brace
+ for (let i = 0; i < text.length; i++) {
+ if (text[i] === '{') {
+ // Find the nearest matching closing brace
+ let depth = 1;
+ for (let j = i + 1; j < text.length && depth > 0; j++) {
+ if (text[j] === '{') {
+ depth++;
+ } else if (text[j] === '}') {
+ depth--;
+ if (depth === 0) {
+ // Found a complete placeholder
+ const placeholder = text.substring(i, j + 1);
+ placeholders.push({
+ start: i,
+ end: j + 1,
+ phrase: placeholder
+ });
+ break; // Found the match for this opening brace, move on
+ }
+ }
+ }
+ }
+ }
+ return placeholders;
}
}
diff --git a/libs/dao/src/main/java/com/akto/dto/ApiInfo.java b/libs/dao/src/main/java/com/akto/dto/ApiInfo.java
index dbe9a510bd..92ceaf05f5 100644
--- a/libs/dao/src/main/java/com/akto/dto/ApiInfo.java
+++ b/libs/dao/src/main/java/com/akto/dto/ApiInfo.java
@@ -73,6 +73,9 @@ public class ApiInfo {
public static final String PARENT_MCP_TOOL_NAMES = "parentMcpToolNames";
private List parentMcpToolNames;
+ public static final String DETECTED_BASE_PROMPT = "detectedBasePrompt";
+ private String detectedBasePrompt;
+
public enum ApiType {
REST, GRAPHQL, GRPC, SOAP
}
@@ -543,4 +546,12 @@ public List getParentMcpToolNames() {
public void setParentMcpToolNames(List parentMcpToolNames) {
this.parentMcpToolNames = parentMcpToolNames;
}
+
+ public String getDetectedBasePrompt() {
+ return detectedBasePrompt;
+ }
+
+ public void setDetectedBasePrompt(String detectedBasePrompt) {
+ this.detectedBasePrompt = detectedBasePrompt;
+ }
}
diff --git a/libs/dao/src/main/java/com/akto/dto/GuardrailPolicies.java b/libs/dao/src/main/java/com/akto/dto/GuardrailPolicies.java
index 0ef74a0e0b..a207bfd7b1 100644
--- a/libs/dao/src/main/java/com/akto/dto/GuardrailPolicies.java
+++ b/libs/dao/src/main/java/com/akto/dto/GuardrailPolicies.java
@@ -50,6 +50,9 @@ public class GuardrailPolicies {
private LLMRule llmRule;
+ // Step 6.5: Base Prompt Rule - for checking intent of user input in agent base prompts with placeholders
+ private BasePromptRule basePromptRule;
+
// Step 7: Server and application settings (old format - backward compatibility)
private List selectedMcpServers;
private List selectedAgentServers;
@@ -118,7 +121,7 @@ public GuardrailPolicies(String name, String description, String blockedMessage,
int updatedTimestamp, String createdBy, String updatedBy, String selectedCollection,
String selectedModel, List deniedTopics, List piiTypes,
List regexPatterns, List regexPatternsV2, Map contentFiltering,
- LLMRule llmRule,
+ LLMRule llmRule, BasePromptRule basePromptRule,
List selectedMcpServers, List selectedAgentServers,
List selectedMcpServersV2, List selectedAgentServersV2,
boolean applyOnResponse, boolean applyOnRequest, String url, double confidenceScore, boolean active) {
@@ -138,6 +141,7 @@ public GuardrailPolicies(String name, String description, String blockedMessage,
this.regexPatternsV2 = regexPatternsV2;
this.contentFiltering = contentFiltering;
this.llmRule = llmRule;
+ this.basePromptRule = basePromptRule;
this.selectedMcpServers = selectedMcpServers;
this.selectedAgentServers = selectedAgentServers;
this.selectedMcpServersV2 = selectedMcpServersV2;
@@ -217,4 +221,17 @@ public LLMRule(boolean enabled, String userPrompt, double confidenceScore) {
this.confidenceScore = confidenceScore;
}
}
+
+ @Getter
+ @Setter
+ @NoArgsConstructor
+ public static class BasePromptRule {
+ private boolean enabled;
+ private double confidenceScore;
+
+ public BasePromptRule(boolean enabled, double confidenceScore) {
+ this.enabled = enabled;
+ this.confidenceScore = confidenceScore;
+ }
+ }
}
\ No newline at end of file