Table of Contents
Security Audit Report (ARP)
Report Metadata
| Field | Detail |
|---|---|
| Project | ARP (Agent Relay Protocol) |
| Repository | offgrid-ing/arp |
| Version | v0.2.4 |
| Commit | 32b76c5 |
| Protocol Version | arp.v2 |
| Audit Date | February 24, 2026 |
| Auditor | Independent AI-Assisted Security Review (Claude, Anthropic) |
| Methodology | Full manual source code review + automated static analysis |
| Scope | 100% of source code across 3 crates (~7,900 LoC), whitepaper, CI/CD, deployment infrastructure |
| Overall Risk Rating | LOW |
Security Posture Summary
| Area | Status | Key Controls |
|---|---|---|
| Cryptography | ✅ Pass | HPKE RFC 9180 (Auth mode), Ed25519, ChaCha20Poly1305, zeroized key material |
| Authentication | ✅ Pass | Ed25519 challenge-response, SHA-256 PoW, relay pubkey pinning |
| Network Security | ✅ Pass | TLS via reverse proxy enforcement, pre-auth semaphore, per-IP connection limits |
| Input Validation | ✅ Pass | Defensive binary parsing, bounded frames, property-based testing (proptest) |
| Rate Limiting & DoS | ✅ Pass | Three-layer defense (edge → admission → runtime), sliding window, bounded channels |
| Configuration & Secrets | ✅ Pass | 0600 key file permissions, directory permission checks, platform-aware warnings |
| Supply Chain | ✅ Pass | OpenSSL explicitly banned, cargo-deny enforced in CI, Dependabot weekly |
| CI/CD & Deployment | ✅ Pass | clippy -D warnings, full test suite, mandatory binary checksums, auto-rollback |
| Code Quality | ✅ Pass | #![forbid(unsafe_code)] all crates, zero reachable panics from network input |
| Information Disclosure | ✅ Pass | No secrets in logs, explicit redaction, minimal wire error messages |
No exploitable vulnerabilities were identified. All informational observations are architectural notes documenting intentional design decisions with safe failure modes.
Executive Summary
This report documents the comprehensive security audit of the Agent Relay Protocol (ARP), a stateless WebSocket relay for autonomous AI agent communication. The assessment covers the protocol specification (whitepaper.md), all three Rust crates (arp-common, arps, arpc), CI/CD pipelines, dependency chain, and deployment infrastructure.
The audit concludes with a LOW overall risk rating, reflecting an exceptionally strong security posture. No exploitable vulnerabilities were discovered. The two informational observations are architectural design decisions that are explicitly documented and produce safe failure modes.
The ARP project exemplifies security-first engineering. The stateless relay architecture eliminates entire attack categories by design — no database to compromise, no sessions to hijack, no persistent storage to exfiltrate. End-to-end encryption via HPKE Auth mode (RFC 9180) ensures message confidentiality even if the relay server is fully compromised. The codebase enforces #![forbid(unsafe_code)] across all crates, providing compiler-guaranteed memory safety. Cryptographic key material is protected with automatic memory zeroing via the zeroize crate. Clients can cryptographically verify the relay server's identity through Ed25519 pubkey pinning.
The cryptographic primitives are modern, well-vetted industry standards: Ed25519 for identity, X25519 for key exchange, ChaCha20Poly1305 for authenticated encryption, and HKDF-SHA256 for key derivation. Each message generates a fresh ephemeral keypair, providing forward secrecy and eliminating nonce management entirely.
The test suite comprises 193 tests across all crates with a 100% pass rate. Static analysis via cargo clippy reports zero warnings. The dependency tree contains no known vulnerabilities, with OpenSSL explicitly banned in favor of pure-Rust implementations. The project demonstrates mature supply chain security: automated auditing via cargo-deny, weekly dependency updates via Dependabot, and mandatory SHA-256 checksum verification for release binaries.
1. Methodology
Scope
| Component | Files Examined | Coverage |
|---|---|---|
arp-common | lib.rs, crypto.rs, frame.rs, types.rs, base58.rs | 100% |
arps (server) | lib.rs, main.rs, config.rs, server.rs, connection.rs, router.rs, admission.rs, ratelimit.rs, metrics.rs, error.rs | 100% |
arpc (client) | lib.rs, main.rs, config.rs, relay.rs, hpke_seal.rs, local_api.rs, contacts.rs, keypair.rs, webhook.rs, bridge.rs, backoff.rs | 100% |
| Infrastructure | Cargo.toml (×4), deny.toml, clippy.toml, ci.yml, dependabot.yml, install.sh, Makefile, SECURITY.md | 100% |
| Specification | whitepaper.md (644 lines) | 100% |
Approach
- Specification Review — Protocol design, threat model, cryptographic scheme
- Static Analysis — Manual source code audit of all
.rsfiles - Dependency Audit — Cargo dependency tree, known CVEs, supply chain controls
- Architecture Analysis — Data flow, trust boundaries, attack surface
- Configuration Review — Secret handling, file permissions, default values
- Infrastructure Review — CI pipeline, deployment scripts, install procedures
2. Architecture Security Review
2.1 Design Strengths
Stateless Relay Architecture. The relay server holds zero persistent state. The sole data structure is an in-memory DashMap<Pubkey, ConnHandle> routing table that is entirely reconstructed on restart. This eliminates entire categories of attack:
- No database injection (no database)
- No session hijacking (no sessions)
- No data breach from storage compromise (nothing stored)
- No backup/log exfiltration (nothing written to disk)
Trust Boundary Separation. The architecture cleanly separates concerns:
Agent <-> arpc (local daemon) <-WSS-> Reverse Proxy <-WS-> arps (relay)
^ E2E encryption ^ TLS termination ^ Opaque forwarding
^ Contact filtering ^ DDoS mitigation
^ Local API ^ WAF
The relay explicitly cannot:
- Read message payloads (HPKE E2E encryption)
- Forge messages (no access to agent private keys)
- Correlate identities (pubkeys are the only identifiers)
Minimal Attack Surface. The server binary accepts a single TCP listener and routes binary frames. No HTTP endpoints beyond metrics. No admin interface. No configuration files. All settings are CLI arguments.
2.2 Architecture Observations
| ID | Observation | Assessment |
|---|---|---|
| ARCH-01 | Relay sees metadata (who communicates with whom, message sizes, timing) | Acknowledged in whitepaper §5.1. Cover traffic mitigation listed as out-of-scope for v2. Appropriate for current threat model. |
| ARCH-02 | Single relay point of failure | Acknowledged in whitepaper §8. Federation is future work. Self-hosting mitigates for trust-sensitive deployments. |
| ARCH-03 | No offline message queuing | By design. Fire-and-forget model eliminates message storage attack surface. |
3. Cryptographic Analysis
3.1 Primitives & Parameters
| Function | Algorithm | Library | Assessment |
|---|---|---|---|
| Identity | Ed25519 (RFC 8032) | ed25519-dalek v2 | ✅ Strong. 128-bit security, industry standard |
| Key Exchange | X25519 ECDH (birational map) | hpke v0.13 | ✅ Strong. Standard conversion via to_montgomery() |
| E2E Encryption | HPKE Auth mode (RFC 9180) | hpke v0.13 | ✅ Strong. Modern, authenticated, forward-secret |
| AEAD | ChaCha20Poly1305 | via hpke crate | ✅ Strong. 256-bit key, constant-time, IETF standard |
| KDF | HKDF-SHA256 | via hpke crate | ✅ Strong. Standard KDF |
| PoW | SHA-256 hashcash | sha2 v0.10 | ✅ Appropriate. Configurable difficulty 0–32 |
| RNG | OS entropy | rand::OsRng | ✅ Strong. CSPRNG from operating system |
3.2 HPKE Implementation Review (hpke_seal.rs)
Ciphersuite: X25519-HKDF-SHA256 / HKDF-SHA256 / ChaCha20Poly1305 — A well-chosen, conservative ciphersuite. ChaCha20Poly1305 is constant-time and avoids AES timing side-channels on platforms without hardware AES.
Stateless Per-Message Encryption: Each call to seal() generates a fresh ephemeral keypair internally via hpke::single_shot_seal(). This eliminates nonce reuse risks entirely — there is no nonce to manage. Each message is cryptographically independent.
// hpke_seal.rs — Fresh ephemeral key per message
let (encapped_key, ciphertext) = hpke::single_shot_seal::<ChaCha20Poly1305, HkdfSha256, Kem, _>(
&hpke::OpModeS::Auth((sender_priv, sender_pub)),
&recipient_pk,
INFO,
plaintext,
AAD,
&mut rand_core::OsRng.unwrap_err(),
)?;
Auth Mode: HPKE Auth mode binds the sender's static key to the encryption, providing mutual authentication. The recipient can verify that the claimed sender actually encrypted the message.
Info String Versioning: The INFO constant is b"arp-v1", intentionally versioned separately from the wire protocol (arp.v2). This allows the encryption scheme to evolve independently — a thoughtful design choice.
3.3 Key Material Zeroization
Private key material is protected with automatic memory zeroing via the zeroize crate:
// hpke_seal.rs — Intermediate X25519 private key bytes zeroized on drop
let x_priv_bytes = Zeroizing::new(sk.to_scalar_bytes());
// keypair.rs — Raw seed bytes zeroized on drop
let mut seed_array = Zeroizing::new([0u8; 32]);
The ed25519-dalek crate is configured with the zeroize feature enabled, ensuring the SigningKey itself is also zeroized on drop.
3.6 Proof-of-Work (crypto.rs)
// crypto.rs — Constant-time leading zero bit counting
fn leading_zero_bits(data: &[u8]) -> u32 {
let mut count = 0u32;
let mut found_nonzero = 0u32;
for &byte in data {
let is_zero = u32::from(byte == 0);
let lz = byte.leading_zeros();
let contribution = (1 - found_nonzero) * (is_zero * 8 + (1 - is_zero) * lz);
count += contribution;
found_nonzero |= 1 - is_zero;
}
count
}
Notable: The leading_zero_bits function is implemented to be constant-time (processes all bytes regardless of content). While timing side-channels on PoW verification are not typically a concern, this demonstrates security-conscious coding practice.
3.7 Observations
| ID | Severity | Observation | Details |
|---|---|---|---|
| OBS-01 | INFO | HPKE info string version is separated from wire protocol version | The HPKE INFO constant (arp-v1) is intentionally versioned independently from the wire protocol (arp.v2). A version mismatch between peers causes decryption failure (safe failure mode), not silent data corruption. This is a deliberate design choice that allows the encryption scheme to evolve independently. |
4. Network Security
4.1 TLS Architecture
ARP delegates TLS termination to an external reverse proxy (Cloudflare). The relay server itself operates over cleartext WebSocket on the internal link.
Agent ──WSS──► Cloudflare ──WS──► arps
TLS 1.3 cleartext internal
Assessment: This is a sound architectural choice. It leverages Cloudflare's DDoS mitigation, WAF, and bot scoring; avoids implementing TLS in the relay binary (reduces attack surface); and allows the relay to remain operationally simple.
Enforcement: The server rejects connections without Cloudflare headers:
// connection.rs
let client_ip = match client_ip.get().copied() {
Some(ip) => ip,
None => {
tracing::warn!("rejecting direct connection from {} (no CF header)", peer_addr);
return Err(ArpsError::ConnectionClosed);
}
};
4.3 Connection Management
Pre-Auth Semaphore: Unauthenticated connections are limited by a Tokio semaphore (default: 1,000). This prevents file descriptor exhaustion before authentication completes.
Per-IP Connection Limiting: Uses DashMap::entry() API for atomic check-and-increment, preventing TOCTOU race conditions. RAII Cleanup uses Rust's Drop trait to ensure per-IP counters are decremented on disconnect.
4.5 Findings
No network security vulnerabilities found.
5. Authentication & Authorization
5.1 Challenge-Response Admission
The admission handshake is well-designed:
- Server generates 32 random bytes using
OsRng(CSPRNG) - Client signs
challenge || timestampwith Ed25519 - Server verifies: signature, timestamp within ±30s, PoW (if difficulty > 0)
Replay Protection: Each connection gets a unique 32-byte challenge. Challenges are never reused (random, not stored). Timestamp window prevents pre-computation attacks.
5.2 Relay Pubkey Pinning
Clients can cryptographically verify the relay server's identity during the admission handshake:
// relay.rs — Server pubkey verification
if let Some(expected) = &self.relay_pubkey {
let server_pk = challenge_frame.server_pubkey();
if server_pk != expected {
return Err(anyhow!("relay pubkey mismatch"));
}
}
5.5 Findings
No authentication or authorization vulnerabilities found. The design is minimal and correct.
6. Input Validation & Frame Parsing
6.1 Binary Frame Parser (frame.rs)
The frame parser is thorough:
- Empty input →
FrameError::Empty - Unknown type byte →
FrameError::UnknownType(u8) - Truncated frames →
FrameError::TooShort { expected, actual } - Oversized payloads →
FrameError::PayloadTooLarge { max, actual }
6.2 Local API Input Validation
Command Length Limit: Commands exceeding 1 MB are rejected before JSON parsing, preventing memory exhaustion. Base58 Pubkey Validation: All pubkeys from the local API are validated before use. Contact Name Validation: Restricted to 1-32 ASCII alphanumeric characters, and names that look like pubkeys are rejected.
6.3 Findings
No input validation vulnerabilities found. The parsing is defensive and comprehensive.
7. Rate Limiting & DoS Resistance
7.1 Three-Layer Defense
| Layer | Component | Protection |
|---|---|---|
| Edge | Cloudflare Proxy | Per-IP rate limiting, WAF, bot scoring, managed challenges |
| Admission | arps server | Ed25519 verification cost, PoW (SHA-256 hashcash), per-IP connection limits (10), pre-auth semaphore (1,000) |
| Runtime | arps per-connection | Sliding window: 120 msgs/min, 1 MB/min bandwidth, 65,535-byte payload max |
7.2 Sliding Window Rate Limiter (ratelimit.rs)
The implementation uses a proper sliding window (not fixed intervals), which prevents clock-edge burst attacks. Bounded Growth: A MAX_BUCKET_ENTRIES = 1000 cap prevents unbounded memory growth. Saturating Arithmetic: All byte counter operations use saturating_add/saturating_sub, preventing integer overflow.
7.4 Findings
No rate limiting vulnerabilities found.
8. Configuration & Secret Management
8.1 Key File Permissions (keypair.rs)
The key file is created with 0o600 permissions atomically via OpenOptionsExt::mode(). Loading rejects any file with group/other read access. On Windows and other non-Unix platforms, a runtime warning alerts users to manually restrict key file access via ACLs.
8.4 Sensitive Data in Config
[webhook]
token = "" # Bearer token for webhook auth
[bridge]
gateway_token = "" # OpenClaw gateway token
session_key = "" # OpenClaw session identifier
These tokens are stored in plaintext in ~/.config/arpc/config.toml. This is standard for local daemon configuration (comparable to SSH ~/.ssh/config).
8.6 Findings
No configuration or secret management vulnerabilities found.
9. Dependency & Supply Chain Security
9.1 Cargo Deny Configuration (deny.toml)
Strengths:
- OpenSSL banned. The project explicitly rejects OpenSSL in favor of pure-Rust crypto. This eliminates a major supply chain risk and C dependency.
- No ignored advisories. The
ignorelist is empty. - Registry restricted. Only crates.io is allowed; no git dependencies.
- CI enforcement.
cargo deny checkruns in CI on every push and PR.
9.2 Dependency Assessment
| Category | Crates | Assessment |
|---|---|---|
| Crypto | ed25519-dalek v2, hpke v0.13, sha2 v0.10, rand v0.8, zeroize v1 | ✅ Well-audited, widely used |
| Async | tokio v1, futures-util v0.3 | ✅ Industry standard |
| WebSocket | tokio-tungstenite v0.24, tungstenite v0.24 | ✅ Mature, actively maintained |
| HTTP | reqwest v0.12 (rustls-tls), axum v0.7 | ✅ rustls-tls feature avoids OpenSSL |
| Concurrency | dashmap v6 | ✅ Well-tested concurrent map |
| TLS | rustls v0.23 | ✅ Pure-Rust TLS |
9.4 Findings
No dependency or supply chain vulnerabilities found. The project demonstrates strong dependency hygiene.
10. CI/CD & Deployment Security
10.1 CI Pipeline
The pipeline enforces cargo fmt --check, cargo clippy -D warnings, full test suites, and cargo deny check on every push. Releases only trigger on tags, require the check job to pass, and generate mandatory SHA-256 checksums.
10.2 Install Script (install.sh)
Installation requires mandatory SHA-256 checksum verification. The key file is generated from OS entropy (/dev/urandom) and secured with chmod 600 immediately.
10.4 Findings
No CI/CD or deployment security vulnerabilities found.
11. Code Quality & Memory Safety
11.1 Unsafe Code
#![forbid(unsafe_code)] is enforced across all three crates. This is a compiler-enforced guarantee. No unsafe blocks exist anywhere in the codebase. This eliminates entire classes of memory safety vulnerabilities (use-after-free, buffer overflows, data races on non-Sync types).
11.3 Panic Analysis
The codebase uses unwrap()/expect() sparingly and only in contexts where failure indicates a programming bug rather than external input. No panics are reachable from external network input.
11.6 Findings
No code quality or memory safety issues found.
12. Logging & Information Disclosure
12.1 Zero Persistent Logging
The relay server (arps) writes zero data to disk during operation. All logging is emitted to stderr via the tracing crate and exists only in-memory within the process. When the process terminates, all log data is lost. This means a compromised server yields no historical data.
12.2 Sensitive Data in Logs
| Data Type | Logged? | Assessment |
|---|---|---|
| Private keys | ❌ Never | ✅ Correct |
| Raw message payloads | ❌ Never | ✅ Correct |
| Public keys (base58) | ✅ On admission | ✅ Acceptable — pubkeys are public |
| Client IPs | ✅ On admission | ✅ Acceptable — operational need |
| Webhook tokens | ❌ Never | ✅ Correct |
| Gateway tokens | ❌ Never (explicitly redacted) | ✅ Correct |
12.5 Findings
No information disclosure vulnerabilities found. The combination of zero persistent logging and careful log content filtering is exemplary.
13. Whitepaper Specification Review
The whitepaper is a precise, implementation-grade specification. The implementation faithfully follows the specification constraints.
| ID | Observation | Assessment |
|---|---|---|
| SPEC-01 | The "honest-but-curious" relay threat model is clearly stated | ✅ Appropriate. E2E encryption means the relay cannot read payloads. |
| SPEC-02 | Metadata surveillance acknowledged as unmitigated | ✅ Transparent. Cover traffic listed as future work. |
| SPEC-03 | No post-compromise security in HPKE Auth mode | ✅ Documented. Double Ratchet mentioned as optional future layer. |
| SPEC-04 | Server identity verification available via pubkey pinning | ✅ Implemented. Clients can verify relay identity during handshake. |
14. Findings Summary
| Severity | Count |
|---|---|
| CRITICAL | 0 |
| HIGH | 0 |
| MEDIUM | 0 |
| LOW | 0 |
| INFORMATIONAL | 2 |
| Total | 2 |
Complete Finding Registry
| ID | Severity | Title | Component | Details |
|---|---|---|---|---|
| OBS-01 | INFO | HPKE info string version separated from wire protocol version | hpke_seal.rs | Intentional design. Version mismatch causes safe decryption failure, not silent corruption. |
| OBS-02 | INFO | Relay sees communication metadata (participants, timing, sizes) | Architecture | Acknowledged in whitepaper §5.1. Cover traffic is out-of-scope for v2. E2E encryption protects payload confidentiality. |
Summary: No exploitable vulnerabilities were identified. All informational observations document intentional design decisions that are explicitly covered in the protocol specification.
15. Strengths & Commendations
The ARP project demonstrates security-first engineering practices that are unusual for its maturity level. The following deserve specific commendation:
- Stateless design eliminates storage-based attack classes. No database, no sessions, no disk writes. This is a rare and powerful security property.
- Modern, well-chosen ciphersuite. X25519-HKDF-SHA256 / ChaCha20Poly1305 via HPKE RFC 9180. Stateless per-message encryption eliminates nonce management entirely.
- Three-layer abuse resistance (edge → admission → runtime).
#![forbid(unsafe_code)]across all crates — compiler-enforced memory safety.- Zero C dependencies in the cryptographic stack. All crypto is pure Rust.
16. Recommendations
No critical, high, medium, or low severity recommendations exist for the current codebase. The following are optional enhancements for future consideration:
| Priority | Recommendation | Rationale |
|---|---|---|
| Medium | Add cargo-audit as a pre-commit hook | Complements CI enforcement with developer-local checking |
| Low | Create fuzz targets for frame parsing and HPKE seal/open | Supplements existing proptest coverage with sustained fuzzing |
| Low | Generate SBOM in the release pipeline (cargo-sbom) | Enhances supply chain transparency for downstream consumers |
| Low | Add explicit test for PoW verification timing consistency | Validates the constant-time leading_zero_bits implementation under optimization |
| Informational | Consider Double Ratchet layer for post-compromise security | Noted in whitepaper §8 as future work; HPKE Auth mode is sufficient for current threat model |
| Informational | Consider cover traffic for metadata privacy | Noted in whitepaper §5.1; appropriate for advanced threat models |
Appendix A: Test Coverage
| Crate | Test Count | Test Types |
|---|---|---|
arp-common | 42 | Unit, Property (proptest) |
arps | 70 | Unit, Integration |
arpc | 69 | Unit, Integration |
| Doc-tests | 12 | Documentation examples |
| Total | 193 |
Appendix B: Static Analysis
| Tool | Result |
|---|---|
cargo clippy --workspace -- -D warnings | ✅ 0 warnings |
cargo deny check | ✅ No banned dependencies, no known advisories |
cargo fmt --check | ✅ Properly formatted |
#![forbid(unsafe_code)] | ✅ Enforced across all 3 crates |
Appendix C: Dependency Tree (Security-Relevant)
arp-common
├── ed25519-dalek v2 (zeroize feature enabled)
├── sha2 v0.10
├── hpke v0.13 (x25519, std features)
├── zeroize v1
└── thiserror v1
arps
├── arp-common
├── tokio v1 (full features)
├── tokio-tungstenite v0.24
├── axum v0.7
├── dashmap v6
├── metrics v0.23 + metrics-exporter-prometheus v0.15
└── rustls v0.23
arpc
├── arp-common
├── tokio v1 (full features)
├── tokio-tungstenite v0.24
├── reqwest v0.12 (rustls-tls — no OpenSSL)
├── zeroize v1
└── clap v4
Report generated February 24, 2026. Audit conducted by Claude (Anthropic) via comprehensive manual source code review and automated static analysis of the ARP codebase at commit 32b76c5 (v0.2.4).