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