Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions docs/docs/explanation/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,17 @@ Static filtering is useful for deployment-wide availability, but it is not dynam

## Client errors are intentionally coarse

The root API exposes stable sentinel classifications. The MCP adapter projects failures to short text such as `permission denied`, `capability failed`, `resource limit exceeded`, and `internal failure`.
Detailed causes exist only on the trusted side of the public boundary. Internal packages and host authorizers, resolvers, and handlers can hold or log those causes. A direct call to `authz/rego.Authorize` can return an ordinary error that identifies an undefined or non-Boolean decision, or carries an OPA evaluation or builtin failure.

This projection prevents trusted diagnostic detail from becoming model-visible. In particular, the MCP response does not forward:
`codemode.Server.Execute` removes those trusted causes. It returns the documented public sentinel for execution, policy, handler, resource, and internal failures. Request cancellation returns `context.Canceled`. A deadline returns `ErrResourceLimit` and preserves `context.DeadlineExceeded` for `errors.Is`; no other execution cause remains wrapped at the root API.

- resolver and authentication diagnostics
- policy reasons
- handler error text
- panic values or stack details
- credentials
- source or argument values copied into wrapped errors
The MCP adapter narrows the boundary again. It emits only the fixed error texts in the [MCP tool reference](../reference/mcp-tools.md#errors). Resolver and custom-service details and recovered panic values become coarse responses. SDK input-schema errors are different: they occur before trusted subject resolution and can identify malformed client-owned fields or values.

The host can log trusted details on its side of the boundary if its authorizer, resolver, and handlers implement that logging. CodeMode's client response remains coarse. Unknown service errors and recovered adapter panics become `internal failure`. A nil `Server.Execute` context is a caller-contract violation and is currently classified as `ErrInternal`.
This projection prevents trusted diagnostic detail from becoming model-visible. In particular, MCP responses do not expose budget values, filtered capability identities, unknown requested names, argument names or values, source locations or text, Rego decision paths or rule names, handler messages, credentials, panic values, or stack details.

If a host needs detailed diagnostics, its trusted authorizer, resolver, or handler must record them before returning. CodeMode cannot recover a discarded cause after the root or MCP projection. Apply the host's normal access controls and redaction rules to those logs.

An allowed or denied call proves only the result for that subject, capability, and canonical argument set. It does not show whether the policy is default-open, default-deny, complete, or incomplete. Treat an unexpected denial or policy failure as a reason to contact the host, not as evidence about the policy's rules or defaults.

## Execution state does not cross calls

Expand Down
16 changes: 8 additions & 8 deletions docs/docs/reference/mcp-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,19 +205,19 @@ Only the final converted value crosses the execution boundary. `print` output is

## Authoring and recovery

The listed descriptions above are the model-facing contract. Recovery uses the same fixed coarse errors on this page. Error payloads stay non-disclosing; they do not gain diagnostic detail, aliases, or suggested alternate names.
The listed descriptions above are the model-facing contract. Recovery uses the same fixed coarse errors on this page. When recording or reporting a failed call, keep the coarse text and the recovery action; do not echo the failed source, arguments, credentials, or unknown requested name.

- Search with a short literal substring over enabled names and summaries. If the result is empty, retry with a shorter term.
- Pass `describe_api` the exact `name` returned by `search_api`, without whitespace or case changes.
- Compare native call arguments with the `describe_api` field shapes before `execute`.
- After `capability not found`, search again and pass `describe_api` an exact returned `name`, without whitespace or case changes.
- After `invalid capability arguments`, compare the call with the published `signature` and `input` field shapes.
- After `invalid program`, use a zero-argument `def main():`, call only names confirmed through search and description inside `main`, and return the final value.
- Use `describe_api.output` as the result contract. Do not parse a type name from `signature`.
- Define `def main():` with zero arguments. Call only names confirmed through `search_api` and `describe_api` inside `main`. Return `main`'s final result.
- After `resource limit exceeded`, reduce program or result complexity and retry.
- `permission denied` and `authorization policy failure` are outcomes for that call only. They do not disclose policy rules.
- After `resource limit exceeded`, reduce source or result complexity and retry.
- After `permission denied` or `authorization policy failure`, contact the host if the access was expected. One allowed or denied input cannot establish whether the policy is default-open, default-deny, complete, or incomplete.

## Errors

After a well-formed call reaches the adapter, a resolver or service failure becomes a successful MCP protocol response with `isError` set and one coarse text item. The adapter does not expose wrapped policy diagnostics, handler errors, panic values, stack details, credentials, source, or arguments.
After a well-formed call reaches the adapter, a resolver or service failure becomes a successful MCP protocol response with `isError` set and one of the eleven fixed text values below. The adapter removes resolver and custom-service details and recovered panic values. It does not expose budget values, filtered capability identities, unknown requested names, argument names or values, source locations or text, Rego decision paths or rule names, handler messages, credentials, panic values, or stack details.

| Text | Meaning |
| --- | --- |
Expand All @@ -233,6 +233,6 @@ After a well-formed call reaches the adapter, a resolver or service failure beco
| `context deadline exceeded` | A service returned a bare deadline error. Root CodeMode execution deadlines are normally projected as `resource limit exceeded`. |
| `internal failure` | Any unknown service error or recovered adapter failure. |

Malformed MCP arguments are rejected by the SDK's input-schema validation and do not call the invocation resolver. For well-formed inputs, resolver failure stops the request before search, description, or execution.
Malformed MCP arguments are rejected by the SDK's input-schema validation and do not call the invocation resolver. These errors can identify malformed client-owned fields or values because validation occurs before trusted resolution. For well-formed inputs, resolver failure stops the request before search, description, or execution and becomes `unauthenticated` without resolver detail.

See [Public API reference](public-api.md) for Go contracts and [Security model](../explanation/security-model.md) for the resolver and execution trust boundaries.
4 changes: 3 additions & 1 deletion docs/docs/reference/public-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ Printed text, globals, and intermediate values do not cross the execution bounda

### Error classifications

Use `errors.Is` to inspect these exported sentinel errors. Client adapters can replace wrapped diagnostic detail with the sentinel text.
Use `errors.Is` to inspect these exported sentinel errors. `Server.Execute` removes trusted wrapped causes and returns only the classifications below. Client adapters must not depend on diagnostic text or an unwrapped internal, policy, or handler error.

| Error | Classification |
| --- | --- |
Expand All @@ -211,6 +211,8 @@ Use `errors.Is` to inspect these exported sentinel errors. Client adapters can r

Request cancellation returns `context.Canceled`. A deadline is classified as `ErrResourceLimit` and also wraps `context.DeadlineExceeded` at the root API.

Internal packages and trusted authorizer and handler implementations can hold detailed causes before this projection. A direct `authz/rego.Authorize` call can return an ordinary error that names an undefined or non-Boolean decision or wraps an OPA evaluation failure. `Server.Execute` maps that error to `ErrPolicyFailure` without retaining the cause. If the host needs the detail, record it inside the trusted implementation before returning.

## `authz`

### Subjects
Expand Down
2 changes: 1 addition & 1 deletion mcpserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func resolveSubject(ctx context.Context, resolver InvocationResolver) (authz.Sub
return subject, nil
}

// projectToolError maps a service failure to stable coarse tool-safe text.
// projectToolError removes wrapped service detail and maps failures to fixed client-visible sentinels.
func projectToolError(err error) error {
switch {
case errors.Is(err, codemode.ErrResourceLimit):
Expand Down
3 changes: 2 additions & 1 deletion server.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ func (server *Server) Execute(ctx context.Context, subject authz.Subject, progra
return result, nil
}

// projectExecutionError converts trusted internal execution failures to the public safe taxonomy.
// projectExecutionError removes trusted execution causes at the root boundary.
// It preserves only safe sentinels and documented context cancellation and deadline wrapping.
func projectExecutionError(err error) error {
switch {
case errors.Is(err, execution.ErrInvalidProgram):
Expand Down