feat(registry): add --security flag to include security info in JSON output#9364
feat(registry): add --security flag to include security info in JSON output#9364
Conversation
…output Consumers that want to catalog per-tool security features (e.g. the mise-versions sync pipeline) currently have to shell out to `mise tool <name> --json` once per tool because `mise registry --json` omits the `security` field. At ~1000 tools that's ~1000 mise process spawns. Add a `--security` flag (JSON-only, via `requires = "json"`) that resolves each backend's `security_info()` and emits the de-duplicated set of `SecurityFeature` values under a new `security` array in the output. The field is omitted via `skip_serializing_if = "Vec::is_empty"` when there are no features, so tools without security info produce the same bytes as before. The flag is opt-in because `security_info()` for aqua backends reads the per-package aqua registry, which may fetch from GitHub on cold cache — keeping the default `--json` path at its current ~50 ms. Includes updated e2e tests, regenerated usage spec, and regenerated docs.
Greptile SummaryAdds Confidence Score: 5/5Safe to merge — changes are additive, backward-compatible, and well-tested. All P1 issues from prior review rounds have been addressed. The backend lookup correctly uses BackendArg::from(full) avoiding the TOOLS-cache collision. The JoinSet+semaphore pattern correctly parallelises security fetches. The default JSON output path is untouched. No remaining blocking findings. No files require special attention. Important Files Changed
Sequence DiagramsequenceDiagram
participant CLI as mise registry --json --security
participant DJ as display_json()
participant JS as JoinSet (N=jobs)
participant TO as to_output(tool, true)
participant CS as collect_security(backends)
participant BE as backend::arg_to_backend
CLI->>DJ: display_json().await
DJ->>DJ: collect filtered RegistryToolOutputArgs
loop for each tool (semaphore-gated)
DJ->>JS: jset.spawn(to_output(tool, true))
end
loop join_next until empty
JS->>TO: await
TO->>CS: collect_security(&backends)
loop for each backend string
CS->>BE: BackendArg::from(full) → arg_to_backend
BE-->>CS: ABackend
CS->>BE: backend.security_info().await
BE-->>CS: Vec<SecurityFeature>
end
CS-->>TO: deduplicated Vec<SecurityFeature>
TO-->>JS: RegistryToolOutput
JS-->>DJ: result
end
DJ->>DJ: sort outputs by short name
DJ-->>CLI: JSON printed
Reviews (4): Last reviewed commit: "fix(registry): limit security metadata c..." | Re-trigger Greptile |
There was a problem hiding this comment.
Code Review
This pull request introduces a new --security flag to the registry command, enabling the inclusion of security features for tool backends in JSON output. The implementation includes documentation updates, E2E tests, and logic to aggregate and de-duplicate security features across multiple backends. Review feedback identifies a logic error in collect_security where a global cache prevents correct data retrieval for tools with multiple backends, and suggests using backend::arg_to_backend instead. Additionally, there are recommendations to parallelize the sequential tool processing to improve performance and to correct a misleading comment regarding lock management.
| let ba = BackendArg::new(short.to_string(), Some(full.to_string())); | ||
| let Some(backend) = backend::get(&ba) else { | ||
| continue; | ||
| }; |
There was a problem hiding this comment.
There is a logic error when a tool has multiple backends. backend::get(&ba) uses a global cache keyed by the shorthand name (ba.short). If a tool like gh has both aqua and asdf backends, the first one encountered will be cached, and backend::get will return that same cached backend for all subsequent iterations of the loop. This causes security info to be collected from the same backend multiple times while skipping others. Using backend::arg_to_backend directly avoids this caching issue.
| let ba = BackendArg::new(short.to_string(), Some(full.to_string())); | |
| let Some(backend) = backend::get(&ba) else { | |
| continue; | |
| }; | |
| let ba = BackendArg::new(short.to_string(), Some(full.to_string())); | |
| let Some(backend) = backend::arg_to_backend(ba) else { | |
| continue; | |
| }; |
| for (short, rt) in tools { | ||
| outputs.push(self.to_output(short, rt).await); | ||
| } |
There was a problem hiding this comment.
This loop processes ~1000 tools sequentially. When --security is enabled, each iteration calls to_output(...).await, which can involve network or file I/O (e.g., for aqua backends). As noted in the PR description, this results in a ~30s execution time. Consider parallelizing these requests using tokio::task::JoinSet or futures::future::join_all to significantly improve performance when the --security flag is used.
| // Collect (short, RegistryTool) pairs first so we can drop the | ||
| // REGISTRY lock borrow before hitting the async per-tool loop. |
There was a problem hiding this comment.
The comment about dropping the REGISTRY lock borrow is misleading because REGISTRY is a static Lazy<BTreeMap> and does not appear to be wrapped in a lock (like a Mutex or RwLock). The collect() call is likely unnecessary for safety if the loop remains sequential, as the references yielded by the iterator are 'static. However, it would be necessary if you decide to parallelize the loop as suggested.
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.20 x -- echo |
23.9 ± 0.6 | 22.9 | 26.5 | 1.00 |
mise x -- echo |
24.1 ± 0.8 | 23.2 | 33.3 | 1.01 ± 0.04 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.20 env |
23.0 ± 0.9 | 22.2 | 37.1 | 1.00 |
mise env |
23.5 ± 0.5 | 22.8 | 27.8 | 1.03 ± 0.05 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.20 hook-env |
23.6 ± 0.3 | 23.0 | 25.9 | 1.00 |
mise hook-env |
24.1 ± 0.3 | 23.4 | 25.2 | 1.02 ± 0.02 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.20 ls |
21.3 ± 0.7 | 20.7 | 32.1 | 1.00 |
mise ls |
21.9 ± 0.3 | 21.3 | 23.2 | 1.03 ± 0.04 |
xtasks/test/perf
| Command | mise-2026.4.20 | mise | Variance |
|---|---|---|---|
| install (cached) | 179ms | 172ms | +4% |
| ls (cached) | 81ms | 85ms | -4% |
| bin-paths (cached) | 87ms | 87ms | +0% |
| task-ls (cached) | 816ms | 806ms | +1% |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7448fa9. Configure here.
### 🚀 Features - **(registry)** add --security flag to include security info in JSON output by @jdx in [#9364](#9364) ### 🐛 Bug Fixes - **(config)** limit resolved backend opts to aliases by @risu729 in [#9315](#9315) - **(docs)** stack banner message and link on mobile by @jdx in [#9362](#9362) - **(github)** prefer shortest asset name as tiebreaker in auto-detection by @jdx in [#9361](#9361) - **(java)** newer zulu versions use a different directory structure by @roele in [#9365](#9365) - **(prune)** respect tracked lockfiles by @jdx in [#9373](#9373) - **(task)** skip tool install for missing naked tasks by @jdx in [#9374](#9374) - **(trust)** add untrust command by @jdx in [#9370](#9370) - fix - flux-operator-mcp aqua path by @monotek in [#9357](#9357) ### 📚 Documentation - update ruby compile msg by @fladson in [#9338](#9338) ### 📦️ Dependency Updates - update ubuntu docker tag to v26 by @renovate[bot] in [#9347](#9347) - update ghcr.io/jdx/mise:deb docker digest to 1af5a69 by @renovate[bot] in [#9352](#9352) - update taiki-e/install-action digest to 787505c by @renovate[bot] in [#9354](#9354) - update ghcr.io/jdx/mise:rpm docker digest to 7015ff3 by @renovate[bot] in [#9353](#9353) - update ghcr.io/jdx/mise:copr docker digest to da63a0f by @renovate[bot] in [#9351](#9351) - update ghcr.io/jdx/mise:alpine docker digest to 461700f by @renovate[bot] in [#9350](#9350) - bump communique 1.0.3 → 1.0.4 by @jdx in [#9378](#9378) ### 📦 Registry - remove openshift-install by @jdx in [#9372](#9372) - remove go-sdk by @jdx in [#9371](#9371) ### Chore - **(npm-publish)** use aube publish instead of npm publish by @jdx in [#9328](#9328) ### New Contributors - @fladson made their first contribution in [#9338](#9338)
…urity The `Sync tools to D1` step previously shelled out to `mise tool <name> --json` once per tool (977 sequential invocations) just to assemble each tool's GitHub slug, description, and security list. That dominated the step at ~4 of its ~5 min wall time. This PR collapses the whole metadata gather into one mise call: - `mise registry --json --security` (added upstream in mise v2026.4.22 via jdx/mise#9364) returns `{short, backends, description, aliases, security}` for every tool in one JSON array. No per-tool shell-outs, no parallel worker pool, no `MISE_TOOL_CONCURRENCY` env knob. - Falls back to `mise registry --json` (without security) if the installed mise predates the flag, so the rest of the manifest still syncs. Local timing on 978 tools: manifest phase ~30 s end-to-end, vs. ~240 s before any of this work. Code is also dramatically simpler — drops the `getSecurity` / `fetchSecurityMap` worker plumbing and the `execFile` + `promisify` async machinery. Bonus bug fix preserved from the prior version of this PR: the original script read `description` from `mise tool --json`, which almost always returned null. With `mise registry --json` providing descriptions, the count jumps from ~17 tools-with-descriptions to ~975. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…urity The `Sync tools to D1` step previously shelled out to `mise tool <name> --json` once per tool (977 sequential invocations) just to assemble each tool's GitHub slug, description, and security list. That dominated the step at ~4 of its ~5 min wall time. This PR collapses the whole metadata gather into one mise call: - `mise registry --json --security` (added upstream in mise v2026.4.22 via jdx/mise#9364) returns `{short, backends, description, aliases, security}` for every tool in one JSON array. No per-tool shell-outs, no parallel worker pool, no `MISE_TOOL_CONCURRENCY` env knob. - Falls back to `mise registry --json` (without security) if the installed mise predates the flag, so the rest of the manifest still syncs. Local timing on 978 tools: manifest phase ~30 s end-to-end, vs. ~240 s before any of this work. Code is also dramatically simpler — drops the `getSecurity` / `fetchSecurityMap` worker plumbing and the `execFile` + `promisify` async machinery. Bonus bug fix preserved from the prior version of this PR: the original script read `description` from `mise tool --json`, which almost always returned null. With `mise registry --json` providing descriptions, the count jumps from ~17 tools-with-descriptions to ~975. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…urity (#130) ## Summary - Replace 977 sequential `mise tool <name> --json` shell-outs (~4 min) with a single `mise registry --json --security` call (~30 s end-to-end for the entire manifest phase). The `--security` flag landed upstream in mise v2026.4.22 via [jdx/mise#9364](jdx/mise#9364) and returns backends + description + security in one JSON array. - Drop the now-unneeded `MISE_TOOL_CONCURRENCY` env knob, the `getSecurity` / `fetchSecurityMap` worker pool, and the `execFile` + `promisify` async plumbing. - Add a graceful fallback to `mise registry --json` (no `--security`) for any environment running pre-4.22 mise — the rest of the manifest still syncs, just without security info. ## Why The `Sync tools to D1` step ran for ~5 min every 30 min in the update workflow. Profiling showed ~4 min was spent in 977 sequential mise process spawns just to read each tool's `security` array (and a `description` that was almost always `null` from `mise tool --json`). mise v2026.4.22 exposes everything we need on `mise registry --json --security`, so the script no longer has any reason to do per-tool work. ## Measurement Local, 978 tools (current registry size): - Before this branch: ~240 s in the manifest phase. - After: ~30 s — the registry call itself is most of that time, file I/O and TOML parsing are the remainder. In CI the relative speedup will be even larger since the previous design paid 977× the process-spawn + mise-startup cost. ## Bonus bug fix The previous code read `description` from `mise tool --json`, which almost always returns `null`. Pulling descriptions from `mise registry --json` instead bumps tools-with-descriptions from **17** to **975** locally — purely a side effect of using the right command. ## Test plan - [x] `bun run test` — 42 tests pass - [x] `prettier --check` + `tsc --noEmit` clean - [x] Local dry run with v2026.4.22 mise: manifest builds in ~30 s, 845 tools with GitHub, 975 with description, 978 with backends, 184 with package URLs - [x] Local dry run with a wrapper that rejects `--security` (simulating older mise): warning printed, fallback path runs, manifest still builds - [ ] First post-merge scheduled run on `main` — verify D1 sync step duration drops from ~5 min → well under 1 min, and `Upserted: ~977 / Errors: 0` in the result ## Notes - The workflow already installs latest mise via `curl https://mise.run | sh` after the `mise-action` step, so CI will have v2026.4.22+ on every run. - Followups not in this PR: kill the second `mise ls-remote --json` call in `update.sh`'s `generate_toml_file` (different rate-limit issue, separate fix). 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Moderate risk because it changes how tool metadata (backends/description/security) is sourced and relies on `mise registry --json --security` output/alias handling; failures would impact the D1 sync payload, though a fallback path is included for older `mise`. > > **Overview** > Speeds up the D1 tool-manifest build by replacing per-tool `mise tool <name> --json` shell-outs with a single `mise registry --json --security` call that provides `backends`, `description`, and `security` for all tools at once. > > Adds a compatibility fallback to `mise registry --json` when `--security` is unsupported, and preserves prior alias resolution by mapping registry `aliases` to the same metadata entry. Metadata derivation is adjusted to pull GitHub/repo URLs from registry `backends` (or `owner/repo` tool names) while keeping manual overrides as highest priority. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 6e28e7d. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>

Summary
--securityflag tomise registry(JSON-only, enforced viarequires = "json") that includes asecurityarray for each tool.backend.security_info().await, then de-duplicated by variant + payload.skip_serializing_if = "Vec::is_empty") when empty, so the default--jsonoutput is byte-identical for tools without security info.Why
Consumers that want a per-tool security catalog currently have to shell out to
mise tool <name> --jsononce per tool becausemise registry --jsondoesn't exposesecurity. That's ~1000 process spawns per rebuild for e.g. https://github.com/jdx/mise-versions. Having it in the registry JSON collapses that to a single call.Why opt-in
security_info()for aqua backends reads the per-package aqua registry, which can fetch from GitHub on cold cache. Keeping--securityopt-in preserves the defaultmise registry --jsonpath at its current ~50 ms so existing consumers aren't slowed down.Local timing (977 tools, warm cache, no rate limit):
mise registry --json— ~50 ms (unchanged)mise registry --json --security— ~30 sTest plan
cargo check/cargo clippy --bin mise --no-deps --testscleancargo fmt --checkcleane2e/cli/test_registry:mise registry bun --json --securityhas asecurityfieldmise registry bun --jsondoes notmise.usage.kdl+docs/cli/registry.mdtarget/debug/mise registry --json --securityproduces ~436/1003 tools with a populated security array on my machineNotes
to_outputanddisplay_jsonbecomeasyncsincesecurity_info()is async; single-name lookup and bulk listing both thread through the same path.Checksum { algorithm: Some("sha256") }collapse to one entry.Note
Medium Risk
Adds an opt-in code path that performs per-backend async
security_info()resolution (potentially network-bound) and parallelizes it, which could impact performance and error behavior when enabled.Overview
Adds a new
mise registry --securityflag (requires--json) that includes a per-toolsecurityarray in the JSON output, populated by collecting and de-duplicatingSecurityFeaturevalues across all of a tool’s backends.To support this,
registryJSON rendering is made async and, when--securityis set, resolves security info concurrently (bounded bySettings::jobs). Documentation/man pages/completions and e2e coverage are updated accordingly, andSecurityFeaturenow derivesPartialEq/Eqto enable structural de-duplication.Reviewed by Cursor Bugbot for commit 2944156. Bugbot is set up for automated code reviews on this repo. Configure here.