docs(security): mark shared-secret HTTP auth as designed

This commit is contained in:
Peter Steinberger
2026-03-31 22:57:35 +09:00
parent a4d72a83f0
commit dc0e0b0f68
4 changed files with 17 additions and 9 deletions

View File

@@ -58,6 +58,7 @@ These are frequently reported but are typically closed with no code change:
- Reports that assume per-user multi-tenant authorization on a shared gateway host/config.
- Reports that treat the Gateway HTTP compatibility endpoints (`POST /v1/chat/completions`, `POST /v1/responses`) as if they implemented scoped operator auth (`operator.write` vs `operator.admin`). These endpoints authenticate the shared Gateway bearer secret/password and are documented full operator-access surfaces, not per-user/per-scope boundaries.
- Reports that assume `x-openclaw-scopes` can reduce or redefine shared-secret bearer auth on the OpenAI-compatible HTTP endpoints. For shared-secret auth (`gateway.auth.mode="token"` or `"password"`), those endpoints ignore narrower bearer-declared scopes and restore the full default operator scope set plus owner semantics.
- Reports that treat `POST /tools/invoke` under shared-secret bearer auth (`gateway.auth.mode="token"` or `"password"`) as a narrower per-request/per-scope authorization surface. That endpoint is designed as the same trusted-operator HTTP boundary: shared-secret bearer auth is full operator access there, narrower `x-openclaw-scopes` values do not reduce that path, and owner-only tool policy follows the shared-secret operator contract.
- Reports that only show differences in heuristic detection/parity (for example obfuscation-pattern detection on one exec path but not another, such as `node.invoke -> system.run` parity gaps) without demonstrating bypass of auth, approvals, allowlist enforcement, sandboxing, or other documented trust boundaries.
- Reports that only show an ACP tool can indirectly execute, mutate, orchestrate sessions, or reach another tool/runtime without demonstrating bypass of ACP prompt/approval, allowlist enforcement, sandboxing, or another documented trust boundary. ACP silent approval is intentionally limited to narrow readonly classes; parity-only indirect-command findings are hardening, not vulnerabilities.
- ReDoS/DoS claims that require trusted operator configuration input (for example catastrophic regex in `sessionFilter` or `logging.redactPatterns`) without a trust-boundary bypass.
@@ -95,11 +96,12 @@ When patching a GHSA via `gh api`, include `X-GitHub-Api-Version: 2022-11-28` (o
OpenClaw does **not** model one gateway as a multi-tenant, adversarial user boundary.
- Authenticated Gateway callers are treated as trusted operators for that gateway instance.
- The HTTP compatibility endpoints (`POST /v1/chat/completions`, `POST /v1/responses`) are in that same trusted-operator bucket. Passing Gateway bearer auth there is equivalent to operator access for that gateway; they do not implement a narrower `operator.write` vs `operator.admin` trust split.
- The HTTP compatibility endpoints (`POST /v1/chat/completions`, `POST /v1/responses`) and direct tool endpoint (`POST /tools/invoke`) are in that same trusted-operator bucket. Passing Gateway bearer auth there is equivalent to operator access for that gateway; they do not implement a narrower `operator.write` vs `operator.admin` trust split.
- Concretely, on the OpenAI-compatible HTTP surface:
- shared-secret bearer auth (`token` / `password`) authenticates possession of the gateway operator secret
- those requests receive the full default operator scope set (`operator.admin`, `operator.read`, `operator.write`, `operator.approvals`, `operator.pairing`)
- chat-turn endpoints (`/v1/chat/completions`, `/v1/responses`) also treat those shared-secret callers as owner senders for owner-only tool policy
- `POST /tools/invoke` follows that same shared-secret rule and also treats those callers as owner senders for owner-only tool policy
- narrower `x-openclaw-scopes` headers are ignored for that shared-secret path
- only identity-bearing HTTP modes (for example trusted proxy auth or `gateway.auth.mode="none"` on private ingress) honor declared per-request operator scopes
- Session identifiers (`sessionKey`, session IDs, labels) are routing controls, not per-user authorization boundaries.

View File

@@ -8,7 +8,7 @@ title: "Tools Invoke API"
# Tools Invoke (HTTP)
OpenClaws Gateway exposes a simple HTTP endpoint for invoking a single tool directly. It is always enabled and uses Gateway auth plus tool policy, but unlike the OpenAI-compatible `/v1/*` surface, shared-secret bearer auth is not enough to use it.
OpenClaws Gateway exposes a simple HTTP endpoint for invoking a single tool directly. It is always enabled and uses Gateway auth plus tool policy. Like the OpenAI-compatible `/v1/*` surface, shared-secret bearer auth is treated as trusted operator access for the whole gateway.
- `POST /tools/invoke`
- Same port as the Gateway (WS + HTTP multiplex): `http://<gateway-host>:<port>/tools/invoke`

View File

@@ -150,9 +150,10 @@ export function resolveOpenAiCompatibleHttpOperatorScopes(
requestAuth: AuthorizedGatewayHttpRequest,
): string[] {
if (usesSharedSecretGatewayMethod(requestAuth.authMethod)) {
// OpenAI-compatible HTTP bearer auth is documented as a trusted-operator
// surface. Shared-secret auth does not carry a narrower per-request scope
// identity, so restore the normal operator defaults for this surface.
// Shared-secret HTTP bearer auth is a documented trusted-operator surface
// for the compat APIs and direct /tools/invoke. This is designed-as-is:
// token/password auth proves possession of the gateway operator secret, not
// a narrower per-request scope identity, so restore the normal defaults.
return [...CLI_DEFAULT_OPERATOR_SCOPES];
}
return resolveTrustedHttpOperatorScopes(req, requestAuth);
@@ -172,10 +173,10 @@ export function resolveOpenAiCompatibleHttpSenderIsOwner(
requestAuth: AuthorizedGatewayHttpRequest,
): boolean {
if (usesSharedSecretGatewayMethod(requestAuth.authMethod)) {
// The OpenAI-compatible HTTP surface treats shared-secret bearer auth as
// trusted operator access for the whole gateway. There is no separate owner
// authentication primitive on that path, so owner-only tools remain
// available to those compat requests.
// Shared-secret HTTP bearer auth also carries owner semantics on the compat
// APIs and direct /tools/invoke. This is intentional: there is no separate
// per-request owner primitive on that shared-secret path, so owner-only
// tool policy follows the documented trusted-operator contract.
return true;
}
return resolveHttpSenderIsOwner(req, requestAuth);

View File

@@ -174,6 +174,9 @@ export async function handleToolsInvokeHttpRequest(
return true;
}
// /tools/invoke intentionally uses the same shared-secret HTTP trust model as
// the OpenAI-compatible APIs: token/password bearer auth is full operator
// access for the gateway, not a narrower per-request scope boundary.
const requestedScopes = resolveOpenAiCompatibleHttpOperatorScopes(req, requestAuth);
const scopeAuth = authorizeOperatorScopesForMethod("agent", requestedScopes);
if (!scopeAuth.allowed) {
@@ -324,6 +327,8 @@ export async function handleToolsInvokeHttpRequest(
Array.isArray(gatewayToolsCfg?.deny) ? gatewayToolsCfg.deny : [],
);
const gatewayDenySet = new Set(gatewayDenyNames);
// Owner semantics intentionally follow the same shared-secret HTTP contract
// on this direct tool surface; SECURITY.md documents this as designed-as-is.
const senderIsOwner = resolveOpenAiCompatibleHttpSenderIsOwner(req, requestAuth);
const ownerFiltered = applyOwnerOnlyToolPolicy(subagentFiltered, senderIsOwner);
const gatewayFiltered = ownerFiltered.filter((t) => !gatewayDenySet.has(t.name));