RelayShield Agentic Attack Surface: smolagents tools + MITRE ATT&CK dataset for agent security

We built four smolagents.Tool classes that let an agent check its own attack surface before acting: is the MCP server it’s about to connect to known-malicious, does incoming content match known prompt-injection patterns, does its own tech stack (including agent frameworks — LangChain, CrewAI, AutoGPT, etc.) carry a known CVE, and bulk identity-risk scoring for domains/emails.

This sits on top of a live threat-intel pipeline (3M+ IOCs, 4,500+ tracked malware families, 40+ monitored criminal marketplaces, 20+ authoritative feeds) rather than a static demo.

Feedback welcome, especially on which of the other ~20 endpoints (identity-risk, wallet/token/NFT security, supply-chain, etc.) would be worth wrapping as tools next.

— RelayShield

Hmm… based on what I could find, I think it looks roughly like this:


Direct answer: if I were choosing the next single endpoint to wrap, I would probably start with oauth-watchlist.

The practical reason is that this is already exposed by the public relayshield-mcp package, so there is an existing contract to reuse and compare against. The current public implementation takes an email address rather than a raw OAuth token, which also makes it a relatively low-friction next wrapper.

After that, I would group oauth-watchlist, nhi-exposure, and session-risk as one agent authority / credential exposure family rather than presenting them as three unrelated tools:

  • oauth-watchlist: risk inherited from connected SaaS applications and delegated access
  • nhi-exposure: API keys, service accounts, PATs, machine identities, and other non-human credentials
  • session-risk: active or reusable session material that may bypass normal authentication controls

That seems closer to the actual agent-security question: can someone else exercise the authority this agent currently holds?

My next choice after that would probably be supply-chain. I would not necessarily convert every remaining endpoint into an MCP or smolagents.Tool, though. Some appear more natural as scheduled checks, local hooks, gateways, or batch integrations.

Use case Default delivery surface I would consider
An agent or analyst wants enrichment during an investigation Advisory MCP / smolagents.Tool
A check must occur before sending, purchasing, deleting, connecting, or executing Host-side pre-action gate
The input may contain raw secrets, private source, or local credentials Local/user-run hook or scanner
The signal changes slowly or should be monitored continuously Scheduled job / webhook
Large IOC or fleet-wide processing Batch / SIEM integration

The main architectural distinction I would make explicit is:

A security tool that the model may call is not the same control as a security gate that the system must pass.

A smolagents.Tool makes a capability available to the agent. That is useful for investigation and self-checking, but it does not by itself guarantee that the model will invoke the check before a sensitive action. For the stronger claim—“check before acting”—I think a second reference workflow would be valuable: put the check in a host hook, gateway, or composite tool that controls access to the side-effecting operation. CrewAI’s before_tool_call hooks, for example, can inspect an action and return False to block it.

So I would suggest publishing two clearly named examples:

  1. Advisory investigation tool — the agent can request risk context and show the evidence to the user.
  2. Mandatory pre-action gate — the raw high-impact action is not directly available until the host has run the required check and made an allow/review/deny decision.

The other high-value improvement, especially for the second workflow, would be a small machine-readable result contract. Something along these lines would already make integration much easier:

{
  "outcome": "finding | no_known_finding | unknown | error",
  "recommended_action": "allow | review | deny | defer",
  "reason_codes": [],
  "evidence": [],
  "coverage": {
    "complete": false,
    "scope": "what was actually checked"
  },
  "freshness": {
    "observed_at": "timestamp or null",
    "expires_at": "timestamp or null"
  },
  "error": {
    "kind": "timeout | rate_limited | auth | upstream | malformed_response | other",
    "retryable": true
  }
}

I would deliberately use no_known_finding, not safe. A threat-intelligence lookup can report that it found nothing in the sources and scope queried; that is not generally the same statement as proving that the target is safe.

MCP already has the relevant protocol pieces: outputSchema and structuredContent, plus isError for tool-execution failures. smolagents also supports object output types, so this does not need to remain a human-formatted string-only interface.

I did a small contract-level sanity check using the current public wrappers and synthetic local upstream responses only—not the live detector and not a detection-accuracy test. In the public MCP package, synthetic upstream 401, 429, 500, HTTP 200 with {"ok": false}, malformed/empty responses, and a network disconnect were returned as ordinary tool content with isError: false; missing required arguments and an unknown tool name did produce isError: true. That matches the current public response-handling path in server.py.

I would not interpret that as anything about the threat-intel backend. It is just an integration-contract observation. But if these checks are ever used as policy gates, distinguishing clean result, unknown result, and failed check becomes important; otherwise a client can accidentally treat “the check did not complete” as “nothing was found.”

A fairly small next release could therefore be:

  1. Add the oauth-watchlist smolagents.Tool.
  2. Add one host-side mandatory-gate example around an existing high-impact action.
  3. Add a shared structured result/error schema.
  4. Publish redacted fixtures for positive, negative, stale, rate-limited, malformed, and upstream-error cases.
  5. Add a short data-flow note for each endpoint.

That would make the project easier to evaluate and integrate even for people who cannot inspect the proprietary backend or call the live API.

Why OAuth, NHI, and session risk seem like one useful agent-security bundle

Agents increasingly act through delegated and machine-held authority rather than through a human entering a password for every operation. That authority can exist in several forms:

  • OAuth access or refresh tokens
  • service-account credentials
  • API keys and personal access tokens
  • session cookies or other bearer session artifacts
  • credentials inherited from the host environment
  • connected third-party applications with delegated account access

This is why I think the three endpoints form a coherent family.

The OWASP Non-Human Identities Top 10 provides useful surrounding vocabulary here: secret leakage, overprivileged identities, long-lived secrets, insecure authentication, reuse, and lifecycle/offboarding problems. It is not specific to agents, but those risks map naturally to agents because agents often operate as machine identities or exercise machine credentials.

For OAuth, the current check_oauth_watchlist contract is especially convenient because it asks for an email identity and looks for connected-app exposure; it does not ask the tool caller to submit a raw access token. That distinction is worth keeping visible in the documentation.

The broader OAuth security context is also useful when deciding what a positive finding should cause. RFC 9700 emphasizes token replay prevention, refresh-token rotation or sender constraints, audience restriction, and least privilege. A watchlist hit may therefore suggest actions such as reviewing connected applications, revoking grants, rotating credentials, or reducing scopes—but the tool should probably return evidence and recommended actions rather than autonomously revoking access unless that separate authority has been explicitly granted.

For session-risk, I would keep “session evidence” separate from “authorization to act.” A restored process or agent state does not prove that its previous authorization remains valid. Session expiry, revocation, credential rotation, and already-executed external actions may all have changed since the state was captured.

A compact lifecycle mapping might be:

Lifecycle point Useful checks
Agent/service onboarding NHI exposure, supply-chain posture, least-privilege review
Before connecting to an external server/tool MCP registry risk, domain/reputation, supply-chain
Before a high-impact action session risk, OAuth/identity exposure, policy-specific checks
Periodic operation OAuth watchlist, NHI exposure, public-repository secret posture
Incident or suspicious behavior session risk, infostealer/breach correlation, credential rotation
Offboarding token revocation, session invalidation, NHI removal, audit reconciliation

This also provides a more scalable roadmap than ranking all 24 endpoints in one flat list.

Suggested decision tree

  • If the result is only advisory context: expose it as an MCP or framework tool.
  • If a protected action must not proceed without the check: enforce it outside the model with a hook, gateway, or composite tool.
  • If the check requires raw secrets or private code: prefer local processing and send only the minimum derived value needed.
  • If the signal is posture or monitoring data: prefer scheduled or event-driven delivery.
  • If the result is unknown, stale, partial, rate-limited, or malformed: route to review/defer rather than silently treating it as allow.
  • If the result is a finding: keep remediation as a separately authorized action unless the deployment has explicitly opted into automatic response.

Minimal reference workflows

Advisory investigation tool

  1. The agent or user requests a check.
  2. The adapter calls RelayShield.
  3. The adapter normalizes the response into finding, no_known_finding, unknown, or error.
  4. It returns evidence, coverage, freshness, and suggested next actions.
  5. It does not silently convert an unavailable check into a clean result.
  6. Any remediation is a separate, explicit operation.

Mandatory pre-action gate

  1. The model proposes a side-effecting action.
  2. The raw action is not directly executable by the model.
  3. A host hook/gateway extracts the least-sensitive identifiers needed for the security check.
  4. The gate receives a typed result.
  5. A fresh, sufficiently covered no_known_finding may allow the action.
  6. A finding follows the deployment policy: deny or require review.
  7. Timeout, rate limit, stale data, partial coverage, malformed response, or upstream failure follows an explicit degraded-mode policy—normally review/defer for high-impact actions.
  8. The audit record stores the decision and reason codes without unnecessarily storing secrets.

