Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci-rust-python-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ jobs:
release-validation:
if: github.event_name == 'pull_request'
needs: validate-and-detect
permissions:
contents: read
pull-requests: read
id-token: write
uses: ./.github/workflows/release-rust-python-package.yaml
with:
tag: rate-limiter-v0.0.3
Expand Down
17 changes: 14 additions & 3 deletions .github/workflows/release-rust-python-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
wheel_matrix: ${{ steps.resolve.outputs.wheel_matrix }}
publish_env: ${{ steps.resolve.outputs.publish_env }}
checkout_ref: ${{ steps.resolve.outputs.checkout_ref }}
tag_on_main: ${{ steps.resolve.outputs.tag_on_main }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5

Expand All @@ -63,22 +64,31 @@ jobs:
REPOSITORY_INPUT: ${{ inputs.repository }}
run: |
set -euo pipefail
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" || "${GITHUB_EVENT_NAME}" == "workflow_call" ]]; then
git fetch --force origin "refs/heads/main:refs/remotes/origin/main"
if [[ -n "${TAG_INPUT}" ]]; then
tag="${TAG_INPUT}"
repository="${REPOSITORY_INPUT}"
git fetch --force origin "refs/tags/${tag}:refs/tags/${tag}"
git show-ref --verify --quiet "refs/tags/${tag}"
checkout_ref="refs/tags/${tag}"
tag_ref="refs/tags/${tag}"
else
tag="${GITHUB_REF_NAME}"
repository="pypi"
checkout_ref="${GITHUB_REF}"
tag_ref="${GITHUB_REF}"
fi

if git merge-base --is-ancestor "${tag_ref}" "refs/remotes/origin/main"; then
tag_on_main=true
else
tag_on_main=false
fi

release_info="$(python3 tools/plugin_catalog.py release-info . "${tag}")"
plugin="$(printf '%s' "${release_info}" | python3 -c 'import json, sys; print(json.load(sys.stdin)["slug"])')"
plugin_path="$(printf '%s' "${release_info}" | python3 -c 'import json, sys; print(json.load(sys.stdin)["path"])')"
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" || "${GITHUB_EVENT_NAME}" == "workflow_call" ]]; then
if [[ -n "${TAG_INPUT}" ]]; then
wheel_matrix="$(python3 -c 'import json; print(json.dumps([{"runner":"ubuntu-latest","platform":"linux-x86_64"},{"runner":"ubuntu-24.04-arm","platform":"linux-aarch64"},{"runner":"ubuntu-24.04-s390x","platform":"linux-s390x"},{"runner":"ubuntu-24.04-ppc64le","platform":"linux-ppc64le"},{"runner":"macos-latest","platform":"macos-arm64"},{"runner":"windows-latest","platform":"windows-x86_64"}]))')"
else
wheel_matrix="$(printf '%s' "${release_info}" | python3 -c 'import json, sys; print(json.dumps(json.load(sys.stdin)["release_wheel_matrix"]))')"
Expand All @@ -89,6 +99,7 @@ jobs:
echo "plugin_path=${plugin_path}"
echo "wheel_matrix=${wheel_matrix}"
echo "checkout_ref=${checkout_ref}"
echo "tag_on_main=${tag_on_main}"
if [[ "${repository}" == "testpypi" ]]; then
echo "publish_env=testpypi"
else
Expand Down Expand Up @@ -240,7 +251,7 @@ jobs:
"${tmpdir}/tests" -v

publish:
if: ${{ github.event_name != 'workflow_call' || inputs.publish_enabled }}
if: ${{ (github.event_name != 'workflow_call' || inputs.publish_enabled) && (needs.resolve.outputs.publish_env != 'pypi' || needs.resolve.outputs.tag_on_main == 'true') }}
needs: [resolve, build-wheel, build-sdist]
runs-on: ubuntu-latest
environment: ${{ needs.resolve.outputs.publish_env }}
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
description = "Rust-backed encoded exfiltration detection plugin for MCP Gateway"
description = "High-performance encoded exfiltration detection for MCP Gateway"

[lib]
name = "encoded_exfil_detection_rust"
Expand Down
97 changes: 95 additions & 2 deletions plugins/rust/python-package/encoded_exfil_detection/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,96 @@
# cpex-encoded-exfil-detection
# Encoded Exfiltration Detection (Rust)

Rust-backed encoded exfiltration detection plugin for MCP Gateway / CPEX.
High-performance encoded exfiltration detection for ContextForge and MCP Gateway.

## Features

- Detects suspicious encoded payloads in prompt args, tool outputs, and resource content
- Scans common exfil encodings:
- base64
- base64url
- hex
- percent-encoding
- escaped hex
- Scores candidates using decoded length, entropy, printable ratio, sensitive keywords, and egress hints
- Optional redaction instead of hard blocking
- Recursive scanning of nested dicts, lists, and JSON-like string payloads
- Allowlist regex support for known-safe encoded strings
- Decode-depth and recursion-depth guardrails

## Build

```bash
make install
```

## Usage

The plugin scans these hooks:

- `prompt_pre_fetch`
- `tool_post_invoke`
- `resource_post_fetch`

Typical uses:

- block suspicious encoded payloads before they leave the gateway
- redact encoded secrets or staged exfil fragments from tool results
- surface findings metadata for review and tuning

## Detection Model

Each candidate encoded segment is decoded and scored. The detector looks for combinations of:

- sufficient decoded length
- suspicious entropy
- printable decoded content
- sensitive markers such as `password`, `secret`, `token`, `authorization`, or `private key`
- egress hints such as `curl`, `wget`, `webhook`, `upload`, `socket`, or `pastebin`

The plugin can also inspect JSON strings recursively so encoded content nested inside serialized blobs is still visible to the detector.

## Configuration

Important settings include:

- `enabled`: per-encoding enable flags
- `min_encoded_length`
- `min_decoded_length`
- `min_entropy`
- `min_printable_ratio`
- `min_suspicion_score`
- `max_scan_string_length`
- `max_findings_per_value`
- `redact`
- `redaction_text`
- `block_on_detection`
- `min_findings_to_block`
- `allowlist_patterns`
- `extra_sensitive_keywords`
- `extra_egress_hints`
- `max_decode_depth`
- `max_recursion_depth`
- `parse_json_strings`

## Returned Metadata

When detections occur, the plugin can emit:

- `encoded_exfil_count`
- `encoded_exfil_findings`
- `encoded_exfil_redacted`
- `implementation`

Blocking responses use the `ENCODED_EXFIL_DETECTED` violation code.

## Security Notes

- Guardrails reject invalid allowlist regexes at configuration time.
- Scan and recursion caps exist to keep detection bounded on large payloads.
- Detailed findings can be reduced or sanitized before metadata emission depending on configuration.

## Testing

```bash
make ci
```
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# This file is automatically generated by pyo3_stub_gen
# ruff: noqa: E501, F401, F403, F405

from .encoded_exfil_detection import EncodedExfiltrationDetectionPlugin
from .encoded_exfil_detection import EncodedExfilDetectorConfig, EncodedExfilDetectorPlugin
from .encoded_exfil_detection_rust import py_scan_container

__all__ = [
"EncodedExfiltrationDetectionPlugin",
"EncodedExfilDetectorConfig",
"EncodedExfilDetectorPlugin",
"py_scan_container",
]
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
description: "Detect suspicious encoded payload exfiltration patterns in prompts, tool outputs, and resources"
author: "ContextForge Contributors"
version: "0.2.0"
kind: "cpex_encoded_exfil_detection.encoded_exfil_detection.EncodedExfilDetectorPlugin"
available_hooks:
- "prompt_pre_fetch"
- "tool_post_invoke"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ build-backend = "maturin"
[project]
name = "cpex-encoded-exfil-detection"
dynamic = ["version"]
description = "Rust-backed encoded exfiltration detection plugin for MCP Gateway"
description = "High-performance encoded exfiltration detection for MCP Gateway"
authors = [{ name = "ContextForge Contributors" }]
license = { text = "Apache-2.0" }
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"pydantic>=2,<3",
]
classifiers = [
"Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython",
Expand All @@ -18,6 +21,9 @@ classifiers = [
"Programming Language :: Python :: 3.13",
]

[project.entry-points."cpex.plugins"]
encoded_exfil_detection = "cpex_encoded_exfil_detection.encoded_exfil_detection:EncodedExfilDetectorPlugin"

[tool.maturin]
module-name = "cpex_encoded_exfil_detection.encoded_exfil_detection_rust"
python-source = "."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@ use encoded_exfil_detection_rust::stub_info;

fn curate_top_level_stub() {
let stub_path = Path::new("cpex_encoded_exfil_detection/__init__.pyi");
let content = "# This file is automatically generated by pyo3_stub_gen\n# ruff: noqa: E501, F401, F403, F405\n\nfrom .encoded_exfil_detection import EncodedExfiltrationDetectionPlugin\nfrom .encoded_exfil_detection_rust import py_scan_container\n\n__all__ = [\n \"EncodedExfiltrationDetectionPlugin\",\n \"py_scan_container\",\n]\n";
let content = "# This file is automatically generated by pyo3_stub_gen\n# ruff: noqa: E501, F401, F403, F405\n\nfrom .encoded_exfil_detection import EncodedExfilDetectorConfig, EncodedExfilDetectorPlugin\nfrom .encoded_exfil_detection_rust import py_scan_container\n\n__all__ = [\n \"EncodedExfilDetectorConfig\",\n \"EncodedExfilDetectorPlugin\",\n \"py_scan_container\",\n]\n";
fs::write(stub_path, content).expect("Failed to write curated top-level stub file");
}

fn curate_extension_stub() {
let stub_path =
Path::new("cpex_encoded_exfil_detection/encoded_exfil_detection_rust/__init__.pyi");
let content = fs::read_to_string(stub_path).expect("Failed to read generated stub file");
let content = content.trim_end().to_string() + "\n";
fs::write(stub_path, content).expect("Failed to write curated extension stub file");
}

fn main() {
let stub_info = stub_info().expect("Failed to get stub info");
stub_info.generate().expect("Failed to generate stub file");
curate_top_level_stub();
curate_extension_stub();
println!("✓ Generated stub files successfully");
}
4 changes: 4 additions & 0 deletions plugins/rust/python-package/encoded_exfil_detection/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions plugins/rust/python-package/retry_with_backoff/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[package]
name = "retry_with_backoff"
version = "0.1.0"
version = "0.1.1"
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
description = "Rust-backed retry and backoff policy plugin for MCP Gateway"
description = "High-performance retry policy engine for MCP Gateway with exponential backoff, jitter, per-tool overrides, and retry metadata"

[lib]
name = "retry_with_backoff_rust"
Expand Down
78 changes: 76 additions & 2 deletions plugins/rust/python-package/retry_with_backoff/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,77 @@
# Retry With Backoff
# Retry With Backoff (Rust)

Rust-backed retry and backoff policy plugin for MCP Gateway.
High-performance retry and backoff policy engine for ContextForge and MCP Gateway.

## Features

- Rust-backed retry state tracking for tool invocations
- Exponential backoff with optional jitter
- Per-tool policy overrides without duplicating whole plugin configs
- Retry decisions based on `isError`, structured `status_code`, or optional parsed text payloads
- Automatic state eviction for stale request entries
- Gateway ceiling enforcement for `max_retries`
- Retry policy metadata returned on tool and resource hooks

## Build

```bash
make install
```

## Usage

The plugin runs on `tool_post_invoke` and `resource_post_fetch`.

Typical uses:

- Retry transient upstream failures such as `429`, `500`, `502`, `503`, and `504`
- Clamp aggressive plugin settings to the gateway-wide retry ceiling
- Apply stricter retry budgets to fragile or expensive tools

## Configuration

### Core settings

- `max_retries`: maximum retry attempts before giving up
- `backoff_base_ms`: base delay for exponential backoff
- `max_backoff_ms`: upper bound for computed retry delays
- `retry_on_status`: HTTP or structured status codes treated as retriable
- `jitter`: randomize delay within the current exponential ceiling
- `check_text_content`: inspect text content for JSON-encoded error payloads when structured content is absent

### Per-tool overrides

Use `tool_overrides` to change retry behavior for a specific tool:

- `max_retries`
- `backoff_base_ms`
- `max_backoff_ms`
- `retry_on_status`
- `jitter`

## Behavior Notes

- Successful responses clear retry state for the `(tool, request_id)` pair.
- Retry state expires after a short TTL so abandoned request state does not accumulate indefinitely.
- If `check_text_content` is disabled, the hot path uses the Rust state manager directly.
- If `check_text_content` is enabled, the plugin falls back to Python-side payload inspection before applying retry policy.

## Returned Metadata

Both tool and resource hooks emit retry policy metadata so downstream systems can observe the active policy:

- `max_retries`
- `backoff_base_ms`
- `max_backoff_ms`
- `retry_on_status`

## Testing

```bash
# Full plugin CI
make ci
```

## Performance

The retry state manager is implemented in Rust so the common retry decision path avoids Python bookkeeping overhead for normal structured tool results.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
description: "Rust-backed retry and backoff policy plugin for tool results"
description: "High-performance retry policy engine with exponential backoff, jitter, per-tool overrides, and retry metadata for transient tool and resource failures"
author: "ContextForge Contributors"
version: "0.1.0"
version: "0.1.1"
kind: "cpex_retry_with_backoff.retry_with_backoff.RetryWithBackoffPlugin"
available_hooks:
- "tool_post_invoke"
- "resource_post_fetch"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "maturin"
[project]
name = "cpex-retry-with-backoff"
dynamic = ["version"]
description = "Rust-backed retry and backoff policy plugin for MCP Gateway"
description = "High-performance retry and backoff policy engine for MCP Gateway"
authors = [{ name = "ContextForge Contributors" }]
license = { text = "Apache-2.0" }
readme = "README.md"
Expand All @@ -21,6 +21,9 @@ classifiers = [
"Programming Language :: Python :: 3.13",
]

[project.entry-points."cpex.plugins"]
retry_with_backoff = "cpex_retry_with_backoff.retry_with_backoff:RetryWithBackoffPlugin"

[tool.maturin]
module-name = "cpex_retry_with_backoff.retry_with_backoff_rust"
python-source = "."
Expand Down
Loading
Loading