Skip to content

Adding Cycraft XecGuard integration - #26011

Merged
krrish-berri-2 merged 5 commits into
BerriAI:litellm_oss_branchfrom
clyang:cycraft-xecguard-integration
Apr 25, 2026
Merged

Adding Cycraft XecGuard integration#26011
krrish-berri-2 merged 5 commits into
BerriAI:litellm_oss_branchfrom
clyang:cycraft-xecguard-integration

Conversation

@clyang

@clyang clyang commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Add XecGuard Guardrail Integration

Pre-Submission Checklist

  • ✅ Added testing in tests/test_litellm/ directory (119 unit tests)
  • ✅ PR passes make test-unit
  • ✅ 100% line + branch coverage on the new xecguard module
  • ✅ Scope isolated to one specific problem
  • ✅ Black formatting applied

Type

🆕 New Feature
📖 Documentation
✅ Test

Changes Summary

Overview

Integrates CyCraft XecGuard as a first-class partner guardrail in LiteLLM's proxy. Provides multi-policy prompt/response scanning (prompt injection, harmful content, PII, system-prompt enforcement, bias, skills protection) and RAG context-grounding validation via the dedicated /grounding endpoint.

Architecture

App → LiteLLM Proxy → XecGuard /xecguard/v1/scan     → decision: SAFE / UNSAFE
                    → XecGuard /xecguard/v1/grounding (post_call, when RAG documents are supplied)
                    → LLM Provider (if allowed)

Backend Components

  • XECGUARD enum entry in SupportedGuardrailIntegrations
  • XecGuardGuardrail class implementing CustomGuardrail with apply_guardrail via POST to /xecguard/v1/scan using Authorization: Bearer <xgs_*>
  • All four event hooks supported: pre_call, during_call, post_call, and logging_only
    • apply_guardrail handles pre/during/post via the framework's unified dispatch
    • async_logging_hook + sync logging_hook override for observe-only scans in logging_only mode — never blocks, swallows all errors, emits a guardrail_information entry onto standard_logging_object for downstream loggers (Langfuse, DataDog, OTEL)
  • Context grounding via /xecguard/v1/grounding — automatically invoked on post_call when metadata.xecguard_grounding_documents: [{document_id, context}, ...] is present in the request; detects CONFLICT / BASELESS / INCOMPLETE violations; configurable grounding_strictness (BALANCED / STRICT)
  • Configurable api_key, api_base, xecguard_model, policy_names (list), block_on_error (fail-closed by default), grounding_strictness
  • Pydantic config model with api_key, api_base, xecguard_model, policy_names, block_on_error, grounding_strictness, ui_friendly_name()

Frontend Components

  • Partner card in Guardrail Garden with description, tags (Security, Policy, Grounding, RAG), and logo
  • Logo integration in guardrailLogoMap
  • Provider mapping entry in guardrail_provider_map
  • Policy multi-select dropdown: policy_names surfaces as a multi-select picker exposing all six default XecGuard policies (SystemPromptEnforcement, GeneralPromptAttackProtection, ContentBiasProtection, HarmfulContentProtection, SkillsProtection, PIISensitiveDataProtection) instead of a free-form list input

Documentation

  • Full documentation page at docs/my-website/docs/proxy/guardrails/xecguard.md with quick start, all four event-hook modes explained, the six available policies table, context-grounding usage with request-time metadata, input+output pipeline example, fail-open and logging-only examples, and the full-history guarantee
  • Sidebar navigation entry under proxy/guardrails/

Testing

  • 119 unit tests covering configuration (env vars, defaults, missing credentials), safe/unsafe scan decisions, grounding trigger logic (metadata location, malformed docs, strictness forwarding, request-side skipped), multimodal message assembly, full-history forwarding, HTTP/connection error handling in both fail-closed and fail-open modes, block-message formatting (policy joining, rationale truncation, grounding rules), logging-only observe-mode (success / blocked / error paths), sync-loop wrapper for logging hooks, config model fields, and registry wiring. All use mocked HTTP responses.
  • 100% line + branch coverage on the XecGuard module (pytest --cov=litellm.proxy.guardrails.guardrail_hooks.xecguard --cov=litellm.types.proxy.guardrails.guardrail_hooks.xecguard --cov-branch).

Files Modified/Created

File Type
litellm/types/guardrails.py Modified — enum entry + LitellmParams mixin
litellm/types/proxy/guardrails/guardrail_hooks/xecguard.py New — config model with multiselect policy options
litellm/proxy/guardrails/guardrail_hooks/xecguard/xecguard.py New — guardrail hook (all four modes)
litellm/proxy/guardrails/guardrail_hooks/xecguard/__init__.py New — registries (initializer + class)
tests/test_litellm/proxy/guardrails/guardrail_hooks/test_xecguard.py New — 119 unit tests
ui/litellm-dashboard/public/assets/logos/xecguard.svg New — logo
ui/litellm-dashboard/src/components/guardrails/guardrail_garden_data.ts Modified — partner card
ui/litellm-dashboard/src/components/guardrails/guardrail_garden_configs.ts Modified — preset
ui/litellm-dashboard/src/components/guardrails/guardrail_info_helpers.tsx Modified — provider map, logo map helper
docs/my-website/docs/proxy/guardrails/xecguard.md New — documentation
docs/my-website/sidebars.js Modified — sidebar entry

