Files
zero/SECURITY.md
16ccd78765 feat(mcp): add AI/ML API as an opt-in built-in server (#1193)
* feat(aimlapi): shared service key and attribution for aimlapi.com services

Adds the pieces the Toolbox and MCP integrations share:

- internal/aimlapi: X-AIMLAPI-Source (agent/zero), the fixed Toolbox and
  MCP URLs, and an exact-hostname check (https, default port, no
  userinfo) for api/tools/mcp.aimlapi.com before the key or attribution
  headers are attached. The partner id still comes from ResolvePartnerID.
- internal/config: AIMLAPIServiceKey picks the key from the aimlapi
  profile (or AIMLAPI_API_KEY without one), reading the credential
  store, and withholds it when any aimlapi-keyed profile points at a
  proxy base URL or AIMLAPI_INFERENCE_URL is overridden. Keys that are
  not a single token are refused.

* feat(mcp): AI/ML API MCP server as an opt-in built-in default

Seeds `aimlapi` (https://mcp.aimlapi.com/mcp, streamable HTTP) next to
Exa as a built-in MCP server that ships disabled: its tool runs and
generations are billed to the user's AI/ML API balance. The disable is
marked as explicitly set, so a project config cannot lift it; the user
turns it on with `zero mcp enable aimlapi`, which now writes
`"disabled": false` for a default that ships off (and `zero mcp add
aimlapi` does the same). `zero doctor` reports the untouched default as
a pass rather than a warning.

The MCP client attaches the AI/ML API key and the X-AIMLAPI-Source /
X-AIMLAPI-Partner-ID headers in one place (internal/mcp/aimlapi.go),
called from the three header-application points. Both apply only to a
request whose own URL is exactly https://mcp.aimlapi.com on the default
port, whatever the server is named. The key is resolved for every
request through config.LoadAIMLAPIServiceKey, so switching the aimlapi
provider to a proxy, or a key that is not a single token, means no
Authorization header on the next request. It is never put in config,
the server identity used for OAuth token binding, list output or errors.
A user Authorization header, `"auth": "oauth"`, or a project-touched
server goes without it. A 401 from that host explains how to add a key
or sign in with `zero mcp oauth login`.

* fix(aimlapi): validate whitespace and preserve store errors

* fix(mcp): tailor unauthorized hints to credential policy

* docs(mcp): clarify automatic credential eligibility

* test(mcp): cover edited disabled built-in defaults

* fix(aimlapi): report store reads after proxy eligibility checks

* docs(aimlapi): document MCP key precedence and clarify 401 hints

Operator copy now matches normalizeProvider resolution (inline key,
apiKeyEnv, then credential store) and warns that AIMLAPI_API_KEY can
bill MCP before a stored profile key. SECURITY and service-key godoc
stay aligned. 401 hints no longer imply a proxy-only failure mode.

Adds a regression test that resolved profile keys win over the store.

Co-authored-by: Cursor <[email protected]>

* fix(mcp): keep the aimlapi key off redirects and error text, persist enable

Redirect hops: net/http copies the first request's headers onto every
redirect hop without calling the header hooks, so the automatic AI/ML API
key and attribution followed a same-origin hop that fails the exact-URL
rule (userinfo) and an eligible hop reused the first hop's key. The
redirect guard now restores the server's own values for those headers and
applies the per-request rule again.

Error text: httpStatusError copied up to 1 KiB of the response body into
the error, so a body echoing the injected key (or its prefix, when cut by
the read limit) reached CLI output. When the request carried the automatic
key, the body is now omitted instead of matched.

Enable persistence: MCPServerConfig had no serializer and Disabled is
omitempty, so any read-modify-write of config.json (SetTheme,
SetActiveProvider, ...) dropped `"disabled": false` and the shipped
disable turned aimlapi off again. MarshalJSON now writes the flag whenever
it was explicitly set; `zero mcp add` keeps writing only a disable.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Stan <[email protected]>
Co-authored-by: Hugo <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-04 20:13:53 -08:00

2.5 KiB

Security Policy

Zero is a terminal coding agent: it reads and edits files in your repository and can run shell commands and call external model/tool endpoints. We take security reports seriously.

Reporting a vulnerability

Please do not open a public issue for security vulnerabilities.

Report privately through GitHub's private vulnerability reporting:

  1. Go to the repository's Security tab → Report a vulnerability (GitHub Security Advisories).
  2. Describe the issue, the affected version/commit, reproduction steps, and the impact.

If you cannot use GitHub Security Advisories, open a regular issue that contains only a request to be contacted for a security report (no details), and a maintainer will arrange a private channel.

Please include, where possible:

  • The version, branch, or commit affected.
  • A minimal reproduction and the impact (e.g. sandbox escape, secret exposure, RCE).
  • Any logs or output, with secrets redacted.

What to expect

  • We aim to acknowledge a report within a few business days.
  • We will work with you on a fix and a coordinated disclosure timeline, and credit you in the release notes unless you prefer to remain anonymous.

Scope notes

  • Findings that require a malicious local user who already controls the machine running Zero, or that depend on a user explicitly disabling the sandbox (--skip-permissions-unsafe), are generally out of scope — but please report anything you are unsure about.
  • Zero sends no telemetry. Network calls go to the model/tool providers you configure (and, for keyless web search, to Exa's hosted MCP endpoint at https://mcp.exa.ai/mcp). The built-in AI/ML API MCP server (https://mcp.aimlapi.com/mcp) is off until you run zero mcp enable aimlapi. Once enabled, the Bearer key follows the aimlapi profile resolution order (inline key, then the profile's apiKeyEnv — default AIMLAPI_API_KEY — then the credential store), or AIMLAPI_API_KEY alone when no aimlapi profile exists; a non-empty AIMLAPI_API_KEY can override a stored profile key for MCP billing. The automatic key is withheld if an aimlapi profile (or a profile reading AIMLAPI_API_KEY) uses a base URL other than https://api.aimlapi.com/v1, AIMLAPI_INFERENCE_URL points elsewhere, a non-empty user Authorization header is configured, OAuth is selected, project config has touched the server, or the key is missing or not a single token (including Unicode whitespace). See docs/EXTENDING.md.