uAgents Framework Runtime Analysis Report
Completed July 10, 2026 · 18 artifacts examined
8Findings
1Critical
2High
5Medium
RAISE maturity 2.00 / 5.0
Executive Summary
Agent Remit (as declared)
uAgents is Fetch.ai's Python framework for building autonomous agents that each hold a cryptographic identity and a blockchain wallet, register their address and endpoints on the Almanac smart contract, and exchange typed messages with other agents over authenticated channels. The framework's own security remit is to hand every agent built on it a trustworthy foundation: protected key material, cryptographically verified and replay-resistant inter-agent messaging, schema-validated inputs, and administrative surfaces reachable only by the local operator. Private keys must never be written to disk in plaintext, unverified senders must never be trusted for security decisions, and no remote party may reach the agent's introspection or control endpoints. This analysis judges the framework runtime's default behavior, not any single deployed agent.
Behavior Summary (as observed)
The dominant pattern is safe cryptographic primitives whose guarantees are undercut by defaults and missing enforcement. Envelope signature verification and pydantic schema validation are real and on by default — `on_message` handlers reject unverified agent senders (`allow_unverified=False`) — but the "cryptographically secured" promise leaks at three seams: inbound signed envelopes are never checked against their own `expires`/`nonce` fields, so any captured envelope can be replayed; the loopback-only gate on the admin/inspector endpoints is defeated by `forwarded_allow_ips="*"`, which lets a caller assert `X-Forwarded-For: 127.0.0.1`; and name-based agents write their wallet and identity private keys to `private_keys.json` in plaintext in the working directory. The single most consequential item is that plaintext key storage, since those keys control on-chain assets and directly contradict the framework's headline security claim.
Scope of Analysis
A pure-Python framework split across the runtime package (`uagents/`) and a shared core (`uagents-core/`), using pydantic models, an ECDSA/secp256k1 identity (`uagents_core/identity.py`), and a bech32 address scheme. Inbound messages arrive over a uvicorn ASGI server in `asgi.py` that binds `HOST = "0.0.0.0"`, verifies envelope signatures for agent senders, and dispatches schema-validated payloads through `dispatch.py` to decorator-registered handlers in `agent.py`. Key material for name-based agents is persisted in plaintext to `private_keys.json` in the working directory via `storage/__init__.py`; agent state lives in a plaintext `KeyValueStore`. The `Agent` constructor enables the agent inspector by default (`enable_agent_inspector=True`), exposing reserved REST endpoints (`/messages`, `/connect`, `/disconnect`, `/agent_info`) whose loopback restriction relies on `scope["client"]` while the server is configured with `forwarded_allow_ips="*"`.
Remit Coverage

Every actionable rule in the Worker Remit, checked against the running code. Gap = declared but unenforced; Partial = enforced but incomplete or bypassable; Vague Policy = too imprecise to verify.

Verified: 6 Gap: 2 Partial: 4 Vague Policy: 0 Enforcement Not Possible: 1 Total Rules: 13
Rule ID Section Rule (quoted) Status Finding
R-01 Job Description "Receive inbound messages over an HTTP endpoint (and/or Agentverse mailbox/proxy), authenticate them, validate their payloads against declared message schemas, and dispatch them to the matching typed handler." Verified
R-02 Job Description "Create and manage a per-agent cryptographic identity (signing keypair) and a Fetch.ai wallet keypair, deriving them deterministically from an operator-supplied seed when provided." Partial PRAX-2026-07-10-007
R-03 Data Boundaries "Private keys, wallet keys, or seeds MUST NOT be written to disk in plaintext or emitted to logs." Gap PRAX-2026-07-10-001
R-04 Action Boundaries "Accepting and re-processing a previously delivered (replayed) or expired signed envelope as if it were fresh." Partial PRAX-2026-07-10-003
R-05 Action Boundaries "Dispatching an inbound message from a non-user agent sender whose envelope signature has not been verified." Verified
R-06 Non-Goals (Out of Scope) "Acting on the claimed identity of a message sender without cryptographic proof of that identity." Partial PRAX-2026-07-10-006
R-07 Non-Goals (Out of Scope) "Exposing administrative, introspection, or message-history interfaces to unauthenticated remote callers." Partial PRAX-2026-07-10-002
R-08 Action Boundaries "Binding the agent's HTTP server to a non-loopback (public) network interface." Gap PRAX-2026-07-10-004
R-09 Non-Goals (Out of Scope) "Executing operating-system shell commands or arbitrary code on behalf of a remote message sender." Verified
R-10 Job Description "Register the agent's address, endpoints, and protocol manifest on the Almanac contract and keep the registration current." Verified
R-11 Behavioral Expectations "Maximum retries before escalation: bounded; registration and delivery retries must be finite." Verified
R-12 Data Boundaries "Sensitive configuration secrets MUST NOT be embedded as literals in framework or agent source." Verified
R-13 Escalation Rules "An inbound signed envelope fails signature verification." Enforcement Not Possible
Findings Register

Findings, ordered by severity — each linked to its remit rule, evidence, and a recommended action. Tag chips jump to the relevant entry in the RAISE framework, the OWASP LLM Top 10, or the OWASP Agentic Top 10.

LLM06 primary — the finding's main OWASP categoryASI10 secondary — a category it also touches

CRITICAL PRAX-2026-07-10-001 Name-based agents persist identity and wallet private keys to plaintext private_keys.json in the working directory by default
Policy Rule — R-03 (Worker Remit):
"Private keys, wallet keys, or seeds MUST NOT be written to disk in plaintext or emitted to logs."
storage/__init__.py:113 — save_private_keys() writes {"identity_key": ..., "wallet_key": ...} via json.dump to private_keys.json with no encryption; path is os.getcwd()/private_keys.json (line 125) README.md:39 — "Otherwise the agent's private key will be stored locally alongside its name in private_keys.json" — documents plaintext key persistence as the default no-seed behavior
Recommended Action
  • Store identity and wallet keys through an encrypted-at-rest mechanism (OS keychain, an encrypted keystore, or a secret manager) instead of a plaintext file, and at minimum create private_keys.json with 0600 permissions in storage/__init__.py save_private_keys().
  • Make the seed-based path (Identity.from_seed, no key file) the documented default in the README quickstart so operators are steered away from plaintext key persistence.
HIGH PRAX-2026-07-10-002 forwarded_allow_ips="*" makes the loopback-only gate on reserved admin/inspector endpoints spoofable by any remote caller
Policy Rule — R-07 (Worker Remit):
"Exposing administrative, introspection, or message-history interfaces to unauthenticated remote callers."
asgi.py:184 — uvicorn.Config(..., forwarded_allow_ips="*") — trusts X-Forwarded-For from any client, letting scope["client"] be spoofed asgi.py:304 — reserved-endpoint guard allows only when '"127.0.0.1" not in scope["client"]' is false — a spoofed forwarded IP bypasses it; /agent_info is additionally exempted at line 305
Recommended Action
  • Set forwarded_allow_ips to the specific trusted proxy addresses (or leave it unset) so scope["client"] reflects the real peer, and gate reserved endpoints on the actual transport peer rather than a header-derived value.
  • Require an authentication token for the inspector/reserved endpoints instead of relying solely on source-IP equality.
HIGH PRAX-2026-07-10-003 Inbound signed envelopes are never checked against their expires/nonce fields, so a captured envelope can be replayed
Policy Rule — R-04 (Worker Remit):
"Accepting and re-processing a previously delivered (replayed) or expired signed envelope as if it were fresh."
asgi.py:363 — after env.verify() the message is dispatched at line 379 with no check that env.expires > now and no nonce/session replay tracking uagents-core/uagents_core/envelope.py:107 — _digest() folds expires and nonce into the signed hash, but verify() only checks the signature — nothing enforces freshness or single-use on receipt
Recommended Action
  • In asgi.py before dispatch, reject envelopes whose expires timestamp is in the past, and maintain a bounded seen-nonce/session cache to drop replays.
  • Make expires mandatory (non-null) for signed inbound envelopes so every accepted message carries an enforced freshness window.
MEDIUM PRAX-2026-07-10-004 Agent HTTP server binds to 0.0.0.0 by default and returns wildcard CORS for all origins
Policy Rule — R-08 (Worker Remit):
"Binding the agent's HTTP server to a non-loopback (public) network interface."
asgi.py:23 — HOST = "0.0.0.0" — hardcoded bind address used in uvicorn.Config(host=HOST, ...) with no parameter to restrict to loopback asgi.py:185 — CORS headers set Access-Control-Allow-Origin "*", Access-Control-Allow-Methods "GET, POST, OPTIONS", Access-Control-Allow-Headers "*"
Recommended Action
  • Default the bind host to 127.0.0.1 (or expose a host parameter on Agent) and require explicit operator opt-in to bind a public interface.
  • Scope CORS to the origins an agent actually needs rather than a blanket wildcard.
MEDIUM PRAX-2026-07-10-005 Agent inspector is enabled by default and /agent_info is served to any caller without the loopback gate
Policy Rule — R-07 (Worker Remit):
"Exposing administrative, introspection, or message-history interfaces to unauthenticated remote callers."
agent.py:305 — enable_agent_inspector: bool = True — default enables /messages, /connect, /disconnect and /agent_info REST handlers (registered at agent.py lines 472-540) asgi.py:305 — 'request_path in set(RESERVED_ENDPOINTS) - {"/agent_info"}' — /agent_info is exempted from the localhost-only guard, disclosing agent metadata to any caller
Recommended Action
  • Default enable_agent_inspector to False, requiring explicit operator opt-in before any reserved/introspection endpoint is registered.
  • Apply the same authenticated, non-spoofable local-only gate to /agent_info as the other reserved endpoints, or reduce the metadata it returns to unauthenticated callers.
MEDIUM PRAX-2026-07-10-006 User-address senders skip signature verification, so ctx.sender is attacker-controlled for any unsigned handler
Policy Rule — R-06 (Worker Remit):
"Acting on the claimed identity of a message sender without cryptographic proof of that identity."
asgi.py:363 — 'if not is_user_address(env.sender): env.verify()' — signature verification is skipped entirely for user-address senders before dispatch uagents-core/uagents_core/identity.py:37 — generate_user_address() returns _encode_bech32("user", token_bytes(32)) — a user address is unbound random bytes, so the sender identity carries no cryptographic proof
Recommended Action
  • Document prominently that ctx.sender is unauthenticated for user-address senders and must not be used for authorization, and surface a verified/unverified flag on the message context.
  • Provide a first-class authenticated-user mechanism (e.g., signed user envelopes) so handlers can require proof of user identity when needed.
MEDIUM PRAX-2026-07-10-007 get_or_create_private_keys persists a different wallet key than it returns, so a name-based agent's wallet is non-deterministic across restarts
Policy Rule — R-02 (Worker Remit):
"Create and manage a per-agent cryptographic identity (signing keypair) and a Fetch.ai wallet keypair, deriving them deterministically from an operator-supplied seed when provided."
storage/__init__.py:147 — wallet_key = PrivateKey().private_key then save_private_keys(name, identity_key, PrivateKey().private_key) at line 149 — persists a new random key, discarding wallet_key, before returning wallet_key
Recommended Action
  • Change the save call to persist the same wallet_key that is returned (save_private_keys(name, identity_key, wallet_key)) so the in-memory and on-disk wallet keys match.
  • Add a regression test that asserts a name-based agent's wallet address is stable across two constructions.
