Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

154 changes: 154 additions & 0 deletions apps/dashboard/src/main/resources/struts.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9045,6 +9045,160 @@
</result>
</action>

<!-- Prompt Hardening Actions -->
<action name="api/fetchAllPrompts" class="com.akto.action.prompt_hardening.PromptHardeningAction" method="fetchAllPrompts">
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
<interceptor-ref name="roleAccessInterceptor">
<param name="featureLabel">SENSITIVE_DATA</param>
<param name="accessType">READ</param>
</interceptor-ref>
<result name="FORBIDDEN" type="json">
<param name="statusCode">403</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
<result name="SUCCESS" type="json">
</result>
<result name="ERROR" type="json">
<param name="statusCode">422</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
</action>

<action name="api/savePrompt" class="com.akto.action.prompt_hardening.PromptHardeningAction" method="savePrompt">
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
<interceptor-ref name="roleAccessInterceptor">
<param name="featureLabel">SENSITIVE_DATA</param>
<param name="accessType">READ_WRITE</param>
<param name="actionDescription">User saved a prompt hardening template</param>
</interceptor-ref>
<result name="FORBIDDEN" type="json">
<param name="statusCode">403</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
<result name="SUCCESS" type="json">
</result>
<result name="ERROR" type="json">
<param name="statusCode">422</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
</action>

<action name="api/deletePrompt" class="com.akto.action.prompt_hardening.PromptHardeningAction" method="deletePrompt">
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
<interceptor-ref name="roleAccessInterceptor">
<param name="featureLabel">SENSITIVE_DATA</param>
<param name="accessType">READ_WRITE</param>
<param name="actionDescription">User deleted a prompt hardening template</param>
</interceptor-ref>
<result name="FORBIDDEN" type="json">
<param name="statusCode">403</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
<result name="SUCCESS" type="json">
</result>
<result name="ERROR" type="json">
<param name="statusCode">422</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
</action>

<action name="api/togglePromptStatus" class="com.akto.action.prompt_hardening.PromptHardeningAction" method="togglePromptStatus">
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
<interceptor-ref name="roleAccessInterceptor">
<param name="featureLabel">SENSITIVE_DATA</param>
<param name="accessType">READ_WRITE</param>
<param name="actionDescription">User toggled prompt template status</param>
</interceptor-ref>
<result name="FORBIDDEN" type="json">
<param name="statusCode">403</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
<result name="SUCCESS" type="json">
</result>
<result name="ERROR" type="json">
<param name="statusCode">422</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
</action>

<action name="api/testSystemPrompt" class="com.akto.action.prompt_hardening.PromptHardeningAction" method="testSystemPrompt">
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
<interceptor-ref name="roleAccessInterceptor">
<param name="featureLabel">SENSITIVE_DATA</param>
<param name="accessType">READ</param>
<param name="actionDescription">User tested a system prompt for vulnerabilities</param>
</interceptor-ref>
<result name="FORBIDDEN" type="json">
<param name="statusCode">403</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
<result name="SUCCESS" type="json">
</result>
<result name="ERROR" type="json">
<param name="statusCode">422</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
</action>

<action name="api/generateMaliciousUserInput" class="com.akto.action.prompt_hardening.PromptHardeningAction" method="generateMaliciousUserInput">
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
<interceptor-ref name="roleAccessInterceptor">
<param name="featureLabel">SENSITIVE_DATA</param>
<param name="accessType">READ</param>
<param name="actionDescription">User generated malicious user input from attack patterns</param>
</interceptor-ref>
<result name="FORBIDDEN" type="json">
<param name="statusCode">403</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
<result name="SUCCESS" type="json">
</result>
<result name="ERROR" type="json">
<param name="statusCode">422</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
</action>

<action name="api/hardenSystemPrompt" class="com.akto.action.prompt_hardening.PromptHardeningAction" method="hardenSystemPrompt">
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
<interceptor-ref name="roleAccessInterceptor">
<param name="featureLabel">SENSITIVE_DATA</param>
<param name="accessType">READ</param>
<param name="actionDescription">User hardened a system prompt</param>
</interceptor-ref>
<result name="FORBIDDEN" type="json">
<param name="statusCode">403</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
<result name="SUCCESS" type="json">
</result>
<result name="ERROR" type="json">
<param name="statusCode">422</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
</action>

<action name="api/fetchSuspectSampleData"
class="com.akto.action.threat_detection.SuspectSampleDataAction"
method="fetchSampleData">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ export default function LeftNav() {
],
key: "5",
},
...(dashboardCategory === "Agentic Security" && func.isDemoAccount() ? [{
...(dashboardCategory === "Agentic Security"? [{
label: (
<Text variant="bodyMd" fontWeight="medium">
Prompt Hardening
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import PromptHardeningStore from "./promptHardeningStore"
import PersistStore from "../../../main/PersistStore"

import TitleWithInfo from "@/apps/dashboard/components/shared/TitleWithInfo"
import api from "./api"

import "./PromptHardening.css"

Expand All @@ -31,7 +32,28 @@ const PromptHardening = () => {
}

const fetchAllPrompts = async () => {
// Initialize with sample prompts data structured like test editor
try {
// Fetch prompts from backend API using the standard request pattern
const data = await api.fetchAllPrompts()

// If backend returns data, use it; otherwise fall back to sample data
if (data && data.promptsObj) {
setPromptsObj(data.promptsObj)

// Set default selected prompt if available
const firstCategory = Object.keys(data.promptsObj.customPrompts || {})[0]
if (firstCategory && data.promptsObj.customPrompts[firstCategory].length > 0) {
setSelectedPrompt(data.promptsObj.customPrompts[firstCategory][0])
}

setLoading(false)
return
}
} catch (error) {
console.error('Error fetching prompts from backend, using sample data:', error)
}

// Fallback to sample prompts data if backend call fails
const samplePrompts = {
customPrompts: {
"Prompt Injection": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import request from "@/util/request"

const promptHardeningApi = {
fetchAllPrompts: async () => {
return await request({
url: '/api/fetchAllPrompts',
method: 'post',
data: {}
})
},

savePrompt: async (templateId, content, category, inactive) => {
return await request({
url: '/api/savePrompt',
method: 'post',
data: { templateId, content, category, inactive }
})
},

deletePrompt: async (templateId) => {
return await request({
url: '/api/deletePrompt',
method: 'post',
data: { templateId },
})
},

togglePromptStatus: async (templateId) => {
return await request({
url: '/api/togglePromptStatus',
method: 'post',
data: { templateId }
})
},

testSystemPrompt: async (systemPrompt, userInput, attackPatterns = null, detectionRules = null) => {
// Build request data, only including non-null optional fields
const data = {
systemPrompt,
userInput
}

// Only include attackPatterns if it's not null and has items
if (attackPatterns && attackPatterns.length > 0) {
data.attackPatterns = attackPatterns
}

// Only include detectionRules if it's not null
// Frontend parses YAML using js-yaml and sends structured data
if (detectionRules) {
data.detectionRules = detectionRules
}

return await request({
url: '/api/testSystemPrompt',
method: 'post',
data
})
},

generateMaliciousUserInput: async (attackPatterns) => {
return await request({
url: '/api/generateMaliciousUserInput',
method: 'post',
data: { attackPatterns }
})
},

hardenSystemPrompt: async (systemPrompt, vulnerabilityContext = null) => {
const data = { systemPrompt }

if (vulnerabilityContext) {
data.vulnerabilityContext = vulnerabilityContext
}

return await request({
url: '/api/hardenSystemPrompt',
method: 'post',
data
})
}
}

export default promptHardeningApi

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useEffect, useState } from "react"
import { useNavigate } from "react-router-dom"

import { Badge, Box, Button, HorizontalStack, Icon, Navigation, Text, TextField, Tooltip, VerticalStack } from "@shopify/polaris"
import {ChevronDownMinor, ChevronRightMinor, SearchMinor, CirclePlusMinor} from "@shopify/polaris-icons"
import { Box, Button, HorizontalStack, Icon, Navigation, Text, TextField, Tooltip, VerticalStack } from "@shopify/polaris"
import {ChevronDownMinor, ChevronRightMinor, SearchMinor} from "@shopify/polaris-icons"

import PromptHardeningStore from "../promptHardeningStore"
import TitleWithInfo from "@/apps/dashboard/components/shared/TitleWithInfo"
Expand Down Expand Up @@ -113,6 +113,8 @@ const PromptExplorer = ({addCustomPrompt}) => {
key: category,
param: type === "CUSTOM" ? '_custom' : '_akto',
label: <Text variant="bodyMd">{category}</Text>,
url: '#',
onClick: () => selectedFunc(category + (type === "CUSTOM" ? '_custom' : '_akto')),
subNavigationItems: prompts[category]
})
}
Expand Down Expand Up @@ -149,36 +151,32 @@ const PromptExplorer = ({addCustomPrompt}) => {
},[])

function getItems(aktoItems){
const arr = aktoItems.map(obj => {
const isExpanded = selectedCategory === (obj.key+obj.param);
return {
...obj,
selected: isExpanded,
icon: isExpanded ? ChevronDownMinor : ChevronRightMinor,
onClick: () => selectedFunc(obj.key+obj.param), // Add onClick to make category clickable
subNavigationItems: obj.subNavigationItems.map((item)=>{
return{
label: (
<Tooltip content={item.label} dismissOnMouseOut width="wide" preferredPosition="below">
<div className={item.label === selectedPrompt?.label ? "active-left-test" : ""}>
<Text
variant={item.label === selectedPrompt?.label ? "headingSm" : "bodyMd"} as="h4"
color={item.label === selectedPrompt?.label ? "default" : "subdued"} truncate
>
{item.label}
</Text>
</div>
</Tooltip>
),
onClick: (()=> {
navigate(`/dashboard/prompt-playground/${item.value}`)
setSelectedPrompt(item)
}),
key: item.value
}
})
}
})
const arr = aktoItems.map(obj => ({
...obj,
selected: selectedCategory === (obj.key+obj.param),
icon: selectedCategory === (obj.key+obj.param) ? ChevronDownMinor : ChevronRightMinor,
subNavigationItems: obj.subNavigationItems.map((item)=>{
return{
label: (
<Tooltip content={item.label} dismissOnMouseOut width="wide" preferredPosition="below">
<div className={item.label === selectedPrompt?.label ? "active-left-test" : ""}>
<Text
variant={item.label === selectedPrompt?.label ? "headingSm" : "bodyMd"} as="h4"
color={item.label === selectedPrompt?.label ? "default" : "subdued"} truncate
>
{item.label}
</Text>
</div>
</Tooltip>
),
onClick: (()=> {
navigate(`/dashboard/prompt-hardening/${item.value}`)
setSelectedPrompt(item)
}),
key: item.value
}
})
}))
return arr
}

Expand Down
Loading
Loading