Skip to content

feat(add-new-block_code_execution-guardrail): prevent agent from executing code - #22154

Merged
1 commit merged into
mainfrom
krrishdholakia/add-block-code-exec
Feb 26, 2026
Merged

feat(add-new-block_code_execution-guardrail): prevent agent from executing code#22154
1 commit merged into
mainfrom
krrishdholakia/add-block-code-exec

Conversation

@ghost

@ghost ghost commented Feb 26, 2026

Copy link
Copy Markdown

Relevant issues

Closes #22056

Pre-Submission checklist

  • I have Added testing in the tests/litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Type

🆕 New Feature

Changes

Adds a new block_code_execution guardrail that detects markdown fenced code blocks in request/response content and blocks or masks them based on language, confidence threshold, and execution-intent heuristics.

Core Guardrail

  • Regex-based fenced code block detection with configurable blocked languages list
  • Confidence scoring with tunable threshold (only block when confidence >= threshold)
  • Execution-intent heuristics for request-side: blocks when user intends to execute, allows when intent is explain/refactor
  • Block or mask actions for detected code blocks
  • Support for pre_call, post_call, and during_call event hooks

Security Hardening

  • Response-side blocking skips intent heuristics entirely — LLM output doesn't contain user intent phrases like "run this", so checking would silently disable post_call blocking
  • No-execution short-circuit includes conflict resolution: when both no-execution and execution-intent phrases are present in the same prompt, execution intent wins (prevents bypass via "Don't run this on staging, but run this on production")
  • Tightened overly broad phrases (e.g. "what would " → "what would happen if", "can you explain" → "can you explain this code") to prevent trivial bypass
  • Removed overly generic execution phrases (" and run", "curl ", " tests pass") that caused false positives on educational prompts
  • _normalize_escaped_newlines only applies to pure-escaped payloads (no real newlines present) to avoid corrupting content discussing escape sequences

UI Integration

  • Guardrail garden cards and presets
  • Provider fields with multiselect for languages and percentage slider for confidence threshold
  • TypeScript added to blocked languages dropdown

Testing

  • 26 unit tests covering detection, blocking, masking, response-side behavior, phrase tightening, and conflict resolution
  • 100-entry compliance dataset with 100% pass rate

@vercel

vercel Bot commented Feb 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Feb 26, 2026 6:02am

Request Review

@greptile-apps

greptile-apps Bot commented Feb 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a new block_code_execution guardrail that detects markdown fenced code blocks in request/response content and blocks or masks them based on language, confidence threshold, and execution-intent heuristics. Closes #22056.

  • Core guardrail (block_code_execution.py): Regex-based fenced code block detection with configurable blocked languages, confidence scoring, execution-intent heuristics for requests, and response-side blocking that bypasses intent checks. Supports pre_call, post_call, and during_call hooks.
  • Security hardening: Response-side blocking skips intent heuristics (prevents silent bypass), conflict resolution when both no-execution and execution phrases appear (execution wins), tightened overly broad phrases.
  • Type system & endpoint integration: New BlockCodeExecutionGuardrailConfigModel with multiselect and percentage UI types; guardrail_endpoints.py enhanced to handle string-based ui_type values and copy min/max/step from json_schema_extra.
  • UI: Guardrail garden cards, presets, provider mapping, multiselect for languages, and a Slider for confidence threshold.
  • Testing: 26 unit tests + 100-entry compliance dataset with 100% pass rate. All tests are mock-based with no network calls.
  • Note: policy_templates_backup.json also includes an unrelated claims-agent-safety template addition.

Confidence Score: 4/5

  • This PR is safe to merge — the guardrail is additive, well-tested, and follows existing patterns. Issues found are stylistic (redundant phrases, minor regex edge case).
  • Score of 4 reflects: comprehensive test coverage (26 unit tests + 100-entry compliance dataset, all mock-based), proper integration with existing guardrail infrastructure (auto-discovery, config model, UI), and thoughtful security hardening (response-side bypass fix, conflict resolution). Minor deductions for: duplicate/redundant entries in phrase tuples (~30+ unnecessary iterations per scan), regex edge case with trailing spaces after language tags, and shared mutable state in compliance test.
  • litellm/proxy/guardrails/guardrail_hooks/block_code_execution/block_code_execution.py — phrase tuple duplicates/redundancy and regex edge case worth addressing before merge.