This is also where a small “degraded mode” can help. If the security service is temporarily unavailable, the agent might still be allowed to read local information or prepare a draft, while external sending, purchasing, deletion, or execution remains deferred.

Integration contract, credentials, documentation, and validation details

Credential placement and tool visibility

The public local relayshield-mcp package keeps the RelayShield API key or x402 proof in environment variables and sends them as headers. That is a good separation from ordinary tool arguments.

The hosted Agentic Attack Surface Space, however, currently includes api_key in the public function/tool inputs in app.py. Depending on the MCP client and observability setup, ordinary tool arguments may be included in model-visible context, traces, replay logs, or debug output.

A useful hosted reference configuration would therefore keep service credentials outside the model-generated argument schema—for example, server-side environment configuration, a gateway, or an authenticated request header. Gradio’s gr.Request can access request headers inside the prediction function, so the Space does not necessarily need to expose the API key as a semantic tool parameter.

This is mainly a deployment-boundary point, not a claim that a credential has leaked.

For future OAuth/session/NHI tools, the documentation could state whether the request contains:

  • an email or account identifier
  • a domain
  • provider/client/application identifiers
  • scopes or token metadata
  • a hash/fingerprint
  • a raw token, cookie, API key, or secret
  • repository names, paths, snippets, or contents

The current OAuth watchlist tool uses an email, not a raw OAuth token. I would preserve that distinction. If a future check can operate from issuer/client ID/scope, a prefix, or a one-way fingerprint, that may be preferable to transmitting bearer material.

If raw downstream tokens ever do cross an MCP boundary, the MCP security guidance on token passthrough is relevant: accepting a token intended for another service and forwarding it without proper audience validation is an anti-pattern. This is not necessarily what RelayShield currently does; it is simply a useful boundary to document before adding token-oriented checks.

Data-flow note that would help adoption

For a security API, the data-handling page is part of the integration contract. A short endpoint-specific table could cover:

Question Example field
What leaves the caller? email, domain, URL, repository metadata, token fingerprint
Is the value raw, truncated, hashed, or normalized? raw email, SHA-256 fingerprint, registrable domain
What is used only in memory? request payload
What may be retained? result, billing record, abuse-prevention record
What may enter logs or traces? identifiers, response codes, request IDs
How long is it retained? endpoint-specific retention
Can the caller request deletion? procedure and limits
Is the result cached? cache key and TTL
How is tenant isolation handled? account/project boundary

This is especially relevant because the security checks themselves may receive the exact identifiers and credentials that an adopter is trying to protect.

Clarifying prompt-injection-breach

There appears to be a terminology/documentation difference worth resolving gently.

The HF blog post describes prompt-injection-breach as checking whether incoming content matches known prompt-injection patterns. The public MCP/PR material describes an email-based check for credential or session exposure associated with prompt-injection attacks against agents; the CrewAI PR calls it “credential breach exposure sourced from prompt-injection attacks.”

Both are potentially useful, but they are different integration contracts:

  • Content scanner: input is untrusted text/document/tool output; result concerns the content being ingested.
  • Breach-source correlation: input is an identity such as an email; result concerns previously observed compromise evidence.

If both exist, I would expose them as two separately named tools. If only the second exists today, aligning the Forum/blog wording with the email-based input and evidence model would prevent downstream users from placing it at the wrong point in an agent pipeline.

x402 and API-key routes

The API-key and x402 paths should remain distinct in examples. The discussion in CrewAI PR #6550 correctly notes that omitting an API-key header does not by itself implement x402: the client also needs the appropriate payment negotiation/signing flow and must call the intended route.

That argues for separate examples or adapters:

  • API-key/metered client
  • x402-capable client
  • discovery response when neither is configured

This avoids making a wrapper appear keyless when it would simply receive an authentication failure.

Fixtures and controls

A redacted fixture pack would let outside users review the integration contract without requiring production access or sensitive data. I would include at least:

  1. confirmed/synthetic positive
  2. clean negative
  3. no known finding with explicit scope
  4. partial coverage
  5. stale result
  6. authentication failure
  7. timeout
  8. rate limit with retry information
  9. upstream 5xx
  10. malformed or empty upstream response
  11. asynchronous/pending result, where applicable

