Skip to content

feat: implement SARIF output and improve redteam test coverage#36

Merged
leonbreukelman merged 15 commits into
mainfrom
feat/sarif-and-redteam-coverage
Jan 20, 2026
Merged

feat: implement SARIF output and improve redteam test coverage#36
leonbreukelman merged 15 commits into
mainfrom
feat/sarif-and-redteam-coverage

Conversation

@leonbreukelman

Copy link
Copy Markdown
Owner

Summary

Implements SARIF output for lint command and improves redteam module test coverage.

Changes

SARIF Output (Closes #35)

  • Wire up existing create_sarif_from_issues() to CLI lint --format sarif

Redteam Coverage

  • Add 10 new tests for better mock coverage:
    • TestDeepTeamUnavailable: 3 tests
    • TestConfigYAMLLoading: 2 tests
    • TestRunnerYAMLAndHTTP: 3 tests
    • TestMockAdapterEdgeCases: 2 tests

Coverage

Module Before After
config.py 77% 96%
runner.py 75% 85%
Overall 87.76% 88.32%

Testing

  • 704 tests pass, 2 skipped
  • All SARIF tests pass (37 tests)
  • quaestor lint --format sarif outputs valid SARIF 2.1.0 JSON

Bazzinga Agent and others added 8 commits January 16, 2026 09:14
Add red team adversarial testing capabilities:

- New `quaestor/redteam/` module with DeepTeam adapter
  - models.py: VulnerabilityType, AttackMethod, AttackResult, RedTeamReport
  - config.py: YAML-based playbook system (quick, standard, comprehensive, owasp-llm)
  - adapter.py: DeepTeamAdapter wrapping DeepTeam APIs with mock fallback
  - runner.py: RedTeamRunner for HTTP/callback agent testing

- CLI command `quaestor redteam` with:
  - Playbook selection (--playbook)
  - YAML config support (--config)
  - Mock mode for testing without DeepTeam (--mock)
  - Console and JSON output formats

- Reporting integration:
  - HTML reports with red team section
  - SARIF output for CI integration (add_redteam_report)
  - Verdict conversion for unified reporting

- Extended QuaestorInvestigator with probe types and history

- 34 new tests for red team module (all passing)

Closes #24

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove unused imports (datetime, UTC) in test_redteam.py
- Rename unused loop/function arguments to underscore prefix
- Add `from None` / `from e` to re-raised exceptions in cli.py
- Remove unused import in _display_redteam_report

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix unused arguments in test_optimizer.py callback functions
- Remove unused json import in test_patterns.py (auto-fixed by ruff)
- Add noqa comments for intentionally unused API arguments in optimizer.py
- Fix unused loop variable in patterns.py

These were pre-existing issues in main that blocked the PR CI.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add missing newline at end of probe_types.py
- Remove unused AsyncMock import in test_investigator_probing.py
- Fix import ordering

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace Optional[X] with X | None
- Replace Dict with dict

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Apply consistent formatting across all modified files.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…t coverage

- Wire up create_sarif_from_issues() to CLI lint --format sarif
- Add 10 new tests for redteam module coverage
- Coverage: config.py 96%, runner.py 85%, adapter.py 67%
- Overall: 87.76% -> 88.32%

Closes #35
Copilot AI review requested due to automatic review settings January 17, 2026 02:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements SARIF output for the lint command and adds comprehensive red team testing capabilities with improved test coverage for the redteam module.

Changes:

  • Wires up existing SARIF infrastructure to CLI lint command with --format sarif option
  • Implements complete red team module with DeepTeam integration including models, adapters, configuration, and runner
  • Adds 10 new tests for redteam module improving coverage from 77% to 96% for config.py and 75% to 85% for runner.py

Reviewed changes

Copilot reviewed 23 out of 30 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
uv.lock Adds deepteam 1.0.5 package dependency
pyproject.toml Enables redteam optional dependency group
quaestor/cli.py Implements SARIF output in lint command, adds redteam CLI command
quaestor/reporting/sarif.py Adds red team attack result SARIF conversion methods
quaestor/reporting/html.py Adds red team report HTML rendering
quaestor/redteam/*.py New module with models, adapter, config, and runner
quaestor/runtime/investigator.py Adds probe history tracking and session management
tests/test_sarif.py Adds 7 new red team SARIF tests
tests/test_redteam.py New comprehensive test file with 1072 lines
tests/test_investigator_probing.py New probe testing file

Comment on lines +31 to +34
# Ensure the LM is configured globally for DSPy
if not dspy.settings.lm:
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))

Copilot AI Jan 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The global DSPy LM configuration is initialized unconditionally at module import time. This could cause issues if the user has already configured DSPy, or if OpenAI API keys are not available. Consider lazy initialization or checking if dspy.settings.lm is already set before overwriting it.

Suggested change
# Ensure the LM is configured globally for DSPy
if not dspy.settings.lm:
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
def _configure_default_dspy_lm() -> None:
"""Safely configure a default DSPy LM if none is set.
This is intentionally defensive: it must not raise at import time
if DSPy settings are not initialized or if API credentials are missing.
"""
try:
current_lm = getattr(dspy.settings, "lm", None)
except Exception:
# If DSPy settings are not accessible yet, avoid failing import.
return
if current_lm:
# An LM is already configured; do not overwrite it.
return
try:
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
except Exception:
# Failure to configure a default LM should not prevent module import.
# Downstream code can configure DSPy explicitly if needed.
return
# Ensure the LM is configured globally for DSPy (best-effort, non-fatal)
_configure_default_dspy_lm()

Copilot uses AI. Check for mistakes.
Comment thread quaestor/runtime/investigator.py Outdated
Comment on lines +932 to +938
probe = self.dspy_module.predict(
conversation_history=context,
observations=json.dumps([]),
probe_type_preferences=json.dumps(self.probe_type_preferences),
current_strategy=self._strategy.value,
test_objective="Explore agent capabilities",
)

Copilot AI Jan 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The probe method is expected to return the generated probe text string, but run_session doesn't extract it properly from the dspy_module.predict result. The predict call likely returns a structured output, not a raw string. You should extract the probe field from the prediction object.

Suggested change
probe = self.dspy_module.predict(
conversation_history=context,
observations=json.dumps([]),
probe_type_preferences=json.dumps(self.probe_type_preferences),
current_strategy=self._strategy.value,
test_objective="Explore agent capabilities",
)
prediction = self.dspy_module.predict(
conversation_history=context,
observations=json.dumps([]),
probe_type_preferences=json.dumps(self.probe_type_preferences),
current_strategy=self._strategy.value,
test_objective="Explore agent capabilities",
)
# DSPy modules typically return a structured prediction object; extract the probe text.
if isinstance(prediction, dict):
probe = prediction.get("probe", prediction)
else:
probe = getattr(prediction, "probe", prediction)

Copilot uses AI. Check for mistakes.
Comment thread quaestor/runtime/investigator.py Outdated
Comment on lines +939 to +965
print(f"Turn {turn}: Generated probe: {probe}") # Debugging
try:
response = await self.adapter.send_probe(probe)
print(f"Turn {turn}: Received response: {response}") # Debugging
self._record_probe(
turn=turn,
probe=probe,
response=response,
probe_type=self._select_probe_type(),
status="success",
)
# Check termination criteria after recording the probe
if self.termination_criteria and self.termination_criteria(response):
print(f"Turn {turn}: Termination criteria met. Ending session.") # Debugging
break
except Exception as e:
print(f"Turn {turn}: Exception occurred: {e}") # Debugging
self._record_probe(
turn=turn,
probe=probe,
response="",
probe_type=self._select_probe_type(),
status=f"failure: {str(e)}",
)

# Update context for the next turn
context = f"{context} {response}"

Copilot AI Jan 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statements should be removed or converted to proper logging. These statements will clutter production output and should use the logging module instead.

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +17
print(f"MockAdapter: Turn {self.turn_count}, Received probe: {probe}") # Debugging
if self.turn_count == 2:
print("MockAdapter: Simulating failure on turn 2") # Debugging
raise Exception("Simulated failure on turn 2")
if "STOP" in probe:
print("MockAdapter: Returning STOP response") # Debugging

Copilot AI Jan 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statements should be removed or converted to proper logging before production. These print statements will appear in test output unnecessarily.

Suggested change
print(f"MockAdapter: Turn {self.turn_count}, Received probe: {probe}") # Debugging
if self.turn_count == 2:
print("MockAdapter: Simulating failure on turn 2") # Debugging
raise Exception("Simulated failure on turn 2")
if "STOP" in probe:
print("MockAdapter: Returning STOP response") # Debugging
if self.turn_count == 2:
raise Exception("Simulated failure on turn 2")
if "STOP" in probe:

Copilot uses AI. Check for mistakes.
Comment thread quaestor/cli.py
Comment on lines +729 to +730
import json

Copilot AI Jan 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import of module json is redundant, as it was previously imported on line 10.

Suggested change
import json

Copilot uses AI. Check for mistakes.
Comment thread quaestor/redteam/runner.py Outdated
Comment on lines +109 to +110
http_adapter = HTTPAdapter(
base_url=url,

Copilot AI Jan 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keyword argument 'base_url' is not a supported parameter name of HTTPAdapter.init.

Suggested change
http_adapter = HTTPAdapter(
base_url=url,
# Configure the base URL on the adapter config to avoid passing
# unsupported keyword arguments to HTTPAdapter.__init__.
adapter_config.base_url = url
http_adapter = HTTPAdapter(

Copilot uses AI. Check for mistakes.
Bazzinga Agent and others added 6 commits January 20, 2026 15:01
- Fix type annotations in optimizer.py for Callable signatures
- Fix HTTPAdapterConfig import and usage in redteam/runner.py
- Add proper type hints to investigator.py for ProbeHistory and run_session
- Fix MockAdapter in tests to implement TargetAdapter protocol (send_message)
- Add case-insensitive matching in _vuln_to_category for redteam adapter
- Handle DSPy scenarios with empty assertions in test_designer.py
- Update cli.py to use correct CoverageTracker and MockResponse APIs

Resolves 47 mypy type errors and test failures.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove unused `cast` import from adapter.py
- Remove unused `AdapterConfig` import from runner.py
- Prefix unused `history` param with underscore in adapter.py
- Prefix unused `extractor` variable with underscore in cli.py

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add use_dspy check in run_session() to use simple fallback probe
  generation when DSPy is disabled (avoids requiring API keys in CI)
- Update test to use renamed _min_score parameter

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@leonbreukelman
leonbreukelman merged commit 2074c0e into main Jan 20, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement SARIF output for lint command

2 participants