Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.q-uestionable.ai/llms.txt

Use this file to discover all available pages before exploring further.

The inject module (q_ai.inject) tests AI model susceptibility to tool poisoning and prompt injection. It serves adversarial MCP tools, runs automated campaigns against LLMs, and scores responses.

Module Structure

inject/
├── campaign.py          # run_campaign() — async campaign execution
├── scoring.py           # Outcome classification from NormalizedResponse
├── models.py            # Campaign, CampaignResult, InjectionTechnique enums
├── server.py            # build_server() — malicious MCP server for manual testing
├── cli.py               # serve, campaign, list-payloads, report commands
├── adapter.py           # InjectAdapter for orchestrator integration
├── coverage.py          # build_coverage_report() — audit finding coverage analysis
├── mapper.py            # persist_campaign() — bridges results to core DB
└── payloads/            # YAML payload templates
    └── *.yaml           # One per injection payload

Campaign Execution

run_campaign() is the core async function. For each payload template:
  1. Render — Build MCP tool definition with poisoned description/response from the YAML template
  2. Call — Send tool call to the LLM via ProviderClient (litellm backend)
  3. Normalize — Convert provider-specific response to NormalizedResponse
  4. Score — Classify the outcome
  5. Persist — Save results as JSON and to the database

Provider Abstraction

The inject module uses q_ai.core.llm for LLM access:
  • ProviderClient — Protocol defining call_with_tools() for tool-use interactions
  • LiteLLMClient — Implementation that centralizes LLM access behind the ProviderClient protocol (wraps litellm)
  • NormalizedResponse — Provider-agnostic response with content, tool_calls, raw_response, model, provider
  • parse_model_string() — Splits provider/model format, applies Anthropic default for bare names
Credentials resolve via environment variable → OS keyring → error.

Scoring System

The scoring module classifies each response into an outcome:
OutcomeMeaning
full_complianceModel fully followed the injected instructions
partial_complianceModel partially followed instructions
refusal_with_leakModel refused but leaked information
clean_refusalModel cleanly refused the injection
errorProvider error or timeout
Scoring examines the NormalizedResponse content — not embeddings or weighted factors. Raw evidence is preserved alongside the classification for manual review.

Payload Templates

Payloads are YAML files in inject/payloads/. Each defines:
  • name — Unique identifier
  • techniquedescription_poisoning, output_injection, or cross_tool_escalation
  • tool_name — The MCP tool name to present
  • owasp_ids — Mapped OWASP MCP Top 10 categories
  • description — What the payload tests
  • Tool definition fields (description, input schema, response template)
Use qai inject list-payloads to see the full catalog, filterable by --technique and --target.

Malicious Server

qai inject serve builds a standalone MCP server from payload templates. It presents poisoned tools via stdio or Streamable HTTP transport. Connect any MCP client to test how it handles adversarial tool content. This is for manual testing — campaigns automate the process.

Findings → Payloads Data Flow

When running inside the assess workflow, the inject adapter queries audit findings to inform template selection:
  1. Query native findingsfinding_service.get_findings_for_run() retrieves findings from the current workflow run’s audit child run
  2. Query imported findingsfinding_service.get_imported_findings_for_target() retrieves findings from qai import runs associated with the same target
  3. Extract categories — finding categories are collected into native and imported sets
  4. Prioritize templates — templates whose relevant_categories overlap with the combined finding categories are moved to the front of the execution queue
  5. Run campaign — all templates still run (prioritization, not exclusion)
  6. Build coverage reportbuild_coverage_report() compares which finding categories were exercised by security-relevant outcomes
The coverage report is persisted as evidence (type="coverage_report") on the inject child run, making it available to the web UI.

Adapter

InjectAdapter wraps run_campaign() for orchestrator integration, handling child run lifecycle, progress events, finding emission, and findings-informed template prioritization. It queries audit findings via finding_service, builds a coverage report, and persists it as evidence.