For each fixture, include the expected normalized outcome and action:

Fixture Expected normalized result Typical gate action
Positive with usable evidence finding deny or review
Fresh, sufficiently covered negative no_known_finding allow according to policy
Partial or stale unknown review/defer
Timeout / 429 / upstream failure error retry or review/defer
Malformed response error do not treat as clean

The most useful end-to-end controls are at the protected-action boundary:

  • Does a malicious/synthetic-positive fixture actually prevent the protected action?
  • Does a benign control still pass?
  • Does service failure avoid becoming a false allow?
  • Is automatic remediation impossible without separate authorization?
  • Are logs useful without recording unnecessary secrets?
  • Can the same policy be replayed against a saved fixture after a wrapper upgrade?

Versioning the input schema, output schema, evidence vocabulary, and policy recommendation separately would also make migrations less surprising.

ATT&CK dataset positioning

The MITRE ATT&CK group/technique dataset looks useful as a transparent taxonomy and join layer: group IDs, aliases, descriptions, technique IDs, software IDs, and source links are easy for others to inspect and reuse.

I would position it as:

  • public taxonomy and normalization
  • retrieval or enrichment material
  • an explainability layer connecting findings to known groups/techniques
  • test data for joins, schemas, and UI paths

I would not use the existence of the dataset itself as evidence of proprietary detector precision or recall; that would require a separate labeled evaluation with controls and a documented sampling method.

For future readers searching specifically for AI-system adversary techniques, MITRE ATLAS is a useful complementary vocabulary. ATT&CK remains relevant for the conventional infrastructure, identity, credential, and post-compromise parts of the chain; ATLAS can supplement it where the behavior is specifically about AI-enabled systems.

Overall, I think the strongest near-term route is:

Ship oauth-watchlist as the next low-friction wrapper, define NHI/OAuth/session as the next coherent bundle, and publish one advisory workflow plus one mandatory-gate workflow using a shared typed failure contract.

That gives people something immediately usable while also making the boundary between threat-intel enrichment and enforceable policy much clearer.

Nice work. Self-checking agents are going to become increasingly important as more systems start using MCP and external tools.

I’d prioritize supply-chain risk, exposed secrets/tokens, and dependency vulnerability checks next since those are common failure points in real deployments. Keeping the results explainable (why something is risky and what to do next) will make these tools much easier for agents and developers to trust.

