Skip to content

Add nexus-tool-builder skill + nexus-tool-auditor agent - #31

Open
mike-hanono wants to merge 4 commits into
mainfrom
add-nexus-tool-builder-skill
Open

Add nexus-tool-builder skill + nexus-tool-auditor agent#31
mike-hanono wants to merge 4 commits into
mainfrom
add-nexus-tool-builder-skill

Conversation

@mike-hanono

Copy link
Copy Markdown

Summary

Adds a shareable Claude Code skill that automates Nexus tool creation, plus an auditor sub-agent that scores generated crates against an authoritative security checklist. Everything is additive under .claude/ — no existing files touched, no behavior change to current tools.

This is PR 1 of 2. PR 2 (payments-stripe tool) is stacked on top of this one and exercises the skill end-to-end.

What this adds

  • .claude/skills/nexus-tool-builder/

    • SKILL.md — entry point with the full skill workflow (off-chain Rust and on-chain Move paths, pre-flight checks, hard rules).
    • reference/ — authoritative material the skill loads on demand:
      • architecture.md — canonical tool-crate layout
      • interface.mdNexusTool trait + bootstrap! semantics
      • style-guide.md — port naming, error variants, flatness rules
      • onchain-tools.md — Move-tool variant (ProofOfUID, witness, TaggedOutput)
      • hosting-options.md — GCP Cloud Run as v1 default; DePIN deferred
      • security-checklist.md — authoritative C1–C11 / H1–H10 / M1–M6 / Move C-M1–C-M6 / H-M1–H-M6 / cross-cutting checks
      • faq.md — common pitfalls (top-level oneOf, dedup paths, workspace deps)
    • templates/rust/ — Cargo.toml, README, main.rs, error.rs, stateless client.rs, endpoint.rs, models.rs, tools_mod.rs
    • templates/move/ — Move.toml + sources/tool.move.tmpl
    • templates/deploy/ — Dockerfile, Cloud Run testnet/mainnet YAML, register.sh, GitHub Actions workflows for testnet (push to main) and mainnet (tag, gated on testnet green)
    • scripts/new_tool.sh — wraps nexus tool new, moves into tools/
    • scripts/verify.shcargo check / clippy -D warnings / test / fmt --check / live /health + /meta smoke
    • scripts/audit.sh — mechanical static + dynamic checks (cargo toolchain, greps for unwrap/expect/panic in non-test code, env-var credential reads, statefulness sniff, sk_live_ leak, Cloud Run secret mounts that don't match the allowed set)
  • .claude/agents/nexus-tool-auditor.md

    • Sub-agent triggered after verify.sh (or invoked manually with Agent({ subagent_type: "nexus-tool-auditor", ... })).
    • Reads reference/security-checklist.md as source of truth.
    • For off-chain: runs audit.sh, fuzzes /invoke with malformed payloads, optionally backtests against fixtures/, writes tools/<crate>/AUDIT.md with severity-graded findings + sign-off recommendation.
    • For on-chain: also checks witness stamping on every code path, missing AdminCap guards, copyable witness, hardcoded addresses, gas-grief loops.
    • Refuses to mark mainnet-ready while any CRITICAL is open.

Credential model (baked into templates)

The skill's templates enforce the same per-request credential model used today by tools/llm-openai-chat-completion and tools/social-twitter: upstream API credentials live ONLY in the Input struct, never in env vars, never on disk, never in Cloud Run secrets. The Cloud Run YAML templates mount only the Tool's own Ed25519 signing key and the allowed-leaders list. The audit script's checks C7, C9, C10, C11 enforce this mechanically.

The relevant section of the security checklist:

| C7 | Tool source MUST NOT read any upstream-API credential from env vars, disk, or any persistent store. Credentials live ONLY in Input struct fields. |
| C8 | Tool MUST NOT log, print, format-debug, or include in Output::Err::reason any value of a credential field on Input. |
| C9 | Tool MUST be stateless across invocations. |
| C10 | Tool MUST NOT mount upstream API keys as Cloud Run secrets. |
| C11 | The crate's README.md and any example DAG JSON MUST NOT contain credential-shaped values. |

How a contributor uses this

# inside nexus-tools/ checkout
# (skill auto-loads in any Claude Code session run from this repo)

# from your AI assistant:
> create a Nexus tool for the Stripe API

The skill walks the contributor through endpoint discovery, generates the crate per the templates above, runs verify.sh, then calls the auditor. PR 2 of 2 is the artifact of running this against Stripe.

Test plan

  • Skill files lint cleanly (bash -n, YAML parse, JSON parse) — verified locally
  • SKILL.md frontmatter is valid YAML
  • Templates render against the live nexus tool new --template rust scaffold (verified by PR 2)
  • scripts/audit.sh produces a clean run on a known-good crate (verified by PR 2)
  • After merge, any team member can trigger the skill in Claude Code with no extra setup

🤖 Generated with Claude Code

The skill automates Nexus Tool creation from an API spec, wrapping the
official `nexus tool new` CLI and rendering canonical Talus tool
templates for both off-chain (Rust + nexus-toolkit) and on-chain
(Sui Move) tools. Each generated crate ships with:

- A stateless reqwest client (no env-var credential reads — credentials
  arrive in the per-request Input struct, matching the convention used
  by tools/llm-openai-chat-completion and tools/social-twitter).
- Typed error envelope mapping (Stripe-style and generic).
- mockito-backed unit tests per endpoint.
- GCP Cloud Run deploy YAML for testnet and mainnet, with secret
  mounts limited to the Tool's own signing key + allowed-leaders list.
- GitHub Actions workflows that build, deploy, smoke-test, then
  idempotently register the tool with Nexus.

The nexus-tool-auditor agent runs after verify.sh and produces an
AUDIT.md scored against `reference/security-checklist.md`. The
checklist covers credential handling (C7-C11), statelessness (C9),
Cloud Run secret hygiene (C10), and Stripe-specific error coverage
(H8-H10). The scripts/audit.sh script runs the mechanical greps.

The skill ships committed to .claude/ so every contributor gets it on
git clone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- chmod +x .claude/skills/nexus-tool-builder/templates/deploy/register.sh.tmpl
  (12-executable-scripts: any file with a shebang on line 1 must be
  executable in the repo)
- Convert numbered list items to all `1.` in SKILL.md, hosting-options.md
  per `MD029` style "one" (markdownlint config sets this)
- Add `text` language tag to three bare fenced code blocks (MD040)
- Add blank lines around lists in nexus-tool-auditor.md (MD032)
- Rephrase SKILL.md verify section to avoid a wrapped `+` that
  markdownlint interpreted as a bullet (MD004)

Verified locally:
  npx markdownlint-cli2@0.18.1 '.claude/**/*.md' --config .markdownlint.json
    Summary: 0 error(s)
  for f in $(git ls-files); do …shebang+exec check; done
    clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mike-hanono
mike-hanono requested review from loverdos and tuky191 May 15, 2026 18:45
@tuky191
tuky191 requested a review from a team May 15, 2026 18:46
@loverdos
loverdos self-requested a review May 22, 2026 06:42
Realigns the skill, its templates, scripts, and the nexus-tool-auditor
agent with two changes that landed on `main` after PR #31 was opened:

1. PR #33 restructured the repo: tools/ → offchain/tools/, single shared
   offchain/Dockerfile, five reusable workflows
   (.github/workflows/offchain-tools.{discover,prepare,deploy,register,readiness}.yml)
   that key off each crate's tools.json + build.rs. No per-tool Dockerfile,
   Cloud Run YAML, register.sh, or workflow file needed anymore.

2. PR #27/#28 (storage-walrus) and PR #29 (memory-memwal) established the
   env-var credential pattern: upstream API secrets are read once at
   startup via from_env(), wrapped in zeroize::Zeroizing<String>, with a
   hand-written Debug that prints `<redacted>`. NEVER put credentials on
   Input — tool inputs flow through the Nexus DAG on Sui as plaintext.

Skill changes:

- templates/deploy/ — DELETED (whole dir). Per-tool Dockerfile / Cloud
  Run YAML / GitHub workflow / register.sh / allowed_leaders.json are
  superseded by the shared offchain/Dockerfile + offchain-tools.* workflows.
- templates/rust/tools.json.tmpl + build.rs.tmpl + env.example.tmpl — NEW.
  build.rs.tmpl is a verbatim copy of offchain/tools/memory-memwal/build.rs
  (validates [[bin]].name == tools.json.command, threads TOOL_FQN_VERSION
  via the Docker build-arg).
- templates/rust/client.rs.tmpl — flipped from per-call .with_auth() to
  startup-env from_env(): Zeroizing<String> bearer, hand-written Debug,
  shared HTTP pool via OnceLock, #[cfg(test)] for_testing() constructor.
- templates/rust/endpoint.rs.tmpl — dropped api_key from Input; clients
  are authed once at startup; FQNs threaded through
  env!("TOOL_FQN_VERSION").
- templates/rust/main.rs.tmpl — adopted memwal's pattern: explicit
  multi-thread tokio runtime, env_logger init, --meta short-circuit (so
  CI's prepare step can pull /meta from an image with no env),
  load_dotenv_if_present() + validate_credentials_at_startup() before
  bootstrap!.
- templates/rust/Cargo.toml.tmpl — added [[bin]], [build-dependencies],
  workspace deps for dotenvy / env_logger / log / anyhow / zeroize.
- scripts/new_tool.sh — scaffolds at offchain/tools/<crate>/, drops in
  tools.json + build.rs from templates, no more deploy/ dir.
- scripts/verify.sh — discovers paths via /meta (no paths.json), runs
  cargo against the offchain workspace, boots binary with a fake env var
  so validate_credentials_at_startup passes.
- scripts/audit.sh — new CRITICAL check `static:input-credential` (C1)
  refuses crates whose Input struct contains credential-shaped fields
  (api_key, *_secret, *_token, bearer*, password, private_key,
  access_token, consumer_key, consumer_secret, client_secret — minus a
  whitelist for idempotency_key, pagination_token, etc.). Added shape
  checks for tools.json + build.rs presence, FQN-versioning check, and
  legacy-plumbing detector. Paths updated to offchain/.

Reference + agent updates:

- reference/security-checklist.md — promoted no-secrets-in-Input to C1.
  Renumbered everything else. Rewrote C7 to require startup-env reads.
  Updated C9 statefulness rule to permit startup-built Arc-wrapped
  client state.
- reference/architecture.md — rewrote layout diagram; added tools.json
  + build.rs siblings; deploy/ removed; memwal as canonical example.
- reference/faq.md — dropped tools/.just entry; added entries for
  tools.json, BLOCKED_TOOLS, secret-mounting model, FQN versioning via
  TOOL_FQN_VERSION.
- reference/hosting-options.md — clarified that the skill emits no
  per-tool deploy plumbing; documented the secret-naming convention
  (nexus-tools-<tool>-v<ver>-…) and the operator-owned upstream-key
  secretKeyRef binding.
- reference/style-guide.md — updated the `api_key` example (now a
  forbidden Input field; replaced with `payment_intent_id`).
- reference/interface.md, reference/onchain-tools.md — no semantic
  changes (table-separator format only).
- SKILL.md — rewrote workflow to reference offchain/tools/, tools.json
  + build.rs, no deploy artifacts emitted, env-var credential rule.
  Hard rules section: no credentials in Input (C1) at top.
- agents/nexus-tool-auditor.md — added the hard rule: any
  credential-shaped Input field is an automatic CRITICAL that blocks
  ready-for-testnet. Paths updated to offchain/tools/.
- .markdownlint config: no change; fixed compact-table separators in
  the touched markdown files (MD060).

Verified locally:
- bash .claude/skills/nexus-tool-builder/scripts/audit.sh off-chain social-twitter
  → FAIL static:input-credential (catches bearer_token on Input across 5 files)
- bash .claude/skills/nexus-tool-builder/scripts/audit.sh off-chain memory-memwal
  → PASS static:input-credential (clean Input, env-var pattern)
- npx markdownlint-cli2 '**/*.md' '!.git/' --config .markdownlint.json
  → 0 errors across 27 files

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants