* fix: allow unknown properties in WakeParams schema (#68347)
WakeParamsSchema used additionalProperties: false, rejecting unknown
properties like 'paperclip' from external tools. Changed to
additionalProperties: true for forward compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: trim wake params schema comments
* fix: allow unknown properties in WakeParams schema (#68355) (thanks @kagura-agent)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* test(gateway): add full unit coverage for http-common.ts
Adds tests exercising every export in src/gateway/http-common.ts so the module reaches 100% line, branch, function and statement coverage (33 tests). Captures current default security headers (including the existing Permissions-Policy microphone=() deny-list) and exhaustively covers sendJson/sendText/sendMethodNotAllowed/sendUnauthorized/sendRateLimited (with and without Retry-After), sendGatewayAuthFailure (both branches), sendInvalidRequest, readJsonBodyOrError (413/408/400/success), writeDone, setSseHeaders (with and without flushHeaders) and watchClientDisconnect (empty/single/dedup/distinct sockets, abort logic and listener cleanup).
* fix(gateway): allow microphone access for same-origin in Permissions-Policy header
The gateway's default security headers set Permissions-Policy to microphone=(), which denies microphone access for every origin including the page itself. As a result, the control-ui chat mic button (ui/src/ui/chat/speech.ts) cannot start SpeechRecognition: the browser refuses with 'Permissions policy violation: microphone is not allowed in this document' and the button silently resets.
Relax microphone to the same-origin allowlist (self) so the dashboard page can use the Web Speech API while still blocking third-party frames. Camera and geolocation remain fully denied.
Fixes#51085
* test(gateway): add seeded property/fuzz tests for http-common.ts
Adds src/gateway/http-common.fuzz.test.ts with 13 property-style tests (200 iterations each) driven by an in-file deterministic mulberry32 PRNG. Covers every export with invariants rather than fixed examples: baseline security headers across all opts shapes, Strict-Transport-Security iff non-empty string, sendJson/sendText status + body round-trips across random codes and payloads, sendMethodNotAllowed with random Allow values, sendRateLimited Retry-After iff retryAfterMs>0 with ceil-seconds value (including fractional ms), sendGatewayAuthFailure delegation, sendInvalidRequest message echo, readJsonBodyOrError status/body mapping across random error texts, writeDone sentinel, setSseHeaders with/without flushHeaders, and watchClientDisconnect invariants across arbitrary socket/controller/callback combinations (empty, same, distinct, pre-aborted). Deterministic seeds keep failures reproducible without introducing a new dev dependency.
Skip bundled channel discovery for plain message-action params and only resolve
plugin-owned media params when an extension field is actually present. This
keeps normal sends on the lightweight path while preserving plugin media-field
coverage.
Run setup auto-enable probes only for plugin ids made relevant by the
current config instead of loading every setup API. This keeps provider
plugin auto-enable checks from paying unrelated setup registration cost.
* fix(exec-approvals): escape control characters in display sanitizers
* docs(changelog): add exec approval control-char display sanitizer entry
* fix(exec-approvals): redact before escape, cover U+2028/U+2029 in display sanitizers
* fix(exec-approvals): strip invisibles before redaction and align forwarder test
* fix(exec-approvals): cover Zs bypass and preserve multi-line context on obfuscated secrets
* fix(exec-approvals): compare redaction outputs by content, not length
* fix(exec-approvals): suppress raw command on bypass; cover non-ASCII Zs in macOS sanitizer
* fix(exec-approvals): use position-bitmap bypass detection and bound input size
* style(exec-approvals): satisfy oxlint no-new-array-single-argument and SwiftFormat
* fix(exec-approvals): iterate by code point and redact before truncating
Honor targeted includes in the contracts Vitest lane and compare bundled
web-search fast-path artifacts against plugin-owned runtime artifacts instead
of loading whole plugin entries. Split Google and Firecrawl runtime-only work
behind lazy seams so provider registration stays metadata-light.
Also keep Perplexity contract metadata aligned by sharing its runtime transport
resolution with the contract artifact.
* fix(gateway): enforce assistant media scopes
* changelog: require read scope for assistant media (#68175)
* skip scope enforcement for auth.mode=none
Exclude method "none" from the identity-bearing scope gate so
gateway.auth.mode=none deployments are not regressed by the new
operator.read check.
---------
Co-authored-by: Devin Robison <drobison@nvidia.com>
* fix(agents): filter bundled tools through final policy
* changelog: filter bundled tools through final policy (#68195)
* forward agentId into compaction tool-policy filter
Pass effectiveSkillAgentId to applyFinalEffectiveToolPolicy in the
compaction path so per-agent tool policies apply to bundled tools
during compaction the same way they do during normal runs.
* scope final tool-policy filter to bundled tools only
Running the full tool-policy pipeline on the merged core + bundled tool list
re-filters core tools whose plugin WeakMap metadata no longer survives the
normalize/hook wrappers applied by createOpenClawCodingTools(). Narrow the
helper to only the newly-appended bundled MCP/LSP tools so plugin-provided
core tools keep matching group:plugins and plugin-id allowlist entries.
* harden authorization signals on final tool policy
- message.action gateway handler now server-derives senderIsOwner from the
authenticated gateway client scopes (ADMIN_SCOPE on client.connect.scopes)
and ignores any senderIsOwner value on the wire, so a non-admin scoped
caller cannot spoof owner status to unlock owner-only channel actions or
owner-only tool policy. Schema keeps the field optional for wire compat
but documents that it is ignored.
- applyFinalEffectiveToolPolicy now cross-checks caller-provided groupId
against the session-derived group context resolved from sessionKey (and
spawnedBy). When they disagree, the caller groupId plus its adjacent
groupChannel/groupSpace are dropped and a warn is emitted, so a caller
that fabricates a different group id cannot reach a more permissive
group-scoped tool policy during the final bundled-tool filter. Added a
JSDoc trust invariant on the helper input describing the required
server-verified identity contract.
* align compact agentId resolution with core tools
Drop the explicit agentId on applyFinalEffectiveToolPolicy during
compaction. The core tool set produced just above via
createOpenClawCodingTools(...) also omits agentId, so resolveEffectiveToolPolicy
falls back to resolveAgentIdFromSessionKey(sessionKey) in both places.
Passing effectiveSkillAgentId only to the final filter made the two
policy lookups diverge on legacy/non-agent session keys where the
sessionKey path resolves to main but effectiveSkillAgentId follows the
configured default-agent path, which could deny or allow bundled tools
under a different per-agent policy than the already-created core tools.
* tighten trusted propagation for owner and group signals
- message.action gateway handler: full-operator callers (shared-secret
bearer or operator.admin scope) now propagate the request-provided
senderIsOwner through to channel action handlers instead of having it
hard-coded off. Previously the hardened path force-derived ownership
from ADMIN_SCOPE alone, which broke owner-gated actions when the
trusted runtime forwards them via the least-privilege gateway path
(callGatewayLeastPrivilege requests only the method scope, so even
legitimate owner senders were downgraded to senderIsOwner=false).
Narrowly-scoped callers (e.g. operator.write-only) still have the wire
value forced to false so a non-admin caller cannot assert ownership.
- applyFinalEffectiveToolPolicy: fail-closed when the session key and
spawnedBy encode no group context. Previously the helper only dropped
a caller-provided groupId that conflicted with a non-empty set of
session-derived group ids, which left an accept-caller fallback open
when the session had no group context at all (direct/cron/subagent
session keys). An attacker who could run without a group-bound session
could then supply an arbitrary groupId and reach a more permissive
group-scoped tool policy. Now: no session-derived group context plus
any caller-provided groupId drops the caller value and warns.
* suppress unavailable-core-tool warnings in bundled-only pass
applyToolPolicyPipeline infers its coreToolNames reference set from the
tools array it is filtering. The bundled-only second pass only sees the
MCP/LSP subset, so normal core allowlist entries (for example
tools.allow: ['read', 'exec']) would look "unknown" during this pass
and emit misleading warnings even when the config is valid for the full
effective tool set — polluting logs and potentially evicting real
diagnostics from the shared warning cache. Set
suppressUnavailableCoreToolWarning on every step of this pass so known
core-tool allowlist entries stay silent; genuinely unknown entries
still surface through the otherEntries warning path.
Keep explicit session-key normalization on loaded channel plugins so
unknown provider contexts pass through without cold-loading bundled channel
runtimes. This preserves active plugin behavior and removes the slow
unknown-provider test path.