Important Files Changed

Filename Overview
litellm/proxy/guardrails/guardrail_hooks/block_code_execution/block_code_execution.py Core guardrail implementation (662 lines). Well-structured with confidence scoring, intent heuristics, response-side hardening. Has duplicate/redundant phrase entries and regex edge case with trailing spaces after language tags.
litellm/proxy/guardrails/guardrail_hooks/block_code_execution/init.py Guardrail initialization and registry integration. Follows the auto-discovery pattern from guardrail_hooks directory. Clean implementation with proper config extraction.
litellm/types/proxy/guardrails/guardrail_hooks/block_code_execution.py Type definitions for config model, detection output, and blocked languages options. Uses json_schema_extra for UI integration with multiselect and percentage slider.
litellm/types/guardrails.py Adds BLOCK_CODE_EXECUTION to SupportedGuardrailIntegrations enum, adds multiselect/percentage UI param types, and mixes in the new config model to LitellmParams.
litellm/proxy/guardrails/guardrail_endpoints.py Fixes ui_type handling for string-based values (was calling .value on plain strings), adds min/max/step copy from json_schema_extra, adds Literal options for select dropdowns. Solid improvements.
tests/test_litellm/proxy/guardrails/guardrail_hooks/test_block_code_execution.py 26 unit tests covering detection, blocking, masking, escaped newlines, response-side behavior, phrase tightening, and conflict resolution. All mock-based, no network calls.
tests/test_litellm/proxy/guardrails/guardrail_hooks/test_block_code_execution_compliance.py Compliance test running 100-entry dataset. Shared mutable request_data across iterations causes state accumulation but doesn't affect correctness. No network calls.
ui/litellm-dashboard/src/components/guardrails/guardrail_provider_fields.tsx Adds Slider component for percentage fields and extends ProviderParam interface. Hardcoded "0%"/"50%"/"100%" labels assume 0–1 range.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Incoming Text] --> B[_normalize_escaped_newlines]
    B --> C{input_type?}
    C -->|response| F[_find_blocks]
    C -->|request| D{detect_execution_intent?}
    D -->|no| F
    D -->|yes| E{_has_no_execution_intent AND NOT _has_execution_intent?}
    E -->|yes: pure explain| Z[Allow through]
    E -->|no| F
    F --> G{Blocks found?}
    G -->|no, request with exec intent| H[Block: execution_request]
    G -->|no, no intent| Z
    G -->|yes| I[For each block]
    I --> J{Language blocked AND confidence >= threshold?}
    J -->|no| K[Allow block through]
    J -->|yes, response| L[effective_block = true]
    J -->|yes, request| M{has_execution_intent OR no intent detection?}
    M -->|yes| L
    M -->|no| K
    L --> N{action?}
    N -->|block| O[Raise HTTPException / ModifyResponseException]
    N -->|mask| P[Replace with CODE_BLOCK_REDACTED]
Loading

Last reviewed commit: 91b3e76

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 91b3e76cfa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


# Regex: fenced code block with optional language tag. Handles ```lang\n...\n```
# Content between fences; does not handle nested ``` inside body (documented edge case).
FENCED_BLOCK_RE = re.compile(r"```(\w*)\n(.*?)```", re.DOTALL)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Broaden fenced-block regex to catch valid markdown code fences

The detector regex only matches fences of the form \w*\n...; this misses common fenced blocks such as language tags with punctuation (c++, objective-c) or CRLF line endings (\r\n). In block-all mode, those prompts are silently treated as having no code block, so executable snippets can bypass the guardrail by changing fence formatting rather than content.

Useful? React with 👍 / 👎.

for h in event_hook
]
else:
_event_hook = GuardrailEventHooks(event_hook)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve Mode objects when normalizing event_hook