Hey Mason. Thanks for your feedback on our smolagents post! We took your suggestion seriously and just shipped supply_chain and secret_scan as new tools (dependency vulns were already covered by tech_stack_cve. It turned out that one had a real bug where it was silently reporting “no CVEs found” so we fixed that one too.

Also rebuilt every tool’s output around your “explainable” point specifically. Instead of a formatted string, each one now returns outcome/recommended_action plus the actual evidence and reason codes behind it, so it’s not just a verdict, it’s now an explanation.

We wrote up the full release here if you want the details: https://huggingface.co/blog/relayshieldadmin/smolagents-agent-security-tools-v2 . Any additional feedback you want to share is most welcome.

Hey John6666, thanks very much for your great feedback!! We listened closely and just added the following enhancements:

  • Shipped oauth_watchlist, nhi_exposure, and session_risk grouped exactly as you framed it: one “agent authority” family, not three unrelated checks. Addedsupply_chain too.
  • Used your structured result contract almost verbatim: outcome/recommended_action/reason_codes/evidence/coverage/freshness/error, all 9 tools now. Kept no_known_finding instead of safe, for the reason you gave.
  • Your synthetic-failure test on relayshield-mcp was absolutely correct. We reproduced all 5 of your cases (401/429/500/200-with-ok:false/malformed) against the real SDK, confirmed isError was wrong on all of them, fixed it end to end. The fix is shipped in v0.2.7 on PyPI now.

We’ve not yet built the advisory-vs-mandatory-gate distinction. Will tackle that in a subsequent release. Full writeup: You Asked, We Shipped: 5 New Agent Security Tools, Structured Results, and Two Bugs We Found Along the Way . If you’d use a host-side gate pattern like you described along with intended use cases, that’s useful to know before we scope it.

Confirmed the fix, thanks! With that in mind, I think it looks something like this:


I reran the same contract-level probe against relayshield-mcp 0.2.7 using synthetic local upstream responses only.

The change reproduced correctly on my side:

  • 401, 429, and 500 returned isError: true
  • HTTP 200 with {"ok": false} returned isError: true
  • malformed JSON, an empty body, plain-text 200, and a network disconnect also returned isError: true
  • the installed PyPI package and the public GitHub server.py matched at the time of the check

So the failure-contract fix appears to work beyond the original five cases. This still says nothing about detection quality or the proprietary threat-intelligence backend; it only confirms the public integration behavior.

Yes, I would use a host-side gate. My first reference use case would be blocking connection to or installation of an unfamiliar MCP server until the registry-risk check completes.

The one intentionally different probe result was HTTP 402. The current server.py returns the payment requirements as ordinary tool content rather than isError: true, which makes sense if 402 is being used as an x402 negotiation/discovery response.

For a mandatory gate, though, I would treat it as:

payment_required is not necessarily an execution error, but it is also not a completed security check.

So the protected action should remain deferred until payment negotiation and a successful recheck have completed.

The first host-side gate I would build

My strongest candidate for the first reference implementation is:

A mandatory gate before connecting to or installing an unfamiliar MCP server or package.

This seems like the cleanest first example because:

  • the target URL or package identifier is available before the action
  • connection or installation is a clear host-controlled chokepoint
  • check_mcp_registry_risk maps directly to the protected action
  • the input does not need to contain a raw secret
  • failure can safely postpone only the external connection while local reading, analysis, and drafting remain available
  • positive, negative, timeout, malformed-response, and payment-required fixtures are straightforward to test

The important implementation detail is that the security check should be invoked by trusted host code, not left as an optional preliminary tool call selected by the model.

A minimal flow would be:

model proposes MCP connection or installation
                    |
                    v
          trusted host-side hook
                    |
                    v
     RelayShield registry-risk check
        (+ supply-chain if relevant)
                    |
                    v
        normalized policy decision
                    |
          +---------+---------+
          |         |         |
        allow     review    deny/defer
          |
          v
actual connection or installation

The raw connection/install capability should either:

  1. not be exposed directly to the model, or
  2. only be exposed through a composite tool or host hook that cannot execute it before the gate completes.

CrewAI’s tool-call hooks are one concrete place to demonstrate this. A before_tool_call hook can inspect the tool and its inputs and return False to block execution. The same boundary could also be implemented in an MCP client, gateway, IDE host, agent runtime, or application-specific composite tool.

Minimal policy

I would keep the policy small and explicit:

Normalized check state Default gate action
finding with relevant evidence Deny or require human review
Fresh, sufficiently covered no_known_finding Allow according to deployment policy
unknown Review
Partial coverage Review
Stale result Review or recheck
Authentication failure Defer
Timeout, 429, or upstream failure Defer or retry within a bounded policy
Malformed or empty response Defer
payment_required Complete payment negotiation, recheck, then decide
Missing or unrecognized outcome Defer

I would keep unknown as a first-class state even if it is initially produced by the host adapter rather than the API itself.

For example, these should not be collapsed into the same meaning:

No matching intelligence was found in a fresh, defined search scope.
The available sources covered only part of the target.
The result is older than the policy permits.
The request failed before a result was obtained.

Only the first is a candidate for no_known_finding. The others are variations of insufficient knowledge or failed execution.

That distinction matters more in a mandatory gate than in an advisory tool, because the downstream question is no longer merely “what should I tell the user?” It is “may this external action proceed?”

Minimal host pattern

Not production code, but the reference shape could be approximately:

PROTECTED_TOOLS = {
    "connect_mcp_server",
    "install_mcp_package",
}


def before_tool_call(context):
    if context.tool_name not in PROTECTED_TOOLS:
        return None  # allow unrelated tools

    target = {
        "server_url": context.tool_input.get("server_url"),
        "package_name": context.tool_input.get("package_name"),
    }

    # Called by trusted host code, not selected by the model as
    # an optional security step.
    result = trusted_relayshield_client.check_mcp_registry_risk(
        **target
    )

    decision = evaluate_gate_policy(result)

    record_gate_decision(
        target=target,
        decision=decision.name,
        reason_codes=decision.reason_codes,
        check_version=decision.check_version,
        observed_at=decision.observed_at,
    )

    if decision.name == "allow":
        return None

    return False  # block the protected tool

A few properties seem important:

  • The RelayShield check itself must be excluded from recursive gating.
  • The model should not be able to bypass the gate by choosing a lower-level raw connection tool.
  • A hook exception should not silently become allow.
  • Retry limits should be bounded.
  • The audit entry should record the decision, reason codes, check version, target identifier, and time without storing API keys, payment proofs, session material, or other unnecessary credentials.
  • Automatic remediation should remain a separately authorized operation.

The default degraded mode could be narrow rather than completely stopping the agent:

Capability during check failure Suggested behavior
Read local documentation Continue
Analyze already available data Continue
Prepare a draft configuration Continue
Connect to the new MCP server Defer
Install an unfamiliar package Defer
Send credentials to the target Defer
Execute a side-effecting remote action Defer

That makes failure safer without turning a temporary intelligence-service outage into a total loss of useful local work.

Small acceptance suite

For a first reference implementation, I think the protected-action tests are more useful than checking only the returned label.

Fixture Expected result
Synthetic known-risk server Connection/install does not occur
Benign control with fresh sufficient coverage Connection/install may occur
no_known_finding with partial coverage Human review or defer
Stale result Recheck or review
401 Protected action does not occur
429 Bounded retry or defer; no false allow
500 Protected action does not occur
Malformed or empty response Protected action does not occur
Network disconnect Protected action does not occur
402 payment_required Payment/recheck path; no protected action yet
Gate raises an internal exception Protected action does not occur
Model attempts to call a lower-level connector directly The same gate still applies

That test set would demonstrate the difference between an advisory security tool and an enforceable policy boundary without requiring claims about detector precision.

Why I would start with this gate rather than a universal gate

It has a clear protected object

The object being evaluated is the MCP server or package that the agent is about to trust. The action being controlled is the establishment of that trust relationship.

That is a much tighter relationship than running a broad security check before every arbitrary tool invocation.

It has a clear time boundary

The host has a natural point at which it can say:

The model has proposed a target, but no connection has been made yet.

This allows the check to operate before credentials, prompts, files, or other context are exposed to the external server.

It has a practical failure mode

When the check cannot complete, the host can postpone the new connection without necessarily preventing unrelated local work.

For some other gate candidates, the correct degraded behavior is harder to generalize. For example, a session-risk finding before an external send may depend on:

  • which human or non-human identity authorizes that send
  • which account or credential the tool will actually use
  • whether the evidence applies to the current session
  • how recent the evidence must be
  • whether reauthentication is available

Those are useful later examples, but they require a stronger identity-to-action binding.

It avoids an overly broad first implementation

I would not start with “run RelayShield before every tool call.”

That can create:

  • unnecessary latency and cost
  • checks unrelated to the proposed action
  • unclear behavior during service failure
  • pressure to simplify all failures into one allow/deny value
  • difficulty explaining what evidence protects which action
  • accidental recursion if a security tool itself is gated

A small list of explicitly protected tools is easier to test and reason about.

Possible second and third gate examples

Second candidate: OAuth or vendor-grant gate

The next useful pattern might be a gate before authorizing a new SaaS application, vendor integration, or delegated OAuth grant.

Possible checks:

  • oauth_watchlist
  • supply_chain
  • optionally nhi_exposure

The flow could be:

proposed application/vendor grant
                |
                v
extract subject + app/vendor + requested scopes
                |
                v
OAuth and supply-chain checks
                |
                v
allow / reduce scopes / review / deny / defer

This is valuable, but the reference contract would need to make several relationships explicit:

  • the subject identity being protected
  • the OAuth provider
  • the connected application or client identity
  • the requested scopes
  • the vendor/domain associated with the application
  • evidence freshness
  • whether the result concerns the user, application, vendor, or a combination

Without those fields, an identity-level watchlist result may be difficult to map to one specific grant decision.

Third candidate: high-impact identity/session gate

A later pattern could protect actions such as:

  • external email or messaging
  • public posting
  • purchasing
  • deletion
  • credential rotation
  • transfer of funds or assets
  • deployment or production changes

Possible checks:

  • session_risk
  • nhi_exposure
  • oauth_watchlist

This could be high value, but the host must know which identity or credential actually authorizes the proposed action.

For example:

proposed send_email action
        |
        v
resolve sending account and active credential
        |
        v
session/NHI/OAuth risk check
        |
        v
allow / require reauthentication / review / deny / defer

The difficult part is not invoking the endpoint. It is binding the security evidence to the credential and authority that will actually execute the action.

That makes it a good second-stage example after the MCP connection gate has established the general host-side pattern.

One further compatibility improvement, separate from the gate itself, would be to expose the new application-level structured results through MCP-native outputSchema and structuredContent.

The JSON structure is already useful. Adding the protocol-native schema would let clients validate it without parsing JSON from TextContent, while retaining serialized text for backwards compatibility as the MCP specification recommends.

So my default scope would be:

  1. Protect one concrete action: connecting to or installing an unfamiliar MCP server/package.
  2. Call RelayShield from trusted host code.
  3. Use a small fail-closed-for-the-protected-action policy.
  4. Preserve local non-side-effecting work as the degraded mode.
  5. Publish the positive, negative, stale, error, and payment-required fixtures.
  6. Add OAuth/vendor-grant gating as the next pattern once identity, app, scope, and vendor bindings are explicit.

That seems small enough to ship as a reference, but complete enough to demonstrate the distinction between an optional security tool and a mandatory security boundary.

Once again thank you!! I appreciate your thorough review, especially the unknown/no_known_finding/partial/stale distinction. That’s the part many advisory tools get away with collapsing, and you’re right that a mandatory gate can’t.

I want to share three concrete updates:

  • CrewAI PR status: The pull request has now been open more than five days without any official reply, and I also sent another nudge message Monday. Rather than wait for someone else’s review queue, I built the first reference gate against a different, unblocked host instead.
  • Built and tested: LangChain’s wrap_tool_call middleware. It provides pre-execution hooks with the same shape as your before_tool_call pseudocode. It inspects the tool call, decides, and only then invokes (or refuses to invoke) the handler. I implemented your policy table verbatim including finding/no_known_finding/unknown/partial/stale/auth_failure/upstream_failure/malformed/payment_required/missing) states, each mapped to allow/review/deny/defer, plus the properties you flagged as important:
    • A hook exception defaults to defer, never silently to allow
    • Bounded retry only on upstream_failure (429/timeout), everything else terminal after one attempt
    • Audit log carries decision, reason codes, check version, target, and timestamp with no keys and no payment proofs
    • The raw connect/install tool is architecturally never bound to the model. There’s no “model calls the lower-level connector directly” bypass to catch after the fact, because that tool doesn’t exist in the model’s toolset in the first place.

