hallpass
Permission checks for AI agents and bots, answered live by the system they act in.
Before your agent acts for a user (delete a Jira issue, push to a repository, scale a deployment),
ask hallpass whether that user may do it. hallpass asks the system that owns the resource, live,
with its own read-only credential, and answers allow, deny or unknown. It only checks; it
never performs the action. Twenty-one systems are supported: Kubernetes, Argo CD, GitHub, GitLab,
Bitbucket, Jira, Confluence, Slack, Datadog, PagerDuty, AWS, Google Workspace, Google Cloud,
Microsoft 365, Databricks, Salesforce, Snowflake, Vault, Azure, Linear and Zendesk.
pip install hallpass
Python 3.10 or later. The core depends only on PyYAML. The engine runs in your process, so there is
no service to deploy; the same package also runs it as a server (hallpass serve).
In-process
From a hallpass YAML file (example, reference):
from hallpass import Hallpass
hp = Hallpass.from_config("hallpass.yaml")
d = hp.check("dana@example.com", "jira-main", "DELETE_ISSUES", "issue:PAY-123")
d.decision # "allow", "deny" or "unknown"
d.reason # "denied: Dana Levi does not hold DELETE_ISSUES on issue PAY-123"
d.allowed # True only for allow
hp.allowed(...) # True or False
hp.require(..., fresh=True) # the decision on allow; raises PermissionDenied otherwise
await hp.acheck(...) # check and require for async code
await hp.arequire(...)
Or with the connections in code, as mappings with exactly the keys of the file. A secret key takes
hallpass.env("NAME"), hallpass.file("/path"), hallpass.literal(value), or the file's
"env:NAME" and "file:/path" strings:
import hallpass
hp = hallpass.Hallpass(connections=[
{"id": "jira-main", "integration": "jira", "url": "https://acme.atlassian.net",
"username": "hallpass-bot@acme.com", "credential": hallpass.env("JIRA_TOKEN")},
])
check never raises for a failed lookup: a timeout, a rejected credential or anything else hallpass
cannot evaluate is unknown. Treat unknown exactly like deny.
Remote
To keep the lookup credentials out of the agent's process, run hallpass as a server (the
ghcr.io/roee-hersh/hallpass image, the Helm chart, or hallpass serve -config hallpass.yaml) and
ask it:
hp = Hallpass.remote("https://hallpass.internal", api_key) # or HALLPASS_URL and HALLPASS_API_KEY
The methods are the same. The URL must be https://, or http:// on localhost; redirects are not
followed; a server that cannot be reached is unknown.
guarded
Puts the check in front of one function, so its body runs only after hallpass said allow:
from contextvars import ContextVar
from hallpass import guarded
current_user: ContextVar[str] = ContextVar("current_user") # set from your login, per request
@tool # any framework's decorator
@guarded(hp, "jira-main", "DELETE_ISSUES", "issue:{key}", user=current_user, fresh=True)
def delete_issue(key: str) -> str:
jira.delete_issue(key) # the agent's own credential, only after allow
return f"deleted {key}"
The user comes from user= (a string, a zero-argument callable or a ContextVar), never from the
call's arguments, so the model cannot choose who it acts as. Anything but allow raises
PermissionDenied before the body runs; deny= returns a message instead.
Framework adapters
Each adapter is configured once on the agent with rules, a tool name to Rule(connection, action, resource) or a (connection, action, resource) tuple, and checks every call to a tool with a rule
before it runs. A refused call does not run; the model reads hallpass refused this call: <reason>
and the run goes on. The user always comes from the application, never from the model. A tool
without a rule runs unchecked, unless strict=True.
Strands Agents: an intervention handler; the user from invocation_state.
from hallpass.strands import HallpassAuthorization, Rule
hallpass = HallpassAuthorization(hp, {"delete_issue": Rule("jira-main", "DELETE_ISSUES", "issue:{key}", fresh=True)})
agent = Agent(tools=tools, interventions=[hallpass])
agent(prompt, invocation_state={"user_id": user.email})
LangChain and LangGraph: agent middleware; the user from the runtime context.
from hallpass.langchain import HallpassMiddleware, Rule
hallpass = HallpassMiddleware(hp, {"delete_issue": Rule("jira-main", "DELETE_ISSUES", "issue:{key}")})
agent = create_agent(model, tools=tools, middleware=[hallpass], context_schema=Context)
agent.invoke({"messages": [...]}, context=Context(user_id=user.email))
# a graph you build yourself: hallpass.tool_node(tools) is a checked ToolNode
MCP servers (the mcp SDK v2): server middleware; the user from the access token's email
claim, or user= for stdio.
from hallpass.mcp import Rule, guard
guard(mcp, hp, {"delete_issue": Rule("jira-main", "DELETE_ISSUES", "issue:{key}")})
OpenAI Agents SDK: tool guardrails; the user from the run context's user_id.
from hallpass.openai_agents import HallpassGuardrails, Rule
agent = HallpassGuardrails(hp, {"delete_issue": Rule("jira-main", "DELETE_ISSUES", "issue:{key}")}).apply(agent)
await Runner.run(agent, prompt, context=RequestContext(user_id=user.email))
Claude Agent SDK: a PreToolUse hook; the user from user=. Rules use Claude Code's tool names.
from hallpass.claude_agent_sdk import HallpassHooks, Rule
hallpass = HallpassHooks(hp, {"mcp__ops__delete_issue": Rule("jira-main", "DELETE_ISSUES", "issue:{key}")}, user=current_user)
options = hallpass.apply(ClaudeAgentOptions(mcp_servers={"ops": server}))
Google ADK: tool callbacks (or .plugin() for an App); the user is the session's user_id.
from hallpass.google_adk import HallpassCallbacks, Rule
agent = HallpassCallbacks(hp, {"delete_issue": Rule("jira-main", "DELETE_ISSUES", "issue:{key}")}).apply(agent)
CrewAI: process-wide tool-call hooks; the user from the crew's kickoff inputs.
from hallpass.crewai import HallpassHooks, Rule
with HallpassHooks(hp, {"delete_issue": Rule("jira-main", "DELETE_ISSUES", "issue:{key}")}):
crew.kickoff(inputs={"user_id": user.email})
Pydantic AI: a capability (or HallpassToolset around one toolset); the user from deps.user.
from hallpass.pydantic_ai import HallpassAuthorization, Rule
agent = Agent(model, deps_type=Deps, tools=tools,
capabilities=[HallpassAuthorization(hp, {"delete_issue": Rule("jira-main", "DELETE_ISSUES", "issue:{key}")})])
agent.run_sync(prompt, deps=Deps(user=user.email))
LlamaIndex: wrapped tools; the user from user=.
from hallpass.llamaindex import HallpassAuthorization, Rule
hallpass = HallpassAuthorization(hp, {"delete_issue": Rule("jira-main", "DELETE_ISSUES", "issue:{key}")}, user=current_user)
agent = FunctionAgent(tools=hallpass.wrap(tools), llm=llm)
Complete, runnable examples:
hallpass-py/examples. Each
adapter's docstring and the
agent guide cover
groups, strict, and what each framework does with a refusal.
Extras
| Extra | Adds |
|---|---|
hallpass[crypto] |
cryptography, for the integrations that sign with a private key: GitHub App, Google service accounts, Snowflake key pair, Salesforce JWT, Microsoft 365 certificates |
hallpass[strands] |
strands-agents, for hallpass.strands |
hallpass[langchain] |
langchain and langgraph, for hallpass.langchain |
hallpass[mcp] |
mcp, for hallpass.mcp |
hallpass[openai-agents] |
openai-agents, for hallpass.openai_agents |
hallpass[claude-agent-sdk] |
claude-agent-sdk, for hallpass.claude_agent_sdk |
hallpass[google-adk] |
google-adk, for hallpass.google_adk |
hallpass[pydantic-ai] |
pydantic-ai-slim, for hallpass.pydantic_ai |
hallpass[crewai] |
crewai, for hallpass.crewai |
hallpass[llamaindex] |
llama-index-core, for hallpass.llamaindex |
The command
The package installs hallpass:
hallpass validate -config hallpass.yaml # the file, references, certificates; no network
hallpass probe -config hallpass.yaml # each connection's credential, live
hallpass check -config hallpass.yaml -connection jira-main \
-user dana@example.com -action DELETE_ISSUES -resource issue:PAY-123
hallpass catalog jira # an integration's keys and actions
hallpass serve -config hallpass.yaml # POST /check and GET /healthz on :8080
More
- Documentation, including one page per integration with the credential to create.
- Architecture and trust boundaries.
hallpass-clienton PyPI, the old Python client, is replaced by this package and gets no new releases; installed versions keep working. To move, depend onhallpassand changefrom hallpass_client import Hallpass/Hallpass()tofrom hallpass import Hallpass/Hallpass.remote()(andhallpass_client.strandstohallpass.strands).
Apache-2.0.
Release files for hallpass 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hallpass-0.6.0.tar.gz | 772.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hallpass-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.2 MB
Release files / hallpass-0.6.0.tar.gz
| Download URL | hallpass-0.6.0.tar.gz |
|---|---|
| Size | 772.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e1df4f4ac2a906da99f445f5476df716c187b50b51e9883e15cb2c27aef062b9
|
|
BLAKE2b-256 checksum How to use checksums |
29968dadbb5379e42b292106a67f9d88cb4f6d64b8de8c59780ac4f0e12197d4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / hallpass-0.6.0-py3-none-any.whl
| Download URL | hallpass-0.6.0-py3-none-any.whl |
|---|---|
| Size | 441.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e89808ad0a70bf08a87fd8c051a8f8b3719ee7868b0c108521910ddc50bca2a3
|
|
BLAKE2b-256 checksum How to use checksums |
6102cdef2d5057b06cdf7997e5658b3ad6a7b7ab53775233cae7fded839b5933
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency log