feat(ironhub): install tools and skills from IronHub (CLI, agent tools, gateway, live catalog)#3737
feat(ironhub): install tools and skills from IronHub (CLI, agent tools, gateway, live catalog)#3737neo-sky wants to merge 7 commits into
Conversation
Adds a name-based install path that resolves a tool or skill against the
IronHub release manifest at github.com/nearai/ironhub/releases, downloads
the wasm + capabilities or SKILL.md, verifies the published SHA-256, and
writes into ~/.ironclaw/{tools,skills}/. Local-path install keeps working
for dev workflows. Manifest entry names, release tags, and post-write
artifact sizes are all validated before trusting any value used as a
filesystem path or URL component, so a poisoned manifest cannot escape
the install tree.
Renames the IronHub install surface from `ironclaw tool install <name>` (with path/hub autodetection) to a dedicated `ironclaw ironhub *` subcommand tree, mirroring the platform's brand identity. Adds four agent-callable builtin tools (ironhub_install/search/list/info) sharing the same HubInstaller code so the hosted agent at agent.near.ai can drive installs via chat. Installed tools hot-load into the live ToolRegistry via ExtensionManager::ensure_extension_ready, skills via SkillRegistry::reload, both visible on the next agent turn without restart.
Tightens the four tool schemas with regex patterns, additionalProperties: false, and length caps so the dispatcher rejects LLM injection before execute. Adds rate_limit_config (6/min, 30/hour) to ironhub_install. Pins five dispatch-level injection tests plus a regression test asserting IronHub skills land in the Installed bucket, not the Trusted user_dir.
Wires the four IronHub agent tools to gateway HTTP routes so a web UI can call install/search/list/info via REST. Install requires AdminUser (matches AdminUser docstring guidance for extension/skill installation); read-only routes accept any AuthenticatedUser. All routes dispatch through ToolDispatcher so the schema regex, additionalProperties:false, rate limit, and audit ActionRecord all fire identically to chat-driven installs. DTOs use serde(deny_unknown_fields) so the axum extractor rejects unknown params before they bypass the dispatcher gate. Nine handler tests cover auth-required, admin-required, schema injection (path traversal, unknown field), and happy-path dispatch via a stub tool.
Per-user HMAC signing keys make hub install links verifiable by the agent. Adds signing-key CRUD + verify-intent endpoints, a #/install/<tool> deep-link confirm flow, and a Settings card to manage the key. Hub-side button is a separate nearai/ironhub PR.
Points the default manifest at the hub's merged catalog endpoint and tags every entry with a provenance tier, gating unverified community installs behind an explicit ack. Adds ironhub_remove and keeps the pinned release-tag path as a GitHub escape hatch. SHA-256 verify and the closed host allowlist are unchanged, with hub.ironclaw.com the only added host.
There was a problem hiding this comment.
Code Review
This pull request introduces the IronHub integration, enabling users to browse and install WASM tools and skills directly from the IronHub catalog via both the CLI and a new web-based interface. The implementation includes new API handlers for installation, search, and signing key management, alongside a dedicated installer registry and frontend components. Feedback highlights several critical areas for improvement: a potential Denial of Service vulnerability in artifact downloading due to missing size limits, the need for atomic file operations during tool installation to prevent inconsistent states, and logic inconsistencies in name validation across different modules. Additionally, the review identifies issues with error handling during skill registry reloads and misleading error mapping in the extension manager.
|
|
||
| /// Download an artifact from a URL. | ||
| async fn download_artifact(url: &str) -> Result<bytes::Bytes, RegistryError> { | ||
| pub(crate) async fn download_artifact(url: &str) -> Result<bytes::Bytes, RegistryError> { |
There was a problem hiding this comment.
The download_artifact function downloads the entire response body into memory without a size limit. This can be exploited as a Denial of Service (DoS) vector. Consider adding a max_size parameter (using u64 to ensure infallible conversions from usize on all platforms) and using response.bytes_stream() with a limit to prevent unbounded memory consumption.
References
- Prefer using u64 for length prefixes and size limits to ensure that conversions from usize are infallible, avoiding panics and potential DoS vectors.
| fs::write(&target_wasm, wasm_bytes) | ||
| .await | ||
| .map_err(RegistryError::Io)?; | ||
| confirm_written_size(&target_wasm, wasm_bytes.len()).await?; | ||
| fs::write(&target_caps, caps_bytes) | ||
| .await | ||
| .map_err(RegistryError::Io)?; | ||
| confirm_written_size(&target_caps, caps_bytes.len()).await?; |
There was a problem hiding this comment.
The installation of WASM tools is not atomic. If writing the capabilities file fails after the WASM file has been written, the system is left in an inconsistent state. Consider writing to temporary files and using tokio::fs::rename to ensure both files are updated atomically, adhering to async I/O practices. If implementing a cleanup mechanism on failure, ensure any secondary errors are logged as warnings so the primary error is preserved and returned.
References
- In async functions, use asynchronous I/O operations like tokio::fs to avoid blocking the runtime.
- When a primary operation fails and a subsequent cleanup fails, log the secondary failure as a warning and return the original primary error.
| if !(first.is_ascii_lowercase() || first.is_ascii_digit()) { | ||
| return false; | ||
| } | ||
| chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') |
There was a problem hiding this comment.
There is an inconsistency between this validation and validate_hub_artifact_name in src/registry/hub_installer.rs. This function only allows hyphens, while the registry validation also allows underscores. Aligning these prevents unexpected rejections. Additionally, validation at this public API boundary must strictly check for path traversal characters (/, , .., \0) to prevent security vulnerabilities.
References
- Path traversal characters in extension names must be validated at the public API boundary for all operations to prevent security vulnerabilities.
| ); | ||
| poison.into_inner() | ||
| }); | ||
| handle.block_on(guard.reload()); |
| .extension_manager | ||
| .list(Some(ExtensionKind::WasmTool), false, &ctx.user_id) | ||
| .await | ||
| .map_err(|_| catalog_unavailable())? |
There was a problem hiding this comment.
| if store | ||
| .exists(&user.user_id, IRONHUB_SIGNING_KEY_NAME) | ||
| .await | ||
| .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? | ||
| { | ||
| store | ||
| .delete(&user.user_id, IRONHUB_SIGNING_KEY_NAME) | ||
| .await | ||
| .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; | ||
| } |
There was a problem hiding this comment.
The signing key regeneration process is not atomic. If the delete operation succeeds but the subsequent create operation fails, the user will be left without any signing key. While not critical, it forces the user to manually retry. Consider if the SecretsStore supports an atomic replacement or ensure the old key is preserved until the new one is successfully created.
…dator parity) Stream artifact downloads with a hard per-call size cap so an oversized response can't exhaust memory, clean up the wasm if the capabilities write fails, align the by-name validator with the registry's underscore charset, preserve the previous signing key if regeneration fails, and use a local error class when the remove read-back can't reach the extension store. Adds regression tests for the size cap, partial-artifact cleanup, and validator parity.
|
Thanks for the review. Addressed in
On the fmt and clippy (all features) are clean and the targeted tests pass. |
Summary
ironclaw ironhub install/search/list/info) and as agent-callable tools, so a running agent can install at runtime instead of only at build time.tools.jsonplus the live moderated Iliad catalog) with a per-entry provenance tier; the pinned--release-tagpath is preserved as a GitHub escape hatch.Change Type
Linked Issue
None. This is net-new work brought to the team directly; happy to file a tracking issue if the team prefers one on record.
Validation
cargo fmt --all -- --checkcargo clippy --all --benches --tests --examples --all-features -- -D warnings(zero warnings)cargo build/cargo check --no-default-features --features libsqlcargo test --lib(5710 passing), including the IronHub installer, provenance, host-allowlist, dispatch, and gateway handler suitescargo test --features integration— N/A; no database-backed or integration behaviour changed on this sideironclawbinary fetching, SHA-256 verifying, and installing through the genuine host allowlist and TLS pathSecurity Impact
Yes. New outbound fetches (catalog manifest + artifacts) are constrained to a closed host allowlist (
hub.ironclaw.complus GitHub release infrastructure), HTTPS-only, IP literals rejected. Every artifact is SHA-256-verified before write. Agent-callable install tools use strict parameter schemas (regex +additionalProperties:false), per-tool rate limiting, and an approval gate; unverified community content requires an explicit acknowledgement. The deep-link path is HMAC-verified with a short replay window; signing keys are per-user in the secrets store. No credential is exposed to tool processes.Database Impact
None. No migrations or schema changes on the ironclaw side.
Blast Radius
Registry installer, tool registry/dispatch, and the gateway web handlers + embedded SPA. The feature is additive (new tools/endpoints/surface). The one behavioural change is the default IronHub manifest URL; the pinned-release path is unchanged.
Rollback Plan
Revert the branch. The change is additive with no migrations and no persisted state, so reverting removes the new tools, endpoints, and the default-URL change cleanly with no data cleanup.
Review Follow-Through
The HMAC deep-link receiver is complete and tested on this side; the matching IronHub-side "Install to Agent" sender and profile key field is a separate follow-up in the IronHub repo and not part of this PR. Iliad-sourced entries are intentionally surfaced as
verified(notofficial);officialis reserved for GitHub-release artifacts. Happy to adjust scope or split commits if that helps review.Review track: C (security/runtime)