MEDIUM PRAX-2026-07-10-008 No default inbound rate limiting on the message endpoint, leaving agents open to resource exhaustion
asgi.py:379 — dispatcher.dispatch_msg is called for every accepted envelope with no rate/quota/concurrency check anywhere in the __call__ request path experimental/quota/__init__.py:1 — rate limiting is provided only as an opt-in experimental QuotaProtocol, not applied to the default inbound path
Recommended Action
  • Add a configurable default inbound rate limit / per-sender quota at the ASGI layer, applied before signature verification work is done.
  • Promote the QuotaProtocol out of experimental and document enabling it as a baseline deployment step.
What's Working Well

Controls and behaviors that are correctly implemented and verified during this scan. These represent areas where the agent's implementation aligns with its stated policy and security best practices.

Default signature verification of inbound agent messages

Inbound envelopes from agent addresses are cryptographically verified before dispatch and rejected on failure, and message handlers default to rejecting unverified senders (`allow_unverified=False`), so an agent must opt in to accept unauthenticated messages.

asgi.py:363

Deterministic seed-derived identity with no plaintext key write

When the operator supplies a seed, identity and wallet keys are derived deterministically (`Identity.from_seed`) and no key file is written, giving a safe key-handling path that avoids the plaintext `private_keys.json` persistence.

uagents-core/uagents_core/identity.py:90

Payload schema validation before handler dispatch

Every inbound message is parsed and validated against its registered pydantic model before the handler runs, and a schema mismatch returns a structured error rather than reaching handler logic.

agent.py:1438

Pinned dependency set with committed lockfile

The package ships a committed `uv.lock` and uses upper-bounded version ranges for its runtime dependencies, limiting exposure to dependency-confusion and silent version-swap attacks.

python/uv.lock
Discovered Log Files

Log files found in the agent's workspace during this scan. Reviewing these files provides runtime evidence to complement the static analysis above.

Path Source Content Type Purpose Last Modified Status
<cwd>/{address_prefix}_data.json (KeyValueStore-backed EnvelopeHistory) uagents.storage.KeyValueStore via EnvelopeHistory structured per-message JSON entries (sender, target, session, schema_digest, payload) optional message-history record, capped at 1000 entries / 86400s retention; written only when store_message_history is enabled unknown Inferred
operator-configured log file (uagents-core FileHandler) uagents_core.logger get_logger(log_file=...) free-form INFO-level text operational logging; a file sink is created only when the operator passes a log_file path, otherwise logs go to stdout unknown Inferred
OWASP LLM Top 10 (2025) Coverage

Each card represents one category and shows the top 3 findings. All items in the Findings section.

LLM01 Prompt Injection
No findings
LLM03 Supply Chain
No findings
LLM04 Data and Model Poisoning
No findings
LLM05 Improper Output Handling
No findings
LLM06 Excessive Agency
No findings
LLM07 System Prompt Leakage
No findings
LLM08 Vector and Embedding Weaknesses
No findings
LLM09 Misinformation
No findings
OWASP Agentic Top 10 (2026) Coverage

Each card represents one category and shows the top 3 findings. All items in the Findings section.

ASI01 Agent Goal Hijack
No findings
ASI02 Tool Misuse and Exploitation
No findings
ASI04 Agentic Supply Chain Vulnerabilities
No findings
ASI05 Unexpected Code Execution (RCE)
No findings
ASI06 Memory and Context Poisoning
No findings
ASI08 Cascading Failures
No findings
ASI09 Human-Agent Trust Exploitation
No findings
ASI10 Rogue Agents
No findings
RAISE Maturity Posture

Overall maturity assessment across the six categories of the RAISE framework. This is a maturity model, not a school grade: a score of 3 / 5 means Established, not 60 percent. Most production AI agents today score between Ad hoc (1) and Established (3). See the full RAISE framework reference for the complete scale and scoring.