I also built your acceptance suite as real tests, asserting on the protected action (did the handler actually run) rather than the return label, per your point about what that distinction is actually for: 12 cases including known-risk server, benign/fresh, no_known_finding_with_partial_coverage, stale, 401, 429, 500, malformed, network disconnect, 402, an internal gate exception, and an unrelated tool passing through untouched. All 12 pass against the real langchain/langgraph packages (only the RelayShield HTTP call is mocked per fixture).

I’ve published this as a public GitHub repo at my account: nzdsf2-gif, repo name: relayshield-langchain-gate. Search those on GitHub directly. My account here is too new to post a link, so I’ll add the direct URL as soon as that restriction clears.

I also need to mention one honest gap, as I’m sure you’d catch it anyway: I checked whether smolagents itself has a pre-execution hook comparable to CrewAI’s or LangChain’s. It doesn’t. Subclassing is technically always possible (override execute_tool_call on MultiStepAgent), but that’s an override of an internal method, not a documented extension point, and it isn’t something a security-sensitive host should have to rely on. step_callbacks fire on ActionStep only after the tool has already run. As this whole conversation is happening in the smolagents/MCP context, it seemed worth raising with HF directly rather than leaving as a footnote — I opened a feature-request issue on the smolagents GitHub repo about this (issue number 2557), and I’ll link it directly once I’m able to.

On the payment_required handling, I agree treat it as “deferred, not denied, pending negotiation.” The reference implementation currently defers and stops there rather than completing an actual x402 negotiation + recheck loop inline. Wiring that recheck is TBD.

Second/third gate candidates from your message (OAuth/vendor-grant gating with explicit identity/app/scope/vendor bindings): Noted. Will be sequenced after this one as you proposed.

Hello. I recently created this account. I’m new here and this is my first post. I’m happy to answer any questions you may have about the content.