mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-24 07:01:49 +00:00
docs(security): mark shared-secret HTTP auth as designed
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -8,7 +8,7 @@ title: "Tools Invoke API"
|
||||
|
||||
# Tools Invoke (HTTP)
|
||||
|
||||
OpenClaw’s 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.
|
||||
OpenClaw’s 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`
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user