Test Results

  • Black formatting: ✅ passes
  • 119/119 unit tests: ✅ pass
  • 100% line + branch coverage on xecguard module: ✅
  • No regressions in existing guardrail test suites: ✅

Usage Example

config.yaml:

guardrails:
  - guardrail_name: "xecguard-guard"
    litellm_params:
      guardrail: xecguard
      mode: "pre_call"
      api_key: os.environ/XECGUARD_API_KEY
      policy_names:
        - Default_Policy_SystemPromptEnforcement
        - Default_Policy_HarmfulContentProtection

Per-request (for RAG grounding):

curl -i http://0.0.0.0:4000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4",
    "messages": [
      {"role": "user", "content": "What nationality was Peggy Seeger?"}
    ],
    "guardrails": ["xecguard-guard"],
    "metadata": {
      "xecguard_grounding_documents": [
        {"document_id": "peggy_seeger_bio",
         "context": "Peggy Seeger (born June 17, 1935) is an American folk singer."}
      ]
    }
  }'

@veria-ai veria-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Low: Raw third-party API response exposed to end users

This PR adds a well-structured XecGuard guardrail integration. The only security observation is that the full raw XecGuard API response is included in the HTTPException detail returned to end users, which could expose internal scan metadata beyond what's in the formatted error message.

