Free for 1 AI agent · Python

Per-tool credential scoping for CrewAI

CrewAI agents have tools. Each tool needs different credentials. secr lets you scope each tool to only the keys it needs — so a misbehaving Slack tool can't decide to read your Stripe key, and a compromised agent can't leak every credential at once.

# Install
pip install secr-sdk

Why per-tool scope

CrewAI's default pattern is one big .env file the whole crew reads from. Every tool — Slack, GitHub, Stripe, internal API, email — sees every key. The agent token has them all in scope.

That works until a tool gets compromised. A bad LangChain wrapper, a poisoned HTTP response, a prompt injection that convinces the agent to call a tool with unintended arguments — and now the credential the tool sees isn't limited to what the tool needs.

The fix is small: don't hand the tool the bundle. Hand it only the keys it asks for, and refuse if any of them aren't on the agent's allowlist.

Scoped tool construction

from secr.langchain import SecrCredentials
from secr.crewai import scope_for_tool, SecrToolScope
from crewai import Agent, Task, Crew

creds = SecrCredentials(
    token=os.environ["SECR_AGENT_TOKEN"],
    org="acme",
    project="support",
    env="production",
)

# Option 1 — pass only the keys this tool needs.
# scope_for_tool raises if any key isn't on the agent's allowlist.
slack_kwargs = scope_for_tool(creds, "slack", "SLACK_BOT_TOKEN")
slack_tool = SlackTool(**slack_kwargs)

github_kwargs = scope_for_tool(creds, "github", "GITHUB_PAT")
github_tool = GitHubTool(**github_kwargs)

# slack_tool can't see GITHUB_PAT, github_tool can't see SLACK_BOT_TOKEN.

support_agent = Agent(
    role="Support triage",
    goal="Route inbound issues",
    tools=[slack_tool, github_tool],
)

Or scope via context manager

For tools that read os.environ instead of constructor args, use the context manager. Secrets land in the environment only for the duration of the tool call.

# Option 2 — context manager for tools that read os.environ.
# Secrets land in the environment only for the duration of the call,
# then revert to whatever was there before.

with SecrToolScope(creds, "SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"):
    result = slack_tool.run("Triage this thread")

# After the with block: SLACK_BOT_TOKEN is back to whatever it was
# (or unset, if it wasn't set before).

Defence in depth

Per-tool credential scoping limits the blast radius of a compromised tool. But it doesn't stop the agent from calling a tool you didn't want it to call in the first place. For that, see the MCP gateway approval queue — same principle, applied to tool invocations.

Together: scoped credentials means a compromised tool can't leak secrets it shouldn't have seen, and the gateway means the agent can't invoke a dangerous tool without a human approving the call.

Read next

Stop handing every tool every credential

Scope each tool to the keys it actually needs. Free for 1 AI agent — no card.