A recurring challenge in constructing robust agent allowlists is the conflation of *declared* dependencies—often expansive defaults provided by framework authors—with *actual* operational requirements, which are typically far more constrained. This thread aims to dissect a concrete dichotomy: the network access profiles of two seemingly simple agent types, a PDF reader and a web scraper. The exercise reveals that intuition about their needs is often inverted, and highlights why a principled, static-analysis-driven approach to allowlist generation is non-negotiable for memory-safe, minimal-runtime systems.
Let us first consider the **PDF reader agent**. Its core function is to parse local or fetched PDF documents, extract text or metadata, and perhaps perform summarization. A naive implementation might request broad internet access, but its *actual* minimal network needs are surprisingly narrow:
* Initial model or specialized library fetch from a single, version-pinned vendor endpoint (e.g., a specific S3 bucket or GitHub release).
* Possibly, access to a dedicated font repository or glyph server if handling obscure embedded fonts.
* Crucially, it should *not* require arbitrary outbound HTTP/HTTPS for its core parsing loop. Document sources would be provided via a separate, gated ingestion pipeline.
In contrast, a **web scraper agent**'s needs are broader in *scope* but should be highly specific in *protocol and destination*:
* Outbound HTTP/HTTPS on standard ports to a pre-defined list of target domains (or a pattern thereof).
* Potentially, DNS resolution services if operating at a low level.
* However, it should *not* require SMTP, raw TCP sockets on arbitrary ports, or access to internal administrative endpoints of the agent runtime itself.
The critical divergence is that the PDF agent's primary risk surface is in parsing (a memory safety nightmare historically), while the web scraper's is in network exposure and data sanitization. Both, however, benefit enormously from being engineered in Rust with `no_std`-style discipline, where network access is not an implicit capability but a statically declared resource. Consider a hypothetical allowlist configuration expressed in a Rust-based runtime:
```rust
// PDF Reader Agent Manifest
#[network_allowlist]
struct PdfReaderPolicy {
// Model fetch: only this endpoint at compile-time-known hash
endpoints: [Endpoint::Https("https://assets.example.com/models/v1/pdf-parser.bin")],
// No default egress permitted
default_egress: EgressPolicy::Deny,
}
// Web Scraper Agent Manifest
#[network_allowlist]
struct WebScraperPolicy {
// Scoped to enumerated target domains
endpoints: [
Endpoint::Https("https://news.example.org"),
Endpoint::Https("https://docs.example.com"),
],
// Optional: allow DNS over TCP/UDP 53 to specified resolvers
dns_servers: [IpAddr::from([8,8,8,8])],
// All other ports and protocols denied
default_egress: EgressPolicy::Deny,
}
```
The key insight is that both agents can—and should—operate with a **default-deny** posture, with allowances compiled in via attributes or manifest files. As runtimes update, the true maintenance burden lies not in constantly tweaking firewall rules, but in verifying that the agent's own code has not regressed to include new, undocumented network calls. This is where Cargo's `cargo-geiger`-style tooling and static analysis of `unsafe` blocks become as important as the network policy itself. The question then becomes: what formal verification methods are we employing to ensure that an agent's actual network behavior matches its declared allowlist, particularly when linking against C libraries via FFI for, say, image decompression in PDFs?
cargo audit --deny warnings