detail={
"error": self._format_scan_block_message(scan_result),
"guardrail_name": self.guardrail_name or "xecguard",
"xecguard_response": scan_result,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Low: Raw API response forwarded to end user

The entire scan_result dict from the XecGuard API is included in the HTTPException detail, which FastAPI serializes back to the client. While the formatted error field already contains a curated subset (policy names, trace_id, truncated rationale), the raw response may include additional fields the XecGuard API adds in the future — internal metadata, scoring details, etc. Consider dropping the xecguard_response key from the user-facing error, or at minimum allowlisting only specific fields (decision, trace_id).

@codecov

codecov Bot commented Apr 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.64286% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...xy/guardrails/guardrail_hooks/xecguard/xecguard.py 99.60% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR integrates CyCraft XecGuard as a first-class partner guardrail in LiteLLM, supporting all four event hooks (pre_call, during_call, post_call, logging_only) plus RAG context-grounding validation. The implementation follows existing guardrail patterns correctly — registry wiring, config model mixin, CustomGuardrail subclassing, and UI partner card — and ships with 119 unit tests at 100% branch coverage.

Two minor P2 items were found:

  • The policy_names field description in the config model says defaults are 2 policies, but _DEFAULT_POLICIES actually contains 3 (including GeneralPromptAttackProtection). The description should be updated to match.
  • The early-return guard in async_logging_hook (lines 211–218) checks a key path (litellm_params.metadata.standard_logging_guardrail_information) that is never populated by XecGuard or the framework; it is dead code since the framework already restricts async_logging_hook dispatch to logging_only mode for CustomGuardrail instances.

Confidence Score: 5/5

Safe to merge — only P2 documentation/dead-code findings remain

All blocking concerns from prior review rounds have been resolved. The two remaining findings are both P2: a field description that understates the actual default policy count, and a dead-code guard block that is harmless but confusing. Neither affects runtime correctness or security.

litellm/types/proxy/guardrails/guardrail_hooks/xecguard.py (description mismatch) and litellm/proxy/guardrails/guardrail_hooks/xecguard/xecguard.py (dead-code guard)

Important Files Changed

Filename Overview
litellm/proxy/guardrails/guardrail_hooks/xecguard/xecguard.py Core guardrail hook: implements all four event modes, grounding validation, and message normalization; contains a harmless but unreachable dead-code guard in async_logging_hook (wrong key path)
litellm/types/proxy/guardrails/guardrail_hooks/xecguard.py Pydantic config model with multiselect policy UI metadata; policy_names description understates the actual 3-policy default
litellm/types/guardrails.py Adds XECGUARD enum entry and XecGuardConfigModel to LitellmParams mixin chain; correctly follows existing integration pattern
litellm/proxy/guardrails/guardrail_hooks/xecguard/init.py Wires XecGuardGuardrail into the initializer and class registries; consistent with other integration patterns
tests/test_litellm/proxy/guardrails/guardrail_hooks/test_xecguard.py 119 unit tests with 100% branch coverage; all HTTP calls are mocked; covers all four event hooks and grounding scenarios

Sequence Diagram

sequenceDiagram
    participant Client
    participant LiteLLM as LiteLLM Proxy
    participant XG as XecGuard /scan
    participant GR as XecGuard /grounding
    participant LLM as LLM Provider

    Client->>LiteLLM: POST /v1/chat/completions

    alt pre_call / during_call
        LiteLLM->>XG: POST /xecguard/v1/scan (scan_type=input)
        XG-->>LiteLLM: decision SAFE or UNSAFE
        alt UNSAFE
            LiteLLM-->>Client: 400 Blocked by XecGuard
        end
    end

    LiteLLM->>LLM: Forward request
    LLM-->>LiteLLM: Response

    alt post_call
        LiteLLM->>XG: POST /xecguard/v1/scan (scan_type=response)
        XG-->>LiteLLM: decision SAFE or UNSAFE
        alt UNSAFE
            LiteLLM-->>Client: 400 Blocked by XecGuard
        end
        opt metadata.xecguard_grounding_documents present
            LiteLLM->>GR: POST /xecguard/v1/grounding
            GR-->>LiteLLM: decision SAFE or UNSAFE
            alt UNSAFE
                LiteLLM-->>Client: 400 Blocked by XecGuard grounding
            end
        end
    end

    alt logging_only
        LiteLLM->>XG: POST /xecguard/v1/scan (suppress_errors=True)
        XG-->>LiteLLM: scan result (never blocks)
        Note over LiteLLM: guardrail_information to standard_logging_object
    end

    LiteLLM-->>Client: 200 OK
Loading

Reviews (3): Last reviewed commit: "Make sure every mode is logged correctly" | Re-trigger Greptile

Comment thread tests/test_litellm/proxy/guardrails/guardrail_hooks/test_xecguard_live.py Outdated
Comment thread litellm/proxy/guardrails/guardrail_hooks/xecguard/xecguard.py Outdated
Comment on lines +231 to +242
return kwargs, result

scan_result = await self._call_scan(
messages=messages,
scan_type=scan_type,
suppress_errors=True,
)
if scan_result is None:
return kwargs, result

guardrail_status: GuardrailStatus = (
"guardrail_intervened"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 logging_hook silently skips the scan when an event loop is already running

When loop.is_running() is True (which is almost always the case inside an async framework like Starlette/FastAPI), the sync logging_hook immediately returns without scanning and without emitting any log message. This means logging_only mode is silently a no-op for virtually every request that comes through the proxy. If this silent-skip behaviour is intentional (to avoid blocking), a verbose_proxy_logger.debug message would make it observable and prevent confusion during debugging.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This particular branch isn't actually a silent no-op for proxy requests. LiteLLM dispatches async callsites (which is all proxy traffic) to async_logging_hook, not to this sync logging_hook. Our async_logging_hook override runs the full scan and attaches guardrail_information to the standard logging payload, so logging_only mode works correctly end-to-end for proxy requests.

The sync logging_hook is a defensive fallback for sync callsites (e.g. litellm.completion() invoked outside any event loop). When it's reached from within a running loop, calling loop.run_until_complete() would raise RuntimeError: This event loop is already running. So the early-return is intentional to avoid a crash, not an observability gap.

@clyang

clyang commented Apr 18, 2026

Copy link
Copy Markdown
Contributor Author

Two out of the three Greptile review comments from the latest round have been addressed. Regarding the remaining P2 review, please refer to our explanation provided directly below that comment.

@ishaan-jaff , we would greatly appreciate it if you could review this PR when you have a chance.

Thank you.

@krrish-berri-2

Copy link
Copy Markdown
Contributor

@clyang — could you add a screenshot or short video showing that this change works as expected? It really helps reviewers verify the integration quickly. Thanks!

@clyang

clyang commented Apr 20, 2026

Copy link
Copy Markdown
Contributor Author

@clyang — could you add a screenshot or short video showing that this change works as expected? It really helps reviewers verify the integration quickly. Thanks!

Hi @krrish-berri-2 ,
As requested, I have recorded a short demo video (no audio): https://youtu.be/aijvteJGGt8

The video demonstrates the following:

  1. Creating four separate guardrails for pre_call, post_call, during_call, and logging_only modes.
  2. Sending both benign and harmful requests to these guardrails.
  3. Verifying the logs via the LiteLLM Web UI, confirming that all guardrail modes are operating as designed.

I look forward to your review and am happy to address any comments you may have.

@krrish-berri-2
krrish-berri-2 changed the base branch from litellm_internal_staging to litellm_oss_branch April 25, 2026 15:16
@krrish-berri-2
krrish-berri-2 merged commit 692638b into BerriAI:litellm_oss_branch Apr 25, 2026
41 of 42 checks passed
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
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.

2 participants