Stop handing a model the secrets the fleet withholds

Nine per-service packages registered a hanzo.tools entry point and sat in
hanzo-mcp's default dependencies, so installing it put them in a model's tool
list beside the unified tool and without its filter.

hanzo-tools-kms offered inject, which lists a path and reads every secret at it.
hanzo-tools-iam offered create_user, delete_user and the token and session reads.
cloud withholds all of them from an agent; these were a second way to the same
API, so the rule held on one path and not the other.

The auto-registration is withdrawn, not the packages: they stay importable for a
human at a CLI, which is where reading a secret is a legitimate act. An agent
reaches all eight through the unified tool, verified against the live document.

mpc keeps its registration because the fleet has no equivalent, and auth keeps
its because it reads the caller's own claims rather than another identity's.
This commit is contained in:
2026-08-30 20:08:02 -07:00
parent 42139d1849
commit eab5541a89
10 changed files with 81 additions and 33 deletions
+34 -10
View File
@@ -316,16 +316,40 @@ everything-allow for their synthetic specs, and the rule gets its own test
(`test_the_offer_rule_filters`) rather than the default being weakened to keep
them green.
**Ten hand-written packages still address the fleet by hand.**
`hanzo-tools-{billing,commerce,iam,ingress,kms,mpc,paas,s3,team}` are ~3,900 lines
that predate both this tool and the generated `hanzoai.cloud` client, and they
have drifted the same way: `paas` is `platform`, `storage` is `s3`, and the
commerce tool asked for plurals where the fleet publishes singulars under
`/v1/commerce/product/`. `hanzo-tools-auth` is NOT one of them — it is a login and
session tool that verifies tokens against the IdP's signing keys, which no
catalog replaces. `hanzo-tools-api` is not either: 8,028 of its lines are a
vendored APIs.guru directory, making it a generic any-API tool beside the
Hanzo-specific `hanzo_tool.py`.
**A model was handed secrets, and the fleet had already said no.** Nine per-service
packages registered a `hanzo.tools` entry point AND sat in `hanzo-mcp`'s default
dependencies, so `pip install hanzo-mcp` put them in a model's tool list — beside
the unified tool, and without its filter. What that offered:
| package | actions reaching a model | what the fleet offers an agent |
|---|---|---|
| `hanzo-tools-kms` | `list get set delete inject` | `get_kms_config`, `get_kms_health` — and nothing else |
| `hanzo-tools-iam` | `create_user delete_user update_user tokens sessions roles` | the reads; every one of those is withheld |
| `hanzo-tools-team` | `create_workspace delete_workspace invite` | the reads |
`inject` is the sharp one: it lists a path and reads every secret at it, in one
call. cloud's rule exists to prevent exactly this — "a tool is not projected when
its name discloses a bearer secret at any verb, or when a mutating verb acts on an
identity or authority object" — and these packages were a second, unfiltered way
to the same API, so the rule held on one path and not the other.
**The auto-registration is withdrawn, not the packages.** `iam`, `kms`, `team`,
`billing`, `commerce`, `ingress`, `paas` and `s3` no longer declare
`[project.entry-points."hanzo.tools"]` and no longer ship in `hanzo-mcp`. They
stay importable for a human at a CLI, which is where reading a secret is a
legitimate act. An agent reaches all eight through the unified `hanzo` tool, whose
surface is filtered — verified against the live document: iam 41 operations, kms
2, team 7, billing 35, commerce 5, ingress 18, platform 34, s3 6.
**Two stay registered, for reasons worth stating.** `hanzo-tools-mpc` has NO
equivalent in the fleet catalog, so unregistering it would remove capability with
nothing to replace it. `hanzo-tools-auth` is a login and session tool — it reads
the CALLER's own claims and verifies tokens against the IdP's signing keys, which
is not another identity's secret and which no catalog replaces.
**`paas` is the name the fleet left behind.** It is `platform` there, which is why
the retired alias mattered: a caller typing the old name was mapped off the live
surface rather than onto it.
## Key entry points
- `pkg/hanzoai/` — the client (`ApiClient`, `Configuration`, `*Api`) under `cloud/`, plus
+7 -7
View File
@@ -49,15 +49,15 @@ dependencies = [
"hanzo-tools-net>=0.1.0",
"hanzo-tools-agent>=0.3.1",
"hanzo-tools-api>=0.3.2", # 0.3.2 is the unified `hanzo` tool projected from cloud's OpenAPI registry
# The fleet is reached through the unified `hanzo` tool (hanzo-tools-api),
# which offers what cloud offers an AGENT — it withholds an operation whose
# name discloses a bearer secret, or whose mutating verb acts on identity or
# authority. The per-service packages that used to be installed here carried
# no such filter: hanzo-tools-kms handed a model `inject`, which reads every
# secret at a path, and hanzo-tools-iam handed it create_user/delete_user and
# the token and session reads. They remain installable for a human at a CLI.
"hanzo-tools-auth>=0.1.0",
"hanzo-tools-kms>=0.1.0",
"hanzo-tools-paas>=0.1.0",
"hanzo-tools-billing>=0.1.0",
"hanzo-tools-commerce>=0.1.0",
"hanzo-tools-iam>=0.1.0",
"hanzo-tools-ingress>=0.1.0",
"hanzo-tools-mpc>=0.1.0",
"hanzo-tools-team>=0.1.0",
"hanzo-tools-ui>=0.1.0",
"hanzo-persona>=1.0.0",
]
+5 -2
View File
@@ -21,8 +21,11 @@ dependencies = [
"httpx>=0.27.0",
]
[project.entry-points."hanzo.tools"]
billing = "hanzo_tools.billing:TOOLS"
# No `hanzo.tools` entry point: this addresses the fleet by hand, and the fleet
# is reached by the unified `hanzo` tool, which filters by what cloud offers an
# agent. Registering here put `billing` in a model's tool list beside it — two ways
# to one API, and the second one unfiltered. Still importable for a human at a
# CLI; it is the auto-registration that is withdrawn.
[build-system]
requires = ["hatchling"]
+5 -2
View File
@@ -21,8 +21,11 @@ dependencies = [
"httpx>=0.27.0",
]
[project.entry-points."hanzo.tools"]
commerce = "hanzo_tools.commerce:TOOLS"
# No `hanzo.tools` entry point: this addresses the fleet by hand, and the fleet
# is reached by the unified `hanzo` tool, which filters by what cloud offers an
# agent. Registering here put `commerce` in a model's tool list beside it — two ways
# to one API, and the second one unfiltered. Still importable for a human at a
# CLI; it is the auto-registration that is withdrawn.
[build-system]
requires = ["hatchling"]
+5 -2
View File
@@ -25,8 +25,11 @@ dependencies = [
"httpx>=0.27.0",
]
[project.entry-points."hanzo.tools"]
iam = "hanzo_tools.iam:TOOLS"
# No `hanzo.tools` entry point: this addresses the fleet by hand, and the fleet
# is reached by the unified `hanzo` tool, which filters by what cloud offers an
# agent. Registering here put `iam` in a model's tool list beside it — two ways
# to one API, and the second one unfiltered. Still importable for a human at a
# CLI; it is the auto-registration that is withdrawn.
[tool.hatch.build.targets.wheel]
packages = ["hanzo_tools"]
+5 -2
View File
@@ -21,8 +21,11 @@ dependencies = [
"httpx>=0.27.0",
]
[project.entry-points."hanzo.tools"]
ingress = "hanzo_tools.ingress:TOOLS"
# No `hanzo.tools` entry point: this addresses the fleet by hand, and the fleet
# is reached by the unified `hanzo` tool, which filters by what cloud offers an
# agent. Registering here put `ingress` in a model's tool list beside it — two ways
# to one API, and the second one unfiltered. Still importable for a human at a
# CLI; it is the auto-registration that is withdrawn.
[build-system]
requires = ["hatchling"]
+5 -2
View File
@@ -20,8 +20,11 @@ dependencies = [
"hanzo-kms>=0.1.0",
]
[project.entry-points."hanzo.tools"]
kms = "hanzo_tools.kms:TOOLS"
# No `hanzo.tools` entry point: this addresses the fleet by hand, and the fleet
# is reached by the unified `hanzo` tool, which filters by what cloud offers an
# agent. Registering here put `kms` in a model's tool list beside it — two ways
# to one API, and the second one unfiltered. Still importable for a human at a
# CLI; it is the auto-registration that is withdrawn.
[build-system]
requires = ["hatchling"]
+5 -2
View File
@@ -22,8 +22,11 @@ dependencies = [
"httpx>=0.27.0",
]
[project.entry-points."hanzo.tools"]
paas = "hanzo_tools.paas:TOOLS"
# No `hanzo.tools` entry point: this addresses the fleet by hand, and the fleet
# is reached by the unified `hanzo` tool, which filters by what cloud offers an
# agent. Registering here put `paas` in a model's tool list beside it — two ways
# to one API, and the second one unfiltered. Still importable for a human at a
# CLI; it is the auto-registration that is withdrawn.
[build-system]
requires = ["hatchling"]
+5 -2
View File
@@ -20,8 +20,11 @@ dependencies = [
"hanzo-s3>=1.0.0",
]
[project.entry-points."hanzo.tools"]
s3 = "hanzo_tools.s3:TOOLS"
# No `hanzo.tools` entry point: this addresses the fleet by hand, and the fleet
# is reached by the unified `hanzo` tool, which filters by what cloud offers an
# agent. Registering here put `s3` in a model's tool list beside it — two ways
# to one API, and the second one unfiltered. Still importable for a human at a
# CLI; it is the auto-registration that is withdrawn.
[build-system]
requires = ["hatchling"]
+5 -2
View File
@@ -25,8 +25,11 @@ dependencies = [
"httpx>=0.27.0",
]
[project.entry-points."hanzo.tools"]
team = "hanzo_tools.team:TOOLS"
# No `hanzo.tools` entry point: this addresses the fleet by hand, and the fleet
# is reached by the unified `hanzo` tool, which filters by what cloud offers an
# agent. Registering here put `team` in a model's tool list beside it — two ways
# to one API, and the second one unfiltered. Still importable for a human at a
# CLI; it is the auto-registration that is withdrawn.
[tool.hatch.build.targets.wheel]
packages = ["hanzo_tools"]