ASO Security: Fail-Closed Protection for AI Agents
ASO is a Python security orchestration library for AI agents, memory systems, tool execution, and secrets. It gives developers a structured, fail-closed boundary for protecting agent workloads before they reach privileged resources.
ASO is designed for projects where an agent can read/write memory, call tools, and access credentials. In those environments, a single prompt classifier is not enough. ASO adds an explicit decision layer that inspects intent, validates context, authorizes access, and quarantines suspicious activity before execution continues.
Source code: ASO on GitHub
Why ASO exists
Modern AI agents often need to:
- read and write memory
- call external tools
- retrieve credentials and tokens
- work with multi-step task flows
- operate in enterprise or production contexts
Without a hardened security boundary, these operations can be abused by prompt injection, unsafe tool invocation, model misalignment, or untrusted memory content.
ASO helps by enforcing a clear policy:
- inspect the request
- validate trusted host context
- authorize the requested resource
- execute only through guarded gateways
- deny or quarantine when the request is suspicious
Core idea
ASO separates concerns into different application layers:
- Prompt inspection and intent analysis
- Central orchestration and decision handling
- Memory protection
- Tool execution protection
- Secret and credential access control
This design keeps security decisions auditable and avoids trusting agent-controlled request data as proof of authorization.
Installation
pip install aso-security
Optional integrations:
pip install "aso-security[openai]"
pip install "aso-security[google]"
pip install "aso-security[huggingface]"
pip install "aso-security[langchain]"
Quick start
from MemoryGuard.config import SecurityContext, StoreConfig, MemoryRequest, Operation
from MemoryGuard.memory_access_layer import StoreRegistry
from DecisionEngine.aso_core import AgentSecurityOrchestrator
from ToolGuard.tool_guard import ToolGuard, ToolRegistry, ToolDefinition
from SecretGuard.config import SecretAccessRequest, SecretSecurityContext, SecretPermissions
from SecretGuard.secret_gateway import SecretBroker
# 1. Memory setup
registry = StoreRegistry()
registry.register(StoreConfig(
store_id="agent-profile",
store_type="sqlite",
provider="sqlite",
adapter=SQLITE(database_path="agent_memory.db"),
memory_types=frozenset({"profile"}),
capabilities=frozenset({"read", "write", "update", "delete"}),
))
memory_context = SecurityContext(
agent_id="agent-42",
tenant_id="tenant-7",
allowed_stores=frozenset({"agent-profile"}),
permissions=frozenset({"read", "write"}),
session_id="session-100",
)
# 2. Tool setup
def lookup_ticket(ticket_id: str) -> dict:
return {"ticket_id": ticket_id, "status": "open"}
tool_registry = ToolRegistry()
tool_registry.register(
ToolDefinition(
tool_id="lookup_ticket",
name="Lookup ticket",
description="Read a ticket status.",
purpose="Check the status of a ticket requested by the user.",
operations=frozenset({"lookup"}),
allowed_resources=frozenset({"T-100"}),
output_schema={"type": "object"},
),
lookup_ticket,
)
tool_context = ToolSecurityContext(
agent_id="agent-42",
session_id="session-100",
authorized_tools=frozenset({"lookup_ticket"}),
)
# 3. Secret setup
secret_repo = SecretRepository("secret_guard.db")
secret_vault = SecretVault(secret_repo, LocalDevelopmentKeyManager())
secret_broker = SecretBroker(secret_repo, secret_vault, permissions=SecretPermissions())
secret_context = SecretSecurityContext(
tenant_id="tenant-7",
requester="service-account",
agent_id="agent-42",
session_id="session-100",
authorized_tools=frozenset({"lookup_ticket"}),
)
# 4. Orchestrator
orchestrator = AgentSecurityOrchestrator(
framework=Framework.LANGCHAIN,
llm="security-model",
prompt_template="Follow the ASO security policy.",
M_Definition=memory_context,
T_Definition=None,
S_Definition=None,
memory_access_layer=GuardedMemoryAccessLayer(registry),
tool_guard=ToolGuard(registry=tool_registry, trusted_context=tool_context),
secret_broker=secret_broker,
tool_context=tool_context,
secret_context=secret_context,
agent_name="support-agent",
developer_name="Example Engineering",
agent_description="Handles support requests using guarded memory, tools, and secrets.",
)
# The orchestrator creates its framework agent during initialization.
agent = orchestrator.agent
# Create another agent explicitly when the application needs one.
another_agent = orchestrator.create_agent(
agent_name="billing-agent",
model_name="security-model",
developer_name="Example Engineering",
description="Handles billing questions.",
)
# 5. Memory request
memory_result = orchestrator.process(
MemoryRequest(
agent_id="agent-42",
operation=Operation.WRITE,
store_id="agent-profile",
payload={"preference": "concise answers"},
memory_type="profile",
metadata={"memory_id": "answer-style"},
),
context=memory_context,
)
print(memory_result.decision)
# 6. Tool request
result = orchestrator.process(
ToolRequest(
request_id="req-tool-1",
agent_id="agent-42",
session_id="session-100",
task_id="task-1",
tool_id="lookup_ticket",
purpose="Read the status of ticket T-100.",
reason="The user asked for the status of a valid ticket.",
arguments={"operation": "lookup", "ticket_id": "T-100"},
),
context=tool_context,
)
print(result.decision)
Main library entry points
AgentSecurityOrchestrator.process(...)
This is the primary unified entry point for the ASO runtime. It accepts a typed request and dispatches it to the appropriate guard based on the request type.
Supported request types:
MemoryRequestToolRequestSecretAccessRequest
The orchestrator validates prompt content and trusted host context before dispatching. If the request is suspicious, it returns a deny or quarantine result instead of continuing execution.
Creating the framework agent
AgentSecurityOrchestrator creates its initial framework agent during initialization and exposes it as orchestrator.agent. Set agent_name, developer_name, and agent_description to provide its metadata; if omitted, ASO generates a name and uses default metadata. The required llm argument supplies the model identifier used to create the framework agent.
Call orchestrator.create_agent(agent_name, model_name, developer_name, description) to create an additional agent explicitly. It returns the framework-specific agent and records its metadata. This method does not register tools; register tools separately through ToolGuard and provide trusted context for guarded calls.
GuardedMemoryAccessLayer.execute(...)
This is the memory-specific gateway. It ensures:
- the store is known and enabled
- the agent identity matches the trusted context
- the requested memory scope is permitted
- the operation is allowed by both the context and the store configuration
- post-read inspection checks retrieved memory
ToolGuard.execute(...)
This gate protects external tool execution. It verifies:
- the tool is registered
- the request is from a trusted agent/session/tool context
- the operation is declared in the tool definition
- the tool usage is in scope
- the tool result itself is safe before returning it to the agent
SecretBroker
This is the secret/credential gateway. It enforces:
- tenant and agent identity matching
- allowed secret type and destination checks
- purpose-based access policy
- read-only or bounded secret callbacks
- encrypted storage and metadata-only request handling
Trust boundaries matter
ASO deliberately requires trusted host-created context. This is not a library that trusts the agent to say, "I am authorized". Instead, the application creates and provides the authenticated security context.
Examples of host-controlled values:
- agent ID
- tenant ID
- session ID
- authorized tools
- allowed memory stores
- secret policy metadata
Agent-controlled text is never treated as authorization.
Guard behavior
ASO uses a fail-closed style of enforcement:
- if prompt inspection fails, the request is blocked
- if the trusted context is missing or mismatched, execution is denied
- if the tool result is suspicious, it is quarantined
- if a secret operation breaks policy, it is denied
A quarantine result means the app should notify a human operator or incident system, because the request or output was not accepted for normal execution.
Example: memory protection
from MemoryGuard.config import SecurityContext, MemoryRequest, Operation
from MemoryGuard.memory_gateway import GuardedMemoryAccessLayer
context = SecurityContext(
agent_id="agent-42",
tenant_id="tenant-7",
allowed_stores=frozenset({"agent-profile"}),
permissions=frozenset({"read", "write"}),
session_id="session-1",
)
request = MemoryRequest(
agent_id="agent-42",
operation=Operation.WRITE,
store_id="agent-profile",
payload={"preference": "be concise"},
memory_type="profile",
)
result = GuardedMemoryAccessLayer(registry).execute(request, context)
print(result.decision)
Example: tool protection
from ToolGuard.config import ToolRequest, ToolSecurityContext
from ToolGuard.tool_guard import ToolGuard, ToolRegistry, ToolDefinition
registry = ToolRegistry()
def get_status(ticket_id: str) -> dict:
return {"ticket_id": ticket_id, "status": "open"}
registry.register(
ToolDefinition(
tool_id="get_status",
name="Status lookup",
description="Fetch ticket status",
purpose="Check the user's requested ticket status.",
operations=frozenset({"lookup"}),
allowed_resources=frozenset({"T-100"}),
output_schema={"type": "object"},
),
get_status,
)
context = ToolSecurityContext(
agent_id="agent-42",
session_id="session-1",
authorized_tools=frozenset({"get_status"}),
)
request = ToolRequest(
request_id="tool-1",
agent_id="agent-42",
session_id="session-1",
task_id="task-1",
tool_id="get_status",
purpose="Check the status of the user's ticket.",
reason="This is required to answer the user request.",
arguments={"operation": "lookup", "ticket_id": "T-100"},
)
result = ToolGuard(registry=registry, trusted_context=context).execute(request, context)
print(result.decision)
Example: secret protection
from SecretGuard.config import SecretAccessRequest, SecretSecurityContext, SecretPermissions
from SecretGuard.secret_gateway import SecretBroker
context = SecretSecurityContext(
tenant_id="tenant-7",
requester="service-account",
agent_id="agent-42",
session_id="session-2",
authorized_tools=frozenset({"get_status"}),
)
request = SecretAccessRequest(
request_id="secret-1",
secret_id="secret-abc",
tenant_id="tenant-7",
requester="service-account",
agent_id="agent-42",
session_id="session-2",
tool_id="get_status",
purpose="Run the API call needed for the current user request.",
operation="read",
destination="external-api",
)
result = secret_broker.execute_with_secret(
request,
context,
lambda value: "token-ok",
)
print(result)
Production guidance
For production deployment, ASO should be used with:
- authenticated host-side identity
- KMS-backed secret management
- explicit tool registry policies
- explicit tenant/session scoping
- activity logs and audit review
- alerting for quarantined events
- tested security policy defaults
License
ASO is licensed under the Apache License 2.0.
Links
- GitHub: https://github.com/AyoubArdem/ASO
- Repository: https://github.com/AyoubArdem/ASO
- PyPI: https://pypi.org/project/aso-security/
Metadata
Release files for aso-security 0.1.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 | Provenance | |
|---|---|---|---|---|
| aso_security-0.1.0.tar.gz | 80.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aso_security-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 147.5 kB
Release files / aso_security-0.1.0.tar.gz
| Download URL | aso_security-0.1.0.tar.gz |
|---|---|
| Size | 80.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
05e457aa5b3e1a1b5b980f2b8c6b234e05a3d2541be72239021ddb2b240816a2
|
|
BLAKE2b-256 checksum How to use checksums |
a8b116a7158d3abe2f6e886eacedebb7d64d34ee5526401ffe7d86fa1bfc2804
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.5
|
Release files / aso_security-0.1.0-py3-none-any.whl
| Download URL | aso_security-0.1.0-py3-none-any.whl |
|---|---|
| Size | 67.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2b49b252bcb8234858222acbf198477ca1f488b2ff646b3744c0df0e5129b8ff
|
|
BLAKE2b-256 checksum How to use checksums |
fcd36fbc9d5d32bf8cbf76f262855d012f8b51eed84c47df888f6cca02f4df19
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.5
|
Security for aso-security 0.1.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.