This conversion path assumes every non-list event_hook can be cast to GuardrailEventHooks, but LitellmParams.mode also supports Mode objects for tag-based routing. Passing a Mode here raises at initialization time, so block_code_execution cannot be enabled in configs that use tag-based guardrail modes even though other guardrails support that mode type.

Useful? React with 👍 / 👎.

@greptile-apps greptile-apps 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.

14 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +65 to +96
_NO_EXECUTION_PHRASES: Tuple[str, ...] = (
"don't run",
"do not run",
"don't execute",
"do not execute",
"no execution",
"without running",
"without execute",
"just reason",
"explain without running",
"explain without execute",
"what would happen if",
"what would this output",
"what would the result be",
"? explain",
"simulate what would happen",
"don't actually run",
"diagnose the error from the text",
"don't run anything",
"without running them",
"no execution)",
"don't execute—just reason",
"no execution).",
"(no execution)",
"no db access",
"no db access).",
"don't execute it",
"don't run).",
"(no execution)",
"no builds/run",
"(don't run)",
"no execution).",

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.

Exact duplicate entries in phrase tuples

_NO_EXECUTION_PHRASES contains exact duplicate entries:

  • "(no execution)" appears at lines 88 and 93
  • "no execution)." appears at lines 87 and 96

Additionally, many entries are functionally redundant because substring matching is used (p in lower). For example, "don't run" (line 66) already matches any input that would match "don't actually run" (line 81), "don't run anything" (line 83), "(don't run)" (line 95), "but don't run" (line 97), "don't run it" (line 98), etc. Similarly, "no execution" (line 70) subsumes all the punctuation variants on lines 85-96.

The same pattern exists in _EXECUTION_REQUEST_PHRASES: "run this " (line 137) subsumes all of "run this python", "run this javascript", "run this bash", "run this code", etc. And "run `" (line 142) subsumes all "run `curl", "run `git", "run `docker" entries.

While redundancy doesn't cause incorrect behavior, it adds ~30+ unnecessary iterations per text scan and makes the phrase lists harder to maintain. Consider deduplicating.

Comment on lines +52 to +55
request_data = {}
passed = 0
failed = []
for item in compliance_dataset:

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.

Shared mutable request_data across iterations

request_data = {} is shared across all 100 compliance iterations. The apply_guardrail method's finally block calls add_standard_logging_guardrail_information_to_request_data, which appends logging entries to request_data["metadata"]["standard_logging_guardrail_information"]. This means the dict accumulates entries from every iteration, growing unbounded throughout the test.

This won't cause test failures, but it means later iterations carry state from earlier ones, which could mask bugs (e.g., if a detection list carried over). Consider resetting request_data inside the loop:

Suggested change
request_data = {}
passed = 0
failed = []
for item in compliance_dataset:
for item in compliance_dataset:
prompt = item["prompt"]
expected = item["expected_result"]
inputs = {"texts": [prompt]}
request_data = {}


# Regex: fenced code block with optional language tag. Handles ```lang\n...\n```
# Content between fences; does not handle nested ``` inside body (documented edge case).
FENCED_BLOCK_RE = re.compile(r"```(\w*)\n(.*?)```", re.DOTALL)

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.

Regex misses code fences with spaces after language tag

The regex r"```(\w*)\n(.*?)```" requires \n immediately after the (\w*) capture group. If a code block has trailing spaces between the language tag and the newline (e.g., ```python \n), the regex won't match because \w* stops at the space characters and then \n fails.

While uncommon, some editors/formatters do insert trailing spaces. A more robust pattern would be:

Suggested change
FENCED_BLOCK_RE = re.compile(r"```(\w*)\n(.*?)```", re.DOTALL)
FENCED_BLOCK_RE = re.compile(r"```(\w*)[ \t]*\n(.*?)```", re.DOTALL)

Comment on lines +199 to +208
<Slider
min={field.min}
max={field.max}
step={field.step ?? 0.1}
marks={{
[field.min]: "0%",
[(field.min + field.max) / 2]: "50%",
[field.max]: "100%",
}}
/>

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.

Hardcoded percentage labels assume 0–1 range

The Slider marks are hardcoded as "0%", "50%", and "100%" regardless of the actual min/max values. While the current block_code_execution guardrail uses 0.0–1.0 (making "0%"/"100%" technically correct), any future percentage field with a different range (e.g., 0.0–0.5) would show misleading labels. Consider deriving labels from the actual min/max values:

Suggested change
<Slider
min={field.min}
max={field.max}
step={field.step ?? 0.1}
marks={{
[field.min]: "0%",
[(field.min + field.max) / 2]: "50%",
[field.max]: "100%",
}}
/>
<Slider
min={field.min}
max={field.max}
step={field.step ?? 0.1}
marks={{
[field.min]: `${Math.round(field.min * 100)}%`,
[(field.min + field.max) / 2]: `${Math.round(((field.min + field.max) / 2) * 100)}%`,
[field.max]: `${Math.round(field.max * 100)}%`,
}}
/>

…uting code

Adds a new block_code_execution guardrail that detects markdown fenced code blocks
in request/response content and blocks or masks them by language. Includes full
UI integration, type definitions, compliance test dataset, and 26 unit tests.

Key guardrail capabilities:
- Regex-based fenced code block detection with configurable blocked languages
- Confidence scoring with tunable threshold
- Execution-intent heuristics (request-side only) with conflict resolution
- Block or mask actions for detected code
- Support for pre_call, post_call, and during_call event hooks

Security hardening:
- Response-side blocking skips intent heuristics (LLM output doesn't contain
  user intent phrases, so checking would silently disable post_call blocking)
- No-execution short-circuit includes conflict resolution: if both no-execution
  and execution phrases match, execution intent wins
- Tightened overly broad phrases to prevent trivial bypass
- _normalize_escaped_newlines only applies to pure-escaped payloads to avoid
  corrupting content that discusses escape sequences

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ghost
ghost force-pushed the krrishdholakia/add-block-code-exec branch from 91b3e76 to 3448640 Compare February 26, 2026 06:00
@ghost
ghost merged commit a9cb267 into main Feb 26, 2026
25 of 33 checks passed
Sameerlite pushed a commit that referenced this pull request Mar 3, 2026
…uting code (#22154)

Adds a new block_code_execution guardrail that detects markdown fenced code blocks
in request/response content and blocks or masks them by language. Includes full
UI integration, type definitions, compliance test dataset, and 26 unit tests.

Key guardrail capabilities:
- Regex-based fenced code block detection with configurable blocked languages
- Confidence scoring with tunable threshold
- Execution-intent heuristics (request-side only) with conflict resolution
- Block or mask actions for detected code
- Support for pre_call, post_call, and during_call event hooks

Security hardening:
- Response-side blocking skips intent heuristics (LLM output doesn't contain
  user intent phrases, so checking would silently disable post_call blocking)
- No-execution short-circuit includes conflict resolution: if both no-execution
  and execution phrases match, execution intent wins
- Tightened overly broad phrases to prevent trivial bypass
- _normalize_escaped_newlines only applies to pure-escaped payloads to avoid
  corrupting content that discusses escape sequences

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…uting code (BerriAI#22154)

Adds a new block_code_execution guardrail that detects markdown fenced code blocks
in request/response content and blocks or masks them by language. Includes full
UI integration, type definitions, compliance test dataset, and 26 unit tests.

Key guardrail capabilities:
- Regex-based fenced code block detection with configurable blocked languages
- Confidence scoring with tunable threshold
- Execution-intent heuristics (request-side only) with conflict resolution
- Block or mask actions for detected code
- Support for pre_call, post_call, and during_call event hooks

Security hardening:
- Response-side blocking skips intent heuristics (LLM output doesn't contain
  user intent phrases, so checking would silently disable post_call blocking)
- No-execution short-circuit includes conflict resolution: if both no-execution
  and execution phrases match, execution intent wins
- Tightened overly broad phrases to prevent trivial bypass
- _normalize_escaped_newlines only applies to pure-escaped payloads to avoid
  corrupting content that discusses escape sequences
This pull request was closed.
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.

0 participants