Problem
crates/agentkeys-mock-server/src/auth.rs::is_owner_of only walks one parent_token hop:
SELECT 1 FROM sessions AS child
WHERE child.wallet_address = ?1
AND child.parent_token IN (
SELECT token FROM sessions WHERE wallet_address = ?2
)
LIMIT 1
So if the session tree is master → child → grandchild, calling any of read_credential / store_credential / list_credentials / teardown_agent from the master session against the grandchild wallet returns 403 DENIED, even though the master is the transitive owner.
Why this surfaced now
Codex flagged it on PR #19 (third pass) because the new agentkeys run master-session path always calls /credential/list first when scope = None. So a master invoking agentkeys run <grandchild-wallet> -- ... now fails fast rather than only on the eventual /credential/read (which had the same limit but was never exercised against grandchildren by anyone).
Proper fix (out of scope for #19)
Make is_owner_of recursive — either with an iterative SQL CTE or a small loop in Rust that walks the parent_token chain until a session owned by caller_wallet is found or the chain terminates. Add an integration test that builds a 3-level nested session tree and asserts the master can read/store/list/teardown against the grandchild.
Touches the security model for all four credential handlers, so this needs its own PR and review pass — not a drive-by fix on #19.
References
Problem
crates/agentkeys-mock-server/src/auth.rs::is_owner_ofonly walks oneparent_tokenhop:So if the session tree is
master → child → grandchild, calling any ofread_credential/store_credential/list_credentials/teardown_agentfrom the master session against the grandchild wallet returns403 DENIED, even though the master is the transitive owner.Why this surfaced now
Codex flagged it on PR #19 (third pass) because the new
agentkeys runmaster-session path always calls/credential/listfirst whenscope = None. So a master invokingagentkeys run <grandchild-wallet> -- ...now fails fast rather than only on the eventual/credential/read(which had the same limit but was never exercised against grandchildren by anyone).Proper fix (out of scope for #19)
Make
is_owner_ofrecursive — either with an iterative SQL CTE or a small loop in Rust that walks the parent_token chain until a session owned bycaller_walletis found or the chain terminates. Add an integration test that builds a 3-level nested session tree and asserts the master can read/store/list/teardown against the grandchild.Touches the security model for all four credential handlers, so this needs its own PR and review pass — not a drive-by fix on #19.
References
crates/agentkeys-mock-server/src/auth.rs:51-70agentkeys runfor master sessions +--envoverride #19 v3agentkeys runfor master sessions +--envoverride #19 (created in commit1a3271e— stage 1)