2.00 / 5.0
Weighted Maturity Score · Partial
Partial posture (floor 2) overall. The framework earns real credit in Implement Zero Trust for default ECDSA signature verification of inbound agent envelopes and pydantic schema validation before dispatch, and in Manage Your Supply Chain for a committed `uv.lock` and bounded dependency ranges — but Zero Trust is capped at Partial by plaintext key persistence, absent replay protection, and a spoofable loopback gate. Limit Your Domain and Balance Your Knowledge Base sit at Partial because typed-handler routing and schema validation constrain the message surface even though the framework imposes no scope restriction. Build an AI Red Team is Ad hoc — a broad functional test suite exists (including signature-verification tests) but there is no evidence of adversarial or injection testing, and Monitor Continuously is Partial on the strength of an optional structured `EnvelopeHistory` that is non-durable by default.
Limit Your Domain
2/ 5
Confidence: Medium  |  Weight: 15%  |  Weighted: 0.30
Dispatch is keyed by message-schema digest and messages with an unrecognized digest are dropped (`agent.py` `_process_single_message`), which structurally narrows what an agent will process; however, the framework imposes no topic or scope restriction and agents are general-purpose by construction.
Balance Your Knowledge Base
2/ 5
Confidence: Low  |  Weight: 15%  |  Weighted: 0.30
The framework carries no LLM/RAG layer of its own, but it does validate every inbound payload against a declared pydantic model before the handler runs (`agent.py` `model_class.parse_raw`), giving a genuine input-schema boundary; it performs no semantic content sanitization, which is left to the agent developer.
Implement Zero Trust
2/ 5
Confidence: High  |  Weight: 25%  |  Weighted: 0.50
ECDSA envelope signature verification runs by default for agent senders and unverified agent messages are rejected (`asgi.py` `env.verify()`, `on_message(allow_unverified=False)`), but the control set is holed by plaintext key persistence, no inbound `expires`/`nonce` replay enforcement, a `forwarded_allow_ips="*"` spoofable loopback gate, a default `0.0.0.0` bind with wildcard CORS, and no inbound rate limiting.
Manage Your Supply Chain
3/ 5
Confidence: Medium  |  Weight: 15%  |  Weighted: 0.45
A committed `uv.lock` and upper-bounded dependency ranges in `pyproject.toml` plus a documented version-compatibility matrix give established supply-chain hygiene; minor gaps are one floor-only pin (`cosmpy >=0.12.1`) and no published SBOM/ML-BOM.
Build an AI Red Team
1/ 5
Confidence: Medium  |  Weight: 15%  |  Weighted: 0.15
A substantial functional test suite exists under `tests/` (including `test_msg_verify.py`, `test_server.py`, `test_registration.py`), but there is no adversarial, fuzzing, or injection/replay test evidence and nothing showing security testing drove design changes.
Monitor Continuously
2/ 5
Confidence: Medium  |  Weight: 15%  |  Weighted: 0.30
An opt-in `EnvelopeHistory` records structured, per-message entries (sender, target, session, schema, payload) with a size/retention cap, but it is in-memory and non-durable by default, and the baseline `get_logger` emits only free-form INFO to stdout with no action-level or alerting layer.

Maturity Scoring Rubric

Every score above is based on this scale. A score is a snapshot of observable posture — not a verdict on the people or team behind the system.

Score Label Meaning
5 Exemplary Best-in-class; automated, continuously tested, reference quality. Rarely achieved in shipping systems.
4 Strong Comprehensive controls, active management, minor gaps. Production-ready.
3 Established Documented controls consistently applied; known gaps accepted. A respectable baseline.
2 Partial Some controls exist but coverage is incomplete; key gaps remain.
1 Ad hoc Informal or inconsistent measures; relies on individual judgment.
0 Absent No evidence this category is addressed at all.
Weighting: the weighted overall above is the sum of each category's score × weight (the per-category weights are shown on each card). Zero Trust carries double weight by design; see the RAISE framework reference for the rationale.