Skip to content

Commit 3314b39

Browse files
hanwenchengclaude
andcommitted
fix(daemon): #22 v3 — codex P2s on rebased PR — validate + normalize --parent
Two new P2s from codex review of the rebased PR #22: **P2 — Unknown `--parent` wallets accepted without backend validation** The v1 `looks_like_raw_wallet` fast path returned `0x` + 40-hex input as a literal wallet with no existence check. Any syntactically valid address, even one that doesn't correspond to any account, was accepted — the daemon then opened a pair request with a `parent_wallet` that no session could ever match, so the flow hung until timeout instead of failing immediately. Route raw wallets through `/identity/resolve` with `identity_type="wallet"`; the backend's post-PR-#21 wallet arm validates existence via the `accounts` table and 404s unknown values. **P2 — Mixed-case `--parent` wallets fail approval** EIP-55 checksummed addresses travel verbatim through the old fast path, but the mock backend stores wallets in lowercase. `parent_wallet` equality check at approval time compared the mixed-case input to the stored lowercase value, so valid checksummed addresses timed out. Normalize raw wallets to lowercase (`to_ascii_lowercase`) before sending to `/identity/resolve`; the backend's response comes back in canonical lowercase, so the stored `parent_wallet` matches what subsequent approve calls compare against. The old `looks_like_raw_wallet` helper is kept — it now just selects between `identity_type="wallet"` (with lowercase normalization) and `identity_type="alias"` (verbatim). Aliases with reserved characters still percent-encode via reqwest's `.query()` builder. Test: cargo test -p agentkeys-daemon --test pair_tests -- --test-threads=1 pair_tests: 15 passed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ffdc908 commit 3314b39

1 file changed

Lines changed: 27 additions & 14 deletions

File tree

crates/agentkeys-daemon/src/main.rs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -244,9 +244,18 @@ fn looks_like_raw_wallet(s: &str) -> bool {
244244
}
245245

246246
/// Resolve `--parent` to a wallet address if set, returning `Ok(None)` when
247-
/// the flag is absent. Uses reqwest's `.query()` builder so aliases with
248-
/// reserved characters (`+`, `&`, `%`, spaces) are percent-encoded per
249-
/// RFC 3986 (codex PR #22 P2 — URL encoding).
247+
/// the flag is absent.
248+
///
249+
/// Uses reqwest's `.query()` builder so aliases with reserved characters
250+
/// (`+`, `&`, `%`, spaces) are percent-encoded per RFC 3986 (codex PR #22
251+
/// v1 P2 — URL encoding).
252+
///
253+
/// All inputs — raw wallets included — go through `/identity/resolve` so
254+
/// the backend can validate existence before the daemon opens a pair
255+
/// request. Raw `0x...` wallets are normalized to lowercase first, which
256+
/// matches the canonical form the backend stores; mixed-case checksummed
257+
/// addresses therefore resolve cleanly instead of timing out at approval
258+
/// (codex PR #22 v2 P2 — unknown wallet accepted + case mismatch).
250259
async fn resolve_parent_if_set(
251260
backend_url: &str,
252261
parent: Option<&str>,
@@ -255,33 +264,37 @@ async fn resolve_parent_if_set(
255264
return Ok(None);
256265
};
257266

258-
// Fast path: strict 0x + 40 hex digits = literal wallet. Skip the HTTP
259-
// round-trip to avoid latency on the common case.
260-
if looks_like_raw_wallet(raw) {
261-
return Ok(Some(WalletAddress(raw.to_string())));
262-
}
267+
// Pick identity_type based on shape. Raw wallets get lowercased to
268+
// match the backend's canonical storage form.
269+
let (identity_type, identity_value) = if looks_like_raw_wallet(raw) {
270+
("wallet", raw.to_ascii_lowercase())
271+
} else {
272+
("alias", raw.to_string())
273+
};
263274

264-
// Alias / ENS-style / `0x-office`-style value → resolve via backend.
265275
let http = reqwest::Client::new();
266276
let resp = http
267277
.get(format!("{backend_url}/identity/resolve"))
268-
.query(&[("identity_type", "alias"), ("identity_value", raw)])
278+
.query(&[
279+
("identity_type", identity_type),
280+
("identity_value", identity_value.as_str()),
281+
])
269282
.send()
270283
.await
271-
.context("resolve parent alias: HTTP request failed")?;
284+
.context("resolve --parent: HTTP request failed")?;
272285
if !resp.status().is_success() {
273286
anyhow::bail!(
274-
"could not resolve --parent '{raw}': status={}",
287+
"could not resolve --parent '{raw}' (identity_type={identity_type}): status={}",
275288
resp.status()
276289
);
277290
}
278291
let body: serde_json::Value = resp
279292
.json()
280293
.await
281-
.context("resolve parent alias: JSON parse failed")?;
294+
.context("resolve --parent: JSON parse failed")?;
282295
let wallet_str = body["wallet_address"]
283296
.as_str()
284-
.ok_or_else(|| anyhow::anyhow!("resolve parent alias: missing wallet_address in response"))?
297+
.ok_or_else(|| anyhow::anyhow!("resolve --parent: missing wallet_address in response"))?
285298
.to_string();
286299
Ok(Some(WalletAddress(wallet_str)))
287300
}

0 commit comments

Comments
 (0)