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
54 changes: 40 additions & 14 deletions packages/tui/internal/screens/evaluations/form_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ import (
"github.com/rogue/tui/internal/theme"
)

// authTypeDisplayName returns the human-readable label for an auth type string
func authTypeDisplayName(authType string) string {
switch authType {
case "no_auth", "":
return "No Auth"
case "api_key":
return "API Key"
case "bearer_token":
return "Bearer Token"
case "basic":
return "Basic Auth"
default:
return authType
}
}

// RenderForm renders the new evaluation form screen
func RenderForm(state *FormState) string {
t := theme.CurrentTheme()
Expand Down Expand Up @@ -186,6 +202,9 @@ func RenderForm(state *FormState) string {
evalMode = "πŸ”΄ Red Team"
}

// Prepare auth type display value
authTypeDisplay := authTypeDisplayName(state.AuthType)

// Prepare scan type display value (only for Red Team mode)
scanTypeDisplay := "Basic"
if state.ScanType == "full" {
Expand All @@ -198,11 +217,11 @@ func RenderForm(state *FormState) string {
}

// Determine start button index based on mode
// Policy mode: StartButton at 6
// Red Team mode: StartButton at 8 (after ScanType at 6, Configure at 7)
startButtonIndex := 6
// Policy mode: StartButton at 8
// Red Team mode: StartButton at 10 (after ScanType at 8, Configure at 9)
startButtonIndex := 8
if isRedTeam {
startButtonIndex = 8
startButtonIndex = 10
}

// Helper function to render the start button
Expand Down Expand Up @@ -274,34 +293,41 @@ func RenderForm(state *FormState) string {
// Build the content sections
// Protocol is shown first, then Agent URL/Python File, then Transport (if applicable)
isPythonProtocol := protocol == "python"
showAuthCredentials := !isPythonProtocol && state.AuthType != "no_auth" && state.AuthType != ""
var formFields []string

if isPythonProtocol {
// Python protocol: show Protocol, Python File, then other fields (no Transport)
// Python protocol: show Protocol, Python File, then other fields (no Transport/Auth)
pythonFile := state.PythonEntrypointFile
formFields = []string{
renderDropdownField(0, "Protocol:", protocol),
renderTextField(1, "Python File:", pythonFile),
// No Transport field for Python protocol
renderTextField(3, "Judge LLM:", judge),
renderToggleField(4, "Deep Test:", deep),
renderDropdownField(5, "Mode:", evalMode),
// No Transport, AuthType, AuthCredentials for Python protocol
renderTextField(5, "Judge LLM:", judge),
renderToggleField(6, "Deep Test:", deep),
renderDropdownField(7, "Mode:", evalMode),
}
} else {
// A2A/MCP protocols: show Protocol, Agent URL, Transport, then other fields
// A2A/MCP/OpenAI protocols: show Protocol, Agent URL, Transport, Auth, then other fields
formFields = []string{
renderDropdownField(0, "Protocol:", protocol),
renderTextField(1, "Agent URL:", agent),
renderDropdownField(2, "Transport:", transport),
renderTextField(3, "Judge LLM:", judge),
renderToggleField(4, "Deep Test:", deep),
renderDropdownField(5, "Mode:", evalMode),
renderDropdownField(3, "Auth Type:", authTypeDisplay),
}
if showAuthCredentials {
formFields = append(formFields, renderTextField(4, "Credentials:", state.AuthCredentials))
}
formFields = append(formFields,
renderTextField(5, "Judge LLM:", judge),
renderToggleField(6, "Deep Test:", deep),
renderDropdownField(7, "Mode:", evalMode),
)
}

// Add ScanType dropdown and Configure button only in Red Team mode
if isRedTeam {
formFields = append(formFields, renderDropdownField(6, "Scan Type:", scanTypeDisplay))
formFields = append(formFields, renderDropdownField(int(FormFieldScanType), "Scan Type:", scanTypeDisplay))

// Configure button for custom scan configuration - styled like other fields
configActive := state.CurrentField == int(FormFieldConfigureButton)
Expand Down
19 changes: 12 additions & 7 deletions packages/tui/internal/screens/evaluations/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@ const (
FormFieldProtocol FormField = iota
FormFieldAgentURL // Also used for PythonEntrypointFile when protocol is Python
FormFieldTransport
FormFieldAuthType // Skipped for Python protocol
FormFieldAuthCredentials // Skipped for Python protocol; skipped when AuthType is no_auth
FormFieldJudgeModel
FormFieldDeepTest
FormFieldEvaluationMode
// Policy mode start button / Red Team mode scan type (both at index 6)
// Policy mode start button / Red Team mode scan type (both at index 8)
FormFieldStartButtonPolicy
FormFieldConfigureButton // 7 - Red Team mode only
FormFieldStartButtonRedTeam // 8 - Red Team mode only
FormFieldConfigureButton // 9 - Red Team mode only
FormFieldStartButtonRedTeam // 10 - Red Team mode only
)

// FormFieldScanType is an alias - in Red Team mode, index 6 is the scan type field
// FormFieldScanType is an alias - in Red Team mode, index 8 is the scan type field
const FormFieldScanType = FormFieldStartButtonPolicy

// FormState contains all data needed to render the evaluation form
Expand All @@ -31,6 +33,8 @@ type FormState struct {
AgentURL string
Protocol string
Transport string
AuthType string // "no_auth", "api_key", "bearer_token", "basic"
AuthCredentials string // Credential value (API key, token, or base64 username:password)
PythonEntrypointFile string // Path to Python file with call_agent function (for Python protocol)
JudgeModel string
DeepTest bool
Expand All @@ -40,9 +44,10 @@ type FormState struct {
ScanType string // "basic", "full", or "custom" (only shown in Red Team mode)

// Editing state
// Policy mode: 0: Protocol, 1: AgentURL/PythonFile, 2: Transport, 3: JudgeModel, 4: DeepTest, 5: EvaluationMode, 6: StartButton
// Red Team mode: 0: Protocol, 1: AgentURL/PythonFile, 2: Transport, 3: JudgeModel, 4: DeepTest, 5: EvaluationMode, 6: ScanType, 7: Configure, 8: StartButton
// Note: Transport field is skipped for Python protocol
// Policy mode: 0: Protocol, 1: AgentURL/PythonFile, 2: Transport, 3: AuthType, 4: AuthCredentials, 5: JudgeModel, 6: DeepTest, 7: EvaluationMode, 8: StartButton
// Red Team mode: 0: Protocol, 1: AgentURL/PythonFile, 2: Transport, 3: AuthType, 4: AuthCredentials, 5: JudgeModel, 6: DeepTest, 7: EvaluationMode, 8: ScanType, 9: Configure, 10: StartButton
// Note: Transport, AuthType, AuthCredentials fields are skipped for Python protocol
// Note: AuthCredentials field is skipped when AuthType is no_auth
CurrentField int
CursorPos int

Expand Down
2 changes: 2 additions & 0 deletions packages/tui/internal/screens/redteam/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ var AttackCatalog = map[string]*Attack{
"permission-escalation": {ID: "permission-escalation", Name: "Permission Escalation", Category: AttackCategorySingleTurn, Description: "Attempts to bypass permission checks by claiming elevated status", Premium: true},
"system-override": {ID: "system-override", Name: "System Override", Category: AttackCategorySingleTurn, Description: "Uses explicit system override commands to bypass restrictions", Premium: true},
"semantic-manipulation": {ID: "semantic-manipulation", Name: "Semantic Manipulation", Category: AttackCategorySingleTurn, Description: "Uses semantic tricks and complex phrasing to disguise intent", Premium: true},
// Indirect injection attacks
"html-indirect-prompt-injection": {ID: "html-indirect-prompt-injection", Name: "HTML Indirect Prompt Injection", Category: AttackCategorySingleTurn, Description: "Tricks web-browsing agents into fetching a URL that serves HTML with hidden prompt injection instructions", Premium: true},
// Advanced premium attacks
"citation": {ID: "citation", Name: "Citation", Category: AttackCategorySingleTurn, Description: "Frames harmful content as academic citations or references", Premium: true},
"gcg": {ID: "gcg", Name: "GCG (Greedy Coordinate Gradient)", Category: AttackCategorySingleTurn, Description: "Gradient-based adversarial suffix generation", Premium: true},
Expand Down
18 changes: 17 additions & 1 deletion packages/tui/internal/tui/common_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (m Model) handlePasteMsg(msg tea.PasteMsg) (Model, tea.Cmd) {
return m, nil
}

// Only paste into text fields (Agent URL/Python File and Judge Model)
// Only paste into text fields (Agent URL/Python File, Auth Credentials, and Judge Model)
switch m.evalState.currentField {
case EvalFieldAgentURL:
// Insert at cursor position (for Agent URL or Python File depending on protocol)
Expand All @@ -68,6 +68,22 @@ func (m Model) handlePasteMsg(msg tea.PasteMsg) (Model, tea.Cmd) {
m.evalState.PythonEntrypointFile,
m.evalState.EvaluationMode,
m.getScanType(),
m.evalState.AgentAuthType,
m.evalState.AgentAuthCredentials,
)
case EvalFieldAuthCredentials:
runes := []rune(m.evalState.AgentAuthCredentials)
m.evalState.AgentAuthCredentials = string(runes[:m.evalState.cursorPos]) + cleanText + string(runes[m.evalState.cursorPos:])
m.evalState.cursorPos += len([]rune(cleanText))
go saveUserConfig(
m.evalState.AgentProtocol,
m.evalState.AgentTransport,
m.evalState.AgentURL,
m.evalState.PythonEntrypointFile,
m.evalState.EvaluationMode,
m.getScanType(),
m.evalState.AgentAuthType,
m.evalState.AgentAuthCredentials,
)
case EvalFieldJudgeModel:
// Insert at cursor position
Expand Down
2 changes: 2 additions & 0 deletions packages/tui/internal/tui/eval_form_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ func (m Model) RenderNewEvaluation() string {
AgentURL: m.evalState.AgentURL,
Protocol: string(m.evalState.AgentProtocol),
Transport: string(m.evalState.AgentTransport),
AuthType: string(m.evalState.AgentAuthType),
AuthCredentials: m.evalState.AgentAuthCredentials,
PythonEntrypointFile: m.evalState.PythonEntrypointFile,
JudgeModel: m.evalState.JudgeModel,
DeepTest: m.evalState.DeepTest,
Expand Down
78 changes: 65 additions & 13 deletions packages/tui/internal/tui/eval_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,26 @@ const (
type EvalFormField int

// EvalField constants for form field indices
// Policy mode: Protocol(0), AgentURL/PythonFile(1), Transport(2), JudgeModel(3), DeepTest(4), EvaluationMode(5), StartButton(6)
// Red Team mode adds: ScanType(6), ConfigureButton(7), StartButton(8)
// Note: Transport field is skipped for Python protocol
// Policy mode: Protocol(0), AgentURL/PythonFile(1), Transport(2), AuthType(3), AuthCredentials(4), JudgeModel(5), DeepTest(6), EvaluationMode(7), StartButton(8)
// Red Team mode adds: ScanType(8), ConfigureButton(9), StartButton(10)
// Note: Transport, AuthType, AuthCredentials fields are skipped for Python protocol
// Note: AuthCredentials field is skipped when AuthType is no_auth
const (
EvalFieldProtocol EvalFormField = iota
EvalFieldAgentURL // Also used for PythonEntrypointFile when protocol is Python
EvalFieldTransport
EvalFieldAuthType // Skipped for Python protocol
EvalFieldAuthCredentials // Skipped for Python protocol; skipped when AuthType is no_auth
EvalFieldJudgeModel
EvalFieldDeepTest
EvalFieldEvaluationMode
// Policy mode start button / Red Team mode scan type (both at index 6)
// Policy mode start button / Red Team mode scan type (both at index 8)
EvalFieldStartButtonPolicy
EvalFieldConfigureButton // 7 - Red Team mode only
EvalFieldStartButtonRedTeam // 8 - Red Team mode only
EvalFieldConfigureButton // 9 - Red Team mode only
EvalFieldStartButtonRedTeam // 10 - Red Team mode only
)

// EvalFieldScanType is an alias - in Red Team mode, index 6 is the scan type field
// EvalFieldScanType is an alias - in Red Team mode, index 8 is the scan type field
const EvalFieldScanType = EvalFieldStartButtonPolicy

// ScanType represents the type of red team scan
Expand Down Expand Up @@ -89,6 +92,8 @@ type EvaluationViewState struct {
AgentURL string
AgentProtocol Protocol
AgentTransport Transport
AgentAuthType AuthType
AgentAuthCredentials string
PythonEntrypointFile string // Path to Python file with call_agent function (for Python protocol)
JudgeModel string
ParallelRuns int
Expand Down Expand Up @@ -121,9 +126,10 @@ type EvaluationViewState struct {
StructuredSummary StructuredSummary

// Editing state for New Evaluation
// Policy mode: 0: Protocol, 1: AgentURL/PythonFile, 2: Transport, 3: JudgeModel, 4: DeepTest, 5: EvaluationMode, 6: StartButton
// Red Team mode: 0: Protocol, 1: AgentURL/PythonFile, 2: Transport, 3: JudgeModel, 4: DeepTest, 5: EvaluationMode, 6: ScanType, 7: ConfigureButton, 8: StartButton
// Note: Transport field is skipped for Python protocol
// Policy mode: 0: Protocol, 1: AgentURL/PythonFile, 2: Transport, 3: AuthType, 4: AuthCredentials, 5: JudgeModel, 6: DeepTest, 7: EvaluationMode, 8: StartButton
// Red Team mode: 0: Protocol, 1: AgentURL/PythonFile, 2: Transport, 3: AuthType, 4: AuthCredentials, 5: JudgeModel, 6: DeepTest, 7: EvaluationMode, 8: ScanType, 9: ConfigureButton, 10: StartButton
// Note: Transport, AuthType, AuthCredentials fields are skipped for Python protocol
// Note: AuthCredentials field is skipped when AuthType is no_auth
currentField EvalFormField // Field index for form navigation
cursorPos int // rune index in current text field
}
Expand All @@ -142,6 +148,8 @@ type UserConfigFromFile struct {
PythonEntrypointFile string `json:"python_entrypoint_file"`
EvaluationMode string `json:"evaluation_mode"`
ScanType string `json:"scan_type"`
AuthType string `json:"auth_type"`
AuthCredentials string `json:"auth_credentials"`
}

// loadUserConfigFromWorkdir reads .rogue/user_config.json upward from CWD
Expand Down Expand Up @@ -184,8 +192,8 @@ func findUserConfigPath() string {
return filepath.Join(wd, ".rogue", "user_config.json")
}

// saveUserConfig saves the protocol, evaluation mode, scan type and other settings to user_config.json
func saveUserConfig(protocol Protocol, transport Transport, agentURL, pythonEntrypointFile string, evaluationMode EvaluationMode, scanType ScanType) error {
// saveUserConfig saves the protocol, evaluation mode, scan type, auth settings and other settings to user_config.json
func saveUserConfig(protocol Protocol, transport Transport, agentURL, pythonEntrypointFile string, evaluationMode EvaluationMode, scanType ScanType, authType AuthType, authCredentials string) error {
configPath := findUserConfigPath()

// Read existing config to preserve other fields
Expand All @@ -198,11 +206,15 @@ func saveUserConfig(protocol Protocol, transport Transport, agentURL, pythonEntr
existingData["protocol"] = string(protocol)
if protocol == ProtocolPython {
existingData["python_entrypoint_file"] = pythonEntrypointFile
// Clear transport for Python protocol
// Clear transport and auth for Python protocol
delete(existingData, "transport")
delete(existingData, "auth_type")
delete(existingData, "auth_credentials")
} else {
existingData["transport"] = string(transport)
existingData["evaluated_agent_url"] = agentURL
existingData["auth_type"] = string(authType)
existingData["auth_credentials"] = authCredentials
// Clear python entrypoint for non-Python protocols
delete(existingData, "python_entrypoint_file")
}
Expand Down Expand Up @@ -304,6 +316,11 @@ func LoadEvaluationStateFromConfig(appConfig *config.Config) (*EvaluationViewSta
if userConfig.Transport != "" {
agentTransport = Transport(userConfig.Transport)
}
agentAuthType := AuthTypeNoAuth
if userConfig.AuthType != "" {
agentAuthType = AuthType(userConfig.AuthType)
}
agentAuthCredentials := userConfig.AuthCredentials
pythonEntrypointFile := userConfig.PythonEntrypointFile

// Load evaluation mode from config
Expand Down Expand Up @@ -384,6 +401,8 @@ func LoadEvaluationStateFromConfig(appConfig *config.Config) (*EvaluationViewSta
AgentURL: agentURL,
AgentProtocol: agentProtocol,
AgentTransport: agentTransport,
AgentAuthType: agentAuthType,
AgentAuthCredentials: agentAuthCredentials,
PythonEntrypointFile: pythonEntrypointFile,
JudgeModel: judgeModel,
ParallelRuns: 1,
Expand Down Expand Up @@ -424,6 +443,8 @@ func (m *Model) startEval(ctx context.Context, st *EvaluationViewState) {
st.AgentURL,
st.AgentProtocol,
st.AgentTransport,
st.AgentAuthType,
st.AgentAuthCredentials,
st.Scenarios,
st.JudgeModel,
st.ParallelRuns,
Expand Down Expand Up @@ -575,6 +596,37 @@ func (st *EvaluationViewState) cycleTransport(reverse bool) {
st.AgentTransport = transports[currentIdx]
}

// getAllAuthTypes returns all available auth type options
func getAllAuthTypes() []AuthType {
return []AuthType{AuthTypeNoAuth, AuthTypeAPIKey, AuthTypeBearer, AuthTypeBasic}
}

// cycleAuthType cycles to the next auth type option
func (st *EvaluationViewState) cycleAuthType(reverse bool) {
authTypes := getAllAuthTypes()
currentIdx := 0
for i, a := range authTypes {
if a == st.AgentAuthType {
currentIdx = i
break
}
}

if reverse {
currentIdx--
if currentIdx < 0 {
currentIdx = len(authTypes) - 1
}
} else {
currentIdx++
if currentIdx >= len(authTypes) {
currentIdx = 0
}
}

st.AgentAuthType = authTypes[currentIdx]
}

// cycleEvaluationMode cycles between Policy and Red Team modes
func (st *EvaluationViewState) cycleEvaluationMode(reverse bool) {
if reverse {
Expand Down
Loading
Loading