feat: Add generic red-team adversarial testing stage (Promptfoo) - #65
feat: Add generic red-team adversarial testing stage (Promptfoo)#65ikrispin wants to merge 7 commits into
Conversation
Add IntegrationTestScenario support so Konflux applications can run ABEvalFlow A/B evaluations as part of their CI pipeline. Includes: - 9 Tekton tasks adapted for Konflux (parse-snapshot, deploy-agent, prepare, test, evaluate, analyze-scorecard, store, emit-result, cleanup-agent) - PipelineRun definition chaining all tasks with cross-cluster agent deployment on a workload cluster - Makefile and GitHub Actions workflow for publishing Tekton Bundles - Secrets template for workload cluster credentials and LLM config - Google Lightspeed Agent submission as initial POC
Refactor the Konflux integration from a Lightspeed-specific pipeline into a generic evaluation framework that any Konflux application can consume. Changes: - Remove deploy-agent and cleanup-agent tasks from core (moved to the example repo github.com/ikrispin/abevalflow-konflux-example) - Refactor evaluate.yaml to support local/remote eval modes and all engines (a2a, mcpchecker, harbor, ase) with parameterized secrets - Rewrite PipelineRun as a generic 7-stage reference pipeline with standardized parameters (EVAL_ENGINE, AGENT_ENDPOINT, MCP_URL, EVAL_MODE, etc.) - Move Lightspeed submission and IntegrationTestScenario to the separate example repo - Add Konflux integration guide documentation - Update Makefile to publish 7 core task bundles (was 9) - Update secrets template with mode-conditional documentation Tested: Full successful pipeline run on Konflux with the Lightspeed agent example repo (PipelineRun lightspeed-abevalflow-eval-qhx2p, 9/9 tasks succeeded).
Adds adversarial testing via Promptfoo as a new stage in the Konflux evaluation pipeline. The task is fully generic — supports A2A agents, MCP servers, and HTTP endpoints without any application-specific code. - pipeline/tasks/konflux/red-team.yaml: new Tekton task with setup, generate-config, and run-redteam steps - scripts/generate_redteam_config.py: engine-agnostic config generator using --target-url (replaces --agent-endpoint) - konflux-eval-pipelinerun.yaml: inserts red-team between test and evaluate, enabled by default, gated on endpoint availability - aggregate_scorecard.py: consumes redteam-results.json as a security gate in the unified scorecard - Makefile: adds red-team to the Tekton Bundles publish list Modes: "smoke" (~25 tests, basic strategy, ~2 min) and "full" (~1750 tests, all strategies, ~90 min). Only tests plugins relevant to agent behavior (policy, hijacking, prompt-extraction, cybercrime, non-violent-crime) — content safety categories are the LLM's job.
GuyZivRH
left a comment
There was a problem hiding this comment.
Consolidated PR Review: #65 — Konflux + Promptfoo red-team
PR: #65
Author: ikrispin
Branch: ikrispin:konflux-redteam → main
HEAD: 7aa828a9
Size: +3,403 / −0 · 15 files · 3 commits
Prerequisite: #60 (Konflux generic integration) — still open
Consolidated from: kxqbz_, qmwxf_, qmxnk_, zplhk_, xrdmw_ (2026-08-04)
Verdict
Request changes. Direction is strong and worth landing, but the PR is not merge-ready.
Consensus across reviews (4/5 explicit request-changes; 1 approve was high-level and missed the scorecard schema bug): opt-in red-team via Promptfoo is the right product shape, but correctness, CI, and ops gaps must be fixed first. The most serious finding is that the scorecard gate is constructed with invalid GateResult fields and is silently dropped at runtime.
What this PR is
Two stacked capabilities under one PR (title understates scope):
- Full Konflux IntegrationTestScenario stack (same lineage as open #60): parse-snapshot → prepare → test → evaluate → analyze-scorecard → store → emit-result, plus Makefile / GHA bundle push / secrets template / integration guide.
- Opt-in red-team stage between
testandevaluate: runtime Promptfoo config frommetadata.yaml, attack live A2A / MCP / HTTP targets, feedredteam-results.jsoninto the unified scorecard.
parse-snapshot → prepare → test → red-team (opt-in) → evaluate → analyze-scorecard → store → emit-result
Key new pieces: pipeline/tasks/konflux/red-team.yaml, scripts/generate_redteam_config.py, scorecard hook in scripts/aggregate_scorecard.py, params ENABLE_RED_TEAM / RED_TEAM_MODE / RED_TEAM_CONCURRENCY.
What looks good (consensus)
- Opt-in default (
ENABLE_RED_TEAM=false) avoids surprising cost/time for existing consumers. - Runtime config from
metadata.yaml(purpose/policy/auth_context) is the right generic model; no per-app baked suites. - Engine-aware providers (A2A JSON-RPC, native MCP, generic HTTP) are a solid first cut.
- Focused plugins (policy, hijacking, prompt-extraction, cybercrime, non-violent-crime) correctly separate agent behavior from base-model content safety.
- Infra noise filtering (
fetch failed) in task + aggregator reduces judge-timeout false positives. - Artifact path wiring is correct: task writes
reports/<submission>/redteam-results.json; analyze passes that directory as--reports-dir. - Smoke vs full modes match CI vs deep-assessment needs (once defaults are fixed).
- POC narrative (Lightspeed) supports the approach.
Must fix (blocking)
1. CI: ruff format failing
Fails on scripts/aggregate_scorecard.py and scripts/generate_redteam_config.py. Pytest never runs.
ruff format scripts/aggregate_scorecard.py scripts/generate_redteam_config.py2. Scorecard red-team gate is broken (silent drop)
GateResult is constructed with invalid kwargs (name= instead of gate_name, details as str, findings as list[str]). Real schema requires:
gate_name(+ optionalpolicy_key)details: dictfindings: list[Finding](severity,message, optionalrule_id)
The broad except Exception logs a warning and skips the gate, so scorecard integration looks wired but does not work.
Fix: construct a valid GateResult / Finding list; add a unit test with a fixture redteam-results.json. Prefer gate_name="security" + policy_key="red_team" to match other security gates, and respect gate_policy enablement like peer gates.
3. Promptfoo Cloud credentials claimed but not wired
PR body requires promptfoo-cloud-credentials, but:
red-team.yamlmounts no Promptfoo secret / envsecrets-template.yamlhas no such Secret- only
PROMPTFOO_DISABLE_TELEMETRY/CI=trueare set
Fix: add secret to template; mount into run-redteam as the env Promptfoo expects; if ENABLE_RED_TEAM=true and creds missing, fail or emit explicit skipped/error status (not a green pass).
4. MCPChecker target not wired
PipelineRun passes only agent-endpoint: $(params.AGENT_ENDPOINT). MCPChecker users use MCP_URL. Empty endpoint → early skip with redteam-passed=true / findings=0. Claimed MCP support does not run.
Fix: resolve target from AGENT_ENDPOINT or MCP_URL by engine (or pass both and choose in the task). Do not treat misconfiguration as a clean security pass when red-team is enabled.
5. Fail-open on generate / eval / missing results
When red-team is enabled and supposed to run:
- missing
promptfooconfig.yaml→ ERROR thenexit 0 set +earound Promptfoo;EVAL_EXITunused- missing/unparseable JSON → findings
0,passed=true
Fix: fail the step, or write redteam-status.json / set failed-or-skipped results, and omit or fail the scorecard gate when results are absent after a non-skip path.
6. Merge / scope with #60
Branch carries the full Konflux stack plus red-team under a red-team-only title. #60 is still open.
Fix: either merge #60 first and rebase a thin #65, or retitle #65 as “Konflux integration + red-team” and review it as one deliberate unit.
Should fix
| # | Issue | Ask |
|---|---|---|
| 7 | Personal Quay / fork defaults (quay.io/rh-ee-ikrispin/..., some pipeline-repo-url → ikrispin/ABEvalFlow) |
Org registry + RHEcosystemAppEng defaults; pin digests in reference PipelineRun |
| 8 | Docs say 7 core tasks; Makefile has 8 including red-team; guide omits red-team params/secrets | Update architecture, params, secrets |
| 9 | Default RED_TEAM_MODE=full (~90 min–hours) for CI reference |
Default smoke; reserve full for nightly/explicit |
| 10 | CLI --mode defaults to smoke while PipelineRun defaults to full |
Align |
| 11 | --agent-endpoint alias broken (--target-url is required=True) |
Mutual exclusive required group / validate one of two |
| 12 | apiBaseUrl = llm_base_url.rstrip("/v1") is character-based rstrip |
Explicit suffix strip of /v1 then trailing / |
| 13 | No unit tests for generator or scorecard red-team path | Provider shapes, metadata fallbacks, enabled: false, gate construction |
| 14 | responseParser.js copied with || true |
Fail early for eval-engine=a2a if missing |
| 15 | Harbor engine uses OpenAI-style HTTP provider | Document “needs live HTTP endpoint” or skip Harbor like ASE |
| 16 | Mutable :0.1 / :latest tags |
Digest pin for production examples (author acknowledges) |
| 17 | Skip path writes redteam-passed=true |
Prefer skipped/empty results so dashboards are not misleading |
| 18 | Red-team ignores gate_policy |
Wire policy_key="red_team" through standard policy enablement |
Design questions (non-blocking)
- Red-team before evaluate: intentional (probe then functional), or should it run after / in parallel with a budget?
- Konflux-only today — should main OpenShift
ci-pipelineget an optional parity stage later? - Promptfoo Cloud acceptable for Red Hat / air-gapped tenants? Offline fallback?
- When
metadata.red_team.enabled: falsebut pipeline enable is true, should Promptfoo steps be skipped entirely (today generator emits a stub and run may still invoke Promptfoo)? - Confirm Tekton
evaluate.runAfter: [red-team, test]when red-team iswhen:-skipped (expected OK; document it).
Merge bar
-
ruff formatgreen; pytest green - Valid
GateResult/Findingconstruction + unit test (gate actually appears on scorecard) - Promptfoo credentials in secrets template and mounted in task; no silent green on missing creds
- MCP URL wired for
mcpchecker - Fail closed (or explicit skip/error) when enable=true and generate/eval/results fail
- Resolve #60 merge order / retitle scope
- Org Quay + org git defaults; digest pinning plan for reference PipelineRun
- Konflux guide: 8 tasks, red-team params, secrets, harbor limitation
- Default CI mode
smoke; fix--agent-endpointalias; fixrstrip("/v1") - Unit tests for
generate_redteam_config.py - (Recommended) One Konflux smoke run with
ENABLE_RED_TEAM=true
Sources
| File | Stance | Notable unique findings |
|---|---|---|
kxqbz_pr_65_review.md |
Approve | High-level pros; noted secret + tag pinning |
qmwxf_pr65_review.md |
Request changes | CI ruff; secret template; repo URL defaults; doc count; parser/` |
qmxnk_pr65_konflux_redteam.md |
Request changes | GateResult schema bug; gate_policy; full-mode default; #60; harbor skip |
zplhk_pr65_review.md |
Request changes | MCP wiring; creds not mounted; rstrip bug; fail-open results |
xrdmw_pr_65_review.md |
Request changes | Scope vs #60; personal Quay; MCP; fail-open; broken alias; mode mismatch |
Consensus blockers to treat as authoritative: CI format, GateResult schema, Promptfoo auth wiring, MCP target wiring, fail-open run semantics, #60/scope clarity.
Complement Promptfoo with a full-mode Crescendo step that adapts each turn from live agent responses and scores objectives via LLM-as-judge.
Must-fix (blocking): - Remote mode now clones SUBMISSION_REPO_URL in the eval Pod when it differs from PIPELINE_REPO_URL (was only cloning pipeline repo) - Remote Failed pod now exits 1 instead of silently passing - All task defaults now point to RHEcosystemAppEng/ABEvalFlow (was pointing to ikrispin fork in 4 tasks) - Fail closed: exit 1 when report.json is missing after eval, when engine commands fail with no results, and when log extraction fails Should-fix (nice to have): - Wire llm-credentials Secret via optional SecretKeyRef in evaluate - Add LLM_API_KEY param to reference PipelineRun - Document .components[0] default and multi-component footgun - Document engine x mode validation matrix in guide - Add comments about disabled security/quality in reference pipeline - Remove hardcoded LiteLLM URL and OpenShift console URL defaults - Remove hardcoded mcpchecker model defaults (use mcpchecker defaults) - Track ASE iteration failures; fail if all iterations fail
Must-fix:
- Fix ruff format on aggregate_scorecard.py and generate_redteam_config.py
- Fix GateResult construction: use gate_name (not name), details as dict
(not str), findings as list[Finding] (not list[str]), add policy_key
- Wire MCP_URL into red-team task for mcpchecker engine support
- Fail closed: exit 1 when config missing, generate fails, eval produces
no results, or results file absent (was exit 0 / silent green pass)
- Default RED_TEAM_MODE to smoke (was full ~90min) in both task and
PipelineRun for CI-appropriate defaults
Nice-to-have:
- Fix rstrip("/v1") to removesuffix("/v1") (character vs substring strip)
- Update docs: 8 tasks (was 7), add red-team bundle to table
- Add promptfoo-cloud-credentials to secrets template (optional)
|
Thanks for the thorough review @GuyZivRH. Pushed fixes for both PRs: PR #60 — Konflux generic integration (
|
GuyZivRH
left a comment
There was a problem hiding this comment.
PR #65 Consolidated Review — Konflux red-team (Promptfoo + PyRIT Crescendo)
PR: #65
Author: ikrispin
Branch: ikrispin:konflux-redteam → main (cross-fork)
HEAD: ef71b43
Size: +4,834 / −1 · 24 files · 6 commits
CI: FAILURE (ruff on scripts/pyrit_crescendo/run_crescendo.py)
Merge state: BEHIND (~24 commits on main)
Prerequisite: #60 still open (branch embeds full Konflux stack)
Prior GitHub review: CHANGES_REQUESTED @ 7aa828a
Consolidated: 2026-08-06 · prefix bkxqm_
Sources folded in: wqfjn_, qzxwn_, nqjfu_, vhtrn_, mzpxw_
Final verdict
Request changes.
Direction is strong (opt-in Promptfoo breadth + PyRIT Crescendo depth, scorecard gate, smoke default). ef71b43 / f17b362 fixed several prior blockers. One review said Approve; consensus is not merge-ready while CI is red and remaining correctness/security gaps stand.
What shipped (scope > title)
Title says Promptfoo; actual delta:
- Full Konflux ITS stack (same lineage as #60)
- Opt-in Konflux
red-team(Promptfoo smoke/full) - PyRIT Crescendo (
scripts/pyrit_crescendo/*, Containerfile) in full mode - Main OpenShift
phases/red-team.yaml+ci-pipeline.yamlwiring RedTeamConfigonmetadata.yaml- Scorecard gate merging Promptfoo + Crescendo
parse → prepare → test → red-team (opt-in) → evaluate → analyze-scorecard → store → emit-result
Commits
| SHA | Summary |
|---|---|
e8284c7 / fd2f807 |
#60 Konflux stack |
7aa828a |
Promptfoo red-team Konflux task + scorecard |
822ebda |
PyRIT Crescendo |
f17b362 |
#60 hardening (remote submission, fail-closed, org git) |
ef71b43 |
Red-team review fixes (GateResult, MCP, smoke, docs) |
Prior blockers — status at ef71b43
| # | Ask | Status |
|---|---|---|
| 1 | CI ruff on scorecard/generator | Partial — those fixed; CI still fails on PyRIT (I001, UP017) |
| 2 | Invalid GateResult / silent drop |
Fixed — gate_name="security", policy_key="red_team", Finding list, dict details |
| 3 | Promptfoo Cloud creds | Still open — template optional secret exists; task mounts no Promptfoo secret; PR body still says Cloud required for generation |
| 4 | MCP target wiring | Fixed on Konflux; phases/ci path drifts (mode full, weak/missing MCP parity, hardcoded namespace noted) |
| 5 | Fail-open Promptfoo | Mostly fixed (config/generate/no results → exit 1); Crescendo CRESCENDO_EXIT ignored |
| 6 | Scope vs #60 | Still open — ~4.8k line stack |
Consensus strengths
- Opt-in
ENABLE_RED_TEAM=false; KonfluxRED_TEAM_MODE=smoke. - Runtime config from
metadata.yaml+ typedRedTeamConfig. - Engine-aware providers; focused agent-behavior plugins; “fetch failed” filter.
- Promptfoo breadth + Crescendo multi-turn depth (lean httpx/LiteLLM image).
- Scorecard can attach a real red-team security gate after schema fix.
- Guide updated to 8 tasks;
removesuffix("/v1")fix.
Must fix before merge
1. CI green
ruff check --fix scripts/pyrit_crescendo/Confirm full test workflow.
2. Promptfoo auth story consistent
Mount promptfoo-cloud-credentials into run-redteam or update PR body/guide if Cloud is optional/offline. Do not leave “required” docs with an unused template.
3. Stop embedding LLM API keys in generated YAML
generate_redteam_config.py writes apiKey: <llm_api_key> into promptfooconfig.yaml (log/artifact footgun). Prefer env-only (OPENAI_API_KEY) or Promptfoo env interpolation.
4. Fix --agent-endpoint alias
--target-url is still required=True, so alias-only invocation never reaches compat logic. Use mutually exclusive required group / post-parse validation.
5. Crescendo fail-closed in full mode
CRESCENDO_EXIT is logged but not used; non-zero / missing results must not leave the step green.
6. Resolve #60 / retitle
Merge #60 first and rebase a thin #65, or retitle as “Konflux + red-team (Promptfoo/PyRIT)” and review as one stack. Prefer not to land a second Konflux copy while #60 is open.
7. Minimal unit tests (security path)
At least: generator provider shapes; scorecard gate fixtures (redteam-results.json / crescendo); one mocked judge/objective path.
Should fix
| # | Issue | Ask |
|---|---|---|
| 8 | phases/ vs Konflux drift |
Align mode default to smoke; MCP endpoint parity; drop hardcoded ab-eval-flow namespace |
| 9 | Skip path → redteam-passed=true |
Prefer skipped/empty (not false security pass) |
| 10 | gate_policy.is_enabled("red_team") |
Peer gates check policy; red-team always appends if files exist |
| 11 | Personal Quay + mutable tags | Org registry + digests (same as #60) |
| 12 | Air-gapped / Cloud dependency | Document offline path or hard blocker |
| 13 | Brittle "fetch failed" filter |
Prefer structured infra-error signal if available |
| 14 | PipelineRun header “7 stages” | Align to 8 if still stale |
| 15 | PR body understates PyRIT + main CI | Refresh summary |
Suggested GitHub action
Request changes until CI green + auth/secret handling + alias + Crescendo fail-closed + #60 plan + minimal tests. Then re-review; product shape is close.
Merge bar (updated)
- CI green (incl.
pyrit_crescendo) - Valid
GateResult/Findingconstruction - Promptfoo creds mounted or docs stop saying required
- No plaintext LLM key in generated
promptfooconfig.yaml - MCP URL on Konflux red-team
- Phases/ci parity (smoke default, MCP, no hardcoded ns)
- Promptfoo fail-closed on generate / missing results
- Crescendo fail-closed when full mode runs it
- Working
--agent-endpointalias - Unit tests (generator + scorecard gate minimum)
- #60 merge order / retitle
- (Follow-up) Org Quay + digest pins
Must-fix:
- CI green: ruff format/check on pyrit_crescendo (I001 import sort, UP017 datetime.UTC)
- Security: remove plaintext LLM API key from generated promptfooconfig.yaml;
use Promptfoo env interpolation {{env:OPENAI_API_KEY}} instead
- Mount promptfoo-cloud-credentials Secret (optional) + OPENAI_API_KEY in
run-redteam step for Promptfoo Cloud auth and LLM judge
- Fix --agent-endpoint alias: --target-url was required=True blocking the
alias from working; now uses post-parse validation with parser.error()
- Crescendo fail-closed: exit 1 when CRESCENDO_EXIT != 0 and no results
file produced (was logging exit code but ignoring it)
Tests:
- Add tests/test_redteam.py with 20 tests covering:
- Generator provider shapes (A2A JSON-RPC, MCP native, HTTP)
- Smoke vs full mode (test count, strategies)
- Metadata handling (full config, disabled, missing, partial)
- Env interpolation for API keys (no plaintext leak)
- URL suffix stripping (removesuffix vs rstrip)
- Scorecard gate construction (GateResult schema, Finding objects)
- Fetch-failed exclusion from findings
- Combined Promptfoo + Crescendo gate
- All-passing and no-results edge cases
Summary
Adds a generic red-team adversarial testing stage to the ABEvalFlow Konflux pipeline. Any submission with a live endpoint (A2A agent, MCP server, or HTTP API) can be adversarially tested using Promptfoo as part of the CI/CD evaluation — without any application-specific code.
The red-team task generates domain-aware attacks at runtime based on the submission's
metadata.yaml(purpose, policy, auth context), sends them to the target, and grades responses with an LLM-as-judge. Results feed into the unified scorecard as a security gate.Builds on top of #60 (generic Konflux integration). Adds the 8th core Tekton task to the framework.
Architecture
The red-team stage sits between
testandevaluate, gated onENABLE_RED_TEAM=true:The task is skipped when:
ENABLE_RED_TEAM=false(default — opt-in)EVAL_ENGINE=ase(no live endpoint to attack)agent-endpointprovidedWhat's included
New Tekton task
pipeline/tasks/konflux/red-team.yamlUpdated files
scripts/generate_redteam_config.py--target-urlto produce correct Promptfoo provider (A2A JSON-RPC, MCP native, or HTTP).pipeline/integration/konflux-eval-pipelinerun.yamlwhengating. AddsENABLE_RED_TEAM,RED_TEAM_MODE,RED_TEAM_CONCURRENCYparams.scripts/aggregate_scorecard.pyredteam-results.jsonas a security gate in the unified scorecard. Excludes infrastructure errors from findings count.pipeline/integration/Makefilered-teamto the Tekton Bundles list (make bundlespublishes 8 tasks).New OCI bundle
abevalflow-task-red-team:0.1Container image (reused)
quay.io/rh-ee-ikrispin/abevalflow-redteam:latestEngine support
a2aresponseParser.jsmcpcheckerid: mcp)harbor/ HTTPaseTwo modes
smokefull(default)Both modes generate attacks on the fly from the submission's
metadata.yamlusing Promptfoo Cloud. Only tests plugins relevant to agent behavior (policy, hijacking, prompt-extraction, cybercrime, non-violent-crime). Content safety categories (weapons, sexual content, self-harm, etc.) are the underlying LLM's responsibility and are excluded.New parameters
ENABLE_RED_TEAM"false"RED_TEAM_MODEsmoke(quick) orfull(comprehensive)"full"RED_TEAM_CONCURRENCY"12"How a submission enables red-teaming
Add to
metadata.yaml:The
purposeandauth_contextfields drive Promptfoo's domain-aware attack generation. If omitted, the task falls back to the submission'sdescriptionfield and a generic security policy.Scorecard integration
When red-team results exist, the scorecard aggregator adds a
red_teamsecurity gate:1.0 - (findings / total_tests)This gate participates in the overall
pass/warn/failrecommendation alongside engine, security, quality, and behavioral gates.Key design decisions
Runtime config generation — No baked-in test suites. Config is generated fresh from submission metadata every run. This ensures every target gets tests tailored to its declared scope and purpose.
Opt-in by default —
ENABLE_RED_TEAM=falsekeeps the pipeline unchanged for existing consumers. Teams enable it when ready.Infrastructure error filtering — The scorecard excludes "fetch failed" errors (LLM judge timeouts) from the findings count to avoid false positives from infrastructure issues.
Focused plugin categories — Only tests plugins relevant to agent behavior (policy, hijacking, prompt-extraction, cybercrime, non-violent-crime). Content safety categories (weapons, sexual content, self-harm, etc.) are the underlying LLM's responsibility, not the agent's.
Backwards-compatible CLI —
generate_redteam_config.pyaccepts both--target-url(new) and--agent-endpoint(deprecated alias) for compatibility with the existingab-eval-flowpipeline.POC validation
The red-team approach was validated in a POC against the Google Lightspeed Agent (A2A):
Known limitations
promptfoo-cloud-credentialsSecret with an API key. Attack generation uses the Promptfoo Cloud service in both modes.responseParser.jshandles safety-blocked responses. MCP and HTTP engines use Promptfoo's built-in parsing.:0.1tag (mutable). Should pin by digest for production.Related