Skip to content
OpenSmartRoute

osr-security-hardening

.claude/skills/osr-security-hardening/SKILL.md

Harden OpenSmartRoute deployments against prompt injection, gadget/steering attacks, PII leakage, runaway agents and untrusted tool arguments - InputGuard and GuardMiddleware, learned gadget detector, PII redaction, ResourceLimits for tools and agents, OriginPolicy for tool-parameter provenance, signed MCP manifests, secret loading and the safety-routing red-team suite. Use when reviewing security of a routing setup, handling PII or private data boundaries, or wiring MCP tools safely.

Package
.claude/skills/osr-security-hardening
Compatibility
OpenSmartRoute >= 0.4, Python >= 3.10
License
Apache-2.0
Domains
coding legal general
Quality prior
0.85
Tags
opensmartroute security injection pii mcp

Install by copying .claude/skills/osr-security-hardening/ into the skills folder of your coding assistant. To load every package as a routing target: osr --skills .claude/skills route "..." --plan.

Threat model: the router sits between untrusted text (users, retrieved documents, tool output) and expensive or state-changing targets. Defend the input, the catalogue, and the execution budget.

1. Input guard (every deployment)#

from opensmartroute.security import InputGuard, GuardMiddleware
guard = InputGuard(max_chars=32_000, max_tokens_est=8_000, gadget_threshold=0.6,
                   reject_on_gadget=False, learned=False, strip_steering=True)
app = RouterBuilder(reg).with_defaults().with_middleware(GuardMiddleware(guard, redact=True)).build()
  • guard.inspect(text) -> GuardReport(ok, reasons, gadget_suspected, clean_text, score, steering_removed).
  • GuardMiddleware raises SecurityError when not ok; with redact=True PII is replaced before routing (original in request.context["_original_text"], map in context["_pii_map"]).
  • learned=True uses GadgetDetector.default(); train your own with osr train --gadget gadget.json and GadgetDetector.load.
  • Pure functions for ad-hoc checks: injection_risk(text), inspect_injection(text), strip_steering(text), sanitize_for_prompt(text), shannon_entropy(text).

2. Data boundaries and PII in the catalogue#

  • Targets declare constraints.data_boundary: public|private|on_prem and pii_allowed. Requests set RequestConstraints(data_boundary=..., contains_pii=...); the PIISignal also detects PII.
  • Policy rejects mismatches (DataBoundaryRule, pii rule) - verify in trace.policy_rejections.
  • Pair with a rule: when: {contains_pii: true} prefer: [llm-onprem] pin: true.
  • Tenant isolation: constraints.tenants on targets + TenantMiddleware(require=True).

3. Budgets for tools, agents and workflows#

from opensmartroute.security import ResourceLimits, ResourceLimiter, ResourceLimitMiddleware, apply_limits
limiter = ResourceLimiter(ResourceLimits(max_steps=50, max_tool_calls=100, max_depth=4,
                                         max_total_tokens=500_000, max_cost_usd=5.0, max_wall_s=900.0))
apply_limits(registry, limiter, kinds=("tool", "agent", "workflow"))   # wraps handlers per task_id
builder.with_middleware(ResourceLimitMiddleware(limiter))

Exceeding a limit raises ResourceLimitExceeded (a SecurityError). Budgets key on request.context["task_id"].

4. Tool-parameter provenance (indirect injection)#

from opensmartroute.security import OriginPolicy, OriginRule, mark_untrusted, apply_origin_policy
mark_untrusted(request, retrieved_text, source="retrieved")         # tag text you did not author
policy = OriginPolicy(rules=[OriginRule(state_changing=["delete_*", "send_*"], allowed_origins=["user"])])
apply_origin_policy(registry, policy)                                # wraps tool handlers

A state-changing tool whose arguments were copied from untrusted text raises OriginViolation.

5. MCP catalogues#

  • tools_from_mcp(payload, max_description_risk=0.6, guard=InputGuard()) drops tools whose descriptions look like steering ("ignore previous instructions ...").
  • Sign catalogues offline and verify at load: osr mcp-manifest tools.json --key-env OSR_MCP_KEY --server name --out manifest.json; then tools_from_manifest(manifest, key, require_signature=True, max_age_s=86400). --algorithm ed25519 needs the crypto extra.

6. Secrets#

load_secret("OPENAI_API_KEY") reads the env var or the file named by OPENAI_API_KEY_FILE (Kubernetes secret mounts). Never put secrets in targets.yaml; handlers read them at call time. Telemetry never logs raw text.

7. Red-team suite#

osr -t targets.yaml safety [--learned-guard] [--json] or run_safety_suite(app.route) runs SafetyCases (jailbreaks, PII exfiltration, boundary hops, cost bombs) and reports pass_rate and by_category. Add it next to osr eval --min-accuracy in CI.

Review checklist#

  • GuardMiddleware first in the chain, redact=True when any target is pii_allowed: false.
  • Every private/on-prem target has data_boundary and regions set; requests carry tenant.
  • apply_limits on all tool/agent/workflow targets; max_cost_usd matches the tenant budget.
  • MCP tools loaded from a signed manifest; max_description_risk left at or below 0.6.
  • Secrets via load_secret; audit sink enabled; osr safety passes in CI.