How to run Rust code inside a hardware TEE that calls OpenWeatherMap — without the API key ever leaving the enclave.
The Problem
AI agents are useless without access to real APIs, real credentials, and real user data. But every external call introduces a trust question: do we trust the agent's operator not to log our API key? Do we trust the node runner not to peek at the response?
Traditional solutions stack SLAs and compliance paperwork. Terminal 3 (T3N) takes a different approach: hardware-enforced confidentiality. Your code runs inside an Intel TDX Trusted Execution Environment — a hardware black box that even the node operator cannot inspect.
This post walks through building a weather agent that proves the pattern end-to-end.
Architecture Overview
┌──────────────┐ encrypted session ┌──────────────────────┐
│ User CLI │ ─────────────────────────→ │ T3N Node │
│ (tsx + SDK) │ │ ┌────────────────┐ │
└──────────────┘ │ │ TDX Enclave │ │
│ │ ┌──────────┐ │ │
│ │ │ Weather │ │ │
│ │ │ Contract │──┼──┼──→ OpenWeatherMap
│ │ │ (WASM) │ │ │
│ │ └────┬─────┘ │ │
│ │ │ │ │
│ │ kv_store::get() │ │
│ │ │ │ │
│ │ ┌────▼─────┐ │ │
│ │ │ Sealed │ │ │
│ │ │ KV Map │ │ │
│ │ └──────────┘ │ │
│ └────────────────┘ │
└──────────────────────┘
Three layers of protection:
- Transport: All communication is encrypted with ML-KEM (post-quantum) through the T3N SDK's session layer
- Compute: The WASM contract executes inside an Intel TDX enclave — memory encrypted by the CPU, attestable remotely
-
Secrets: API keys are written via the control plane (
map-entry-set), bypassing even the map's own ACL, and can only be read bykv_store::get()from inside the enclave
The WIT Interface
WebAssembly Interface Types (WIT) declares the contract's boundary — what it imports from the host and what it exports to callers:
package z:tenant-weather@0.1.0;
world tenant-weather {
// Host capabilities the contract needs
import host:tenant/tenant-context@1.0.0; // → tenant-did, contract-id
import host:interfaces/logging@2.1.0; // → info, debug, error
import host:interfaces/kv-store@2.1.0; // → get, put, delete, scan
import host:interfaces/http@2.1.0; // → call (GET/POST/etc.)
// What callers can invoke
export contracts;
}
interface contracts {
record generic-input {
input: option<list<u8>>,
user-profile: option<list<u8>>,
context: option<list<u8>>,
}
get-weather: func(req: generic-input) -> result<list<u8>, string>;
}
Every capability the contract has is declared explicitly here. No implicit access to filesystem, network, or secrets. The host enforces this at the Wasm boundary.
Contract Implementation
Reading Secrets Inside the TEE
The contract resolves the tenant DID at runtime, constructs the map name, and reads the API key:
fn get_api_key() -> Result<String, String> {
let tid = tenant_context::tenant_did();
let map_name = format!("z:{}:secrets", hex::encode(&tid));
let bytes = kv_store::get(&map_name, b"weather_api_key")
.map_err(|e| format!("kv read: {e}"))?
.ok_or("weather_api_key not found")?;
String::from_utf8(bytes).map_err(|e| e.to_string())
}
The key was seeded via the SDK's control plane (tenant.executeControl("map-entry-set", ...)) — it writes directly to the underlying storage, bypassing the map's writers ACL. This is a one-time operation done during deployment. After that, no external observer can read it back.
Making HTTP from Inside the Enclave
let resp = http_iface::call(&http_iface::Request {
method: http_iface::Verb::Get,
url: format!("{OWM_BASE}/data/2.5/weather?q={}&appid={}&units=metric",
city, api_key),
headers: None,
payload: None,
})?;
The HTTP call goes through the host's egress proxy. Before the request leaves the enclave, the host checks: does the calling user's authorization grant allow this contract to dial api.openweathermap.org? If not — the request is denied with egress_denied.
Parsing and Returning
let weather = WeatherResp {
city: req.city,
temperature: data["main"]["temp"].as_f64().unwrap_or(0.0),
feels_like: data["main"]["feels_like"].as_f64().unwrap_or(0.0),
humidity: data["main"]["humidity"].as_u64().unwrap_or(0) as u32,
description: data["weather"][0]["description"]
.as_str().unwrap_or("unknown").to_string(),
wind_speed: data["wind"]["speed"].as_f64().unwrap_or(0.0),
};
serde_json::to_vec(&weather)
The JSON-serialized response is encrypted through the session and returned to the caller — who never saw the API key, never touched OpenWeatherMap directly, and whose only interaction with the TEE was a signed invocation.
Deployment Script
The TypeScript SDK orchestrates the full lifecycle:
// 1. Authenticate
setEnvironment("testnet");
const t3n = new T3nClient({
wasmComponent: await loadWasmComponent(),
handlers: { EthSign: metamask_sign(address, undefined, key) },
});
await t3n.handshake();
const did = await t3n.authenticate(createEthAuthInput(address));
// 2. Register the WASM component
const tenant = new TenantClient({ t3n, baseUrl: getNodeUrl(), tenantDid });
const { contract_id } = await tenant.contracts.register({
tail: "weather-agent", version: "0.1.0", wasm: wasmBytes,
});
// 3. Create sealed KV map (only this contract can access)
await tenant.maps.create({
tail: "secrets", visibility: "private",
writers: { only: [contract_id] },
readers: { only: [contract_id] },
});
// 4. Seed the API key (control-plane bypasses ACL)
await tenant.executeControl("map-entry-set", {
map_name: tenant.canonicalName("secrets"),
key: "weather_api_key", value: OWM_API_KEY,
});
// 5. Grant the contract permission to call OpenWeatherMap
await t3n.execute({
script_name: "tee:user/contracts",
function_name: "agent-auth-update",
input: {
agents: [{
agentDid: tenantDid,
scripts: [{
scriptName, functions: ["get-weather"],
allowedHosts: ["api.openweathermap.org"],
}],
}],
},
});
// 6. Invoke
const result = await t3n.executeAndDecode({
script_name: scriptName, function_name: "get-weather",
input: { city: "Tokyo" },
});
The script is designed to be idempotent — if the contract version already exists, registration is skipped, the map ACL is updated in place, and only the invocation costs tokens on re-runs.
Live Output
$ npm run deploy
authenticated as did:t3n:2c9d71730c17e69e394afbffb973d1a6444f4423
registered z:...:weather-agent as contract id 260
seeded weather_api_key into z:<tid>:secrets
authorized outbound HTTP to api.openweathermap.org
weather result: {
"city": "Tokyo",
"temperature": 23.18,
"feels_like": 23.85,
"humidity": 88,
"description": "moderate rain",
"wind_speed": 4.78
}
Why This Matters
The weather agent is deliberately minimal, but the pattern generalizes to any confidential agent workflow:
| Scenario | Secret | External API | Pattern |
|---|---|---|---|
| Weather | OpenWeatherMap key | api.openweathermap.org | This demo |
| Payroll | Bank API credentials + PII | bank.disbursement.com |
http-with-placeholders for PII substitution |
| Travel booking | Duffel/Amadeus API key | api.duffel.com | Same pattern + PII placeholders |
| KYC/Identity | Veriff/Onfido key + user docs | api.veriff.com | PII substitution; user profile never in WASM memory |
| Healthcare | EHR API token + patient data | fhir.ehr.com | PII substitution; HIPAA-relevant audit trail |
The key insight: the contract holds credentials, not the agent. The agent calls the contract; the contract uses the credentials inside the TEE. If the agent is compromised, the attacker gets a function call — not the API key.
The Full Stack
| Layer | Technology |
|---|---|
| Contract language | Rust |
| Target |
wasm32-wasip2 (WASI Preview 2 component) |
| Code generation |
wit-bindgen 0.49 |
| Host interfaces |
kv-store, http, logging, tenant-context
|
| Client SDK |
@terminal3/t3n-sdk 3.9.0 |
| TEE technology | Intel TDX (trusted execution environment) |
| Network | T3N testnet (public sandbox with test tokens) |
| External data | OpenWeatherMap API |
| Serialization |
serde + serde_json (no-std, alloc-only) |
Try It Yourself
- Get a T3N API key at terminal3.io/claim-page (20,000 free test tokens)
- Get an OpenWeatherMap API key
- Clone the repo, run
npm install && cargo build --release && npm run deploy
Full source code and documentation: https://github.com/harishkotra/weather-agent
Built with Terminal 3 — the agent developer kit for confidential, auditable AI agents.
Top comments (0)