fix(gateway): fail closed on runtime config edits#70726
Conversation
🔒 Aisle Security AnalysisWe found 3 potential security issue(s) in this PR:
1. 🟠 Agent can delete or rename configured agents via allowlisted agents.list[].id/model edits (policy bypass)
DescriptionThe gateway tool’s config mutation guard moved from a denylist of protected paths to an allowlist of “agent-tunable” paths. As part of this, Because deletions/renames of keyed entries are represented as per-entry diffs at the synthetic path Impact depends on deployment, but in configs where additional agents provide safety/oversight (e.g., monitoring/policy/approval agents) and their entries only contain allowlisted fields, an untrusted model could:
Relevant code: const ALLOWED_GATEWAY_CONFIG_PATHS = [
// ...
"agents.list[].id",
"agents.list[].model",
// ...
];
// When a keyed array entry disappears, nextEntries.get(id) is undefined,
// so collectConfigLeafPaths() records only leaf paths under `${basePath}[]`.
collectChangedConfigPaths(currentEntries.entries.get(id), nextEntries.entries.get(id), `${basePath}[]`, out);Downstream, agent identity and availability are driven by
RecommendationFail closed on membership/identity changes to Options:
Example hardening (conceptual): if (basePath === "agents.list") {
const currentIds = new Set(currentEntries.entries.keys());
const nextIds = new Set(nextEntries.entries.keys());
if (!setsEqual(currentIds, nextIds)) {
out.add("agents.list"); // not allowlisted => blocked
return out;
}
}Also consider an explicit invariant that safety/oversight agents must be present and immutable via agent-facing tools. 2. 🟡 Potential CPU/memory exhaustion in gateway config mutation path-diffing
Description
An agent (not a trusted principal per comments) that can call the owner-only Vulnerable code: const nextConfig = ...applyMergePatch(...)
const changedPaths = [...collectChangedConfigPaths(params.currentConfig, nextConfig)].toSorted();
...
function collectChangedConfigPaths(currentValue, nextValue, basePath = "", out = new Set()) {
if (isDeepStrictEqual(currentValue, nextValue)) return out;
... // recursively walks objects/arrays
}RecommendationAdd explicit resource bounds and early-exit behavior for agent-supplied config mutations. Suggested mitigations (combine as appropriate):
Example guard (illustrative): const MAX_RAW_BYTES = 50_000;
if (params.raw.length > MAX_RAW_BYTES) {
throw new Error(`gateway ${params.action} raw too large`);
}
function collectChangedConfigPaths(..., depth = 0, budget = { nodes: 0 }) {
if (depth > 50) throw new Error("config mutation too deep");
if (++budget.nodes > 20_000) throw new Error("config mutation too large");
...
}This prevents a model/agent from supplying pathological payloads that monopolize CPU/memory. 3. 🟡 Allowlist check matches path prefixes, allowing nested mutations under allowlisted keys
Description
This becomes dangerous in combination with Impact depends on downstream config consumers, but common outcomes include:
Vulnerable code: // Prefix match: patternSegments.length > pathSegments.length is the only length check.
if (patternSegments.length > pathSegments.length) {
return false;
}
...
return true;RecommendationMake allowlist matching exact by default (same number of segments), and only allow prefix/descendant matches when explicitly requested (e.g., patterns ending with Additionally, when types differ in Example fix (exact match): function isAllowedGatewayConfigPath(path: string): boolean {
const pathSegments = path.split(".");
return ALLOWED_GATEWAY_CONFIG_PATHS.some((pattern) => {
const patternSegments = pattern.split(".");
if (patternSegments.length !== pathSegments.length) return false;
for (let i = 0; i < patternSegments.length; i++) {
if (!pathSegmentMatches(patternSegments[i], pathSegments[i])) return false;
}
return true;
});
}Example fix (type-change leaf enumeration): // when current/next are different primitive/object kinds
collectConfigLeafPaths(currentValue, basePath, out);
collectConfigLeafPaths(nextValue, basePath, out);Analyzed PR: #70726 at commit Last updated on: 2026-04-23T21:23:36Z |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 83be9659e4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Greptile SummaryThis PR converts the gateway config-mutation guard from a denylist to a narrow allowlist: rather than blocking a fixed set of known-sensitive paths, Two P2 notes: the Confidence Score: 5/5Safe to merge — no P0/P1 findings; the allowlist approach is strictly more secure than the replaced denylist. All findings are P2 (style/documentation concerns). The core logic — structural diff + allowlist matching + dangerous-flag secondary check — is correct and the test coverage is thorough. The prefix-match permissiveness is by design and mitigated by schema validation downstream. No files require special attention for merge safety. The PR description should be corrected before merging for traceability.
|
Append a user-facing Unreleased/Fixes entry describing the fail-closed gateway config-mutation allowlist, and extend the allowlist so Telegram topic-level paths like channels.telegram.groups.<group>.topics.<topic>.requireMention stay agent-tunable instead of being rejected as protected after this change.
83be965 to
4a62641
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a626413f9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
* fix(gateway): fail closed on runtime config edits * changelog + telegram topic requireMention depth Append a user-facing Unreleased/Fixes entry describing the fail-closed gateway config-mutation allowlist, and extend the allowlist so Telegram topic-level paths like channels.telegram.groups.<group>.topics.<topic>.requireMention stay agent-tunable instead of being rejected as protected after this change.
* fix(gateway): fail closed on runtime config edits * changelog + telegram topic requireMention depth Append a user-facing Unreleased/Fixes entry describing the fail-closed gateway config-mutation allowlist, and extend the allowlist so Telegram topic-level paths like channels.telegram.groups.<group>.topics.<topic>.requireMention stay agent-tunable instead of being rejected as protected after this change.
* fix(gateway): fail closed on runtime config edits * changelog + telegram topic requireMention depth Append a user-facing Unreleased/Fixes entry describing the fail-closed gateway config-mutation allowlist, and extend the allowlist so Telegram topic-level paths like channels.telegram.groups.<group>.topics.<topic>.requireMention stay agent-tunable instead of being rejected as protected after this change.
* fix(gateway): fail closed on runtime config edits * changelog + telegram topic requireMention depth Append a user-facing Unreleased/Fixes entry describing the fail-closed gateway config-mutation allowlist, and extend the allowlist so Telegram topic-level paths like channels.telegram.groups.<group>.topics.<topic>.requireMention stay agent-tunable instead of being rejected as protected after this change.
* fix(gateway): fail closed on runtime config edits * changelog + telegram topic requireMention depth Append a user-facing Unreleased/Fixes entry describing the fail-closed gateway config-mutation allowlist, and extend the allowlist so Telegram topic-level paths like channels.telegram.groups.<group>.topics.<topic>.requireMention stay agent-tunable instead of being rejected as protected after this change.
* fix(gateway): fail closed on runtime config edits * changelog + telegram topic requireMention depth Append a user-facing Unreleased/Fixes entry describing the fail-closed gateway config-mutation allowlist, and extend the allowlist so Telegram topic-level paths like channels.telegram.groups.<group>.topics.<topic>.requireMention stay agent-tunable instead of being rejected as protected after this change.
fix(gateway): fail closed on runtime config edits
Summary
gateway config.apply/config.patchrelied on a hand-maintained denylist, so agent-driven runtime edits could still mutate sensitive operator-controlled config outside the listed paths..github, CI, workflow, or automation changes; no new gateway RPCs; no config schema expansion beyond the runtime guard behavior.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause (if applicable)
config.patchapplies deep merge semantics, including merge-by-id behavior for keyed arrays, which made incomplete denylist coverage especially brittle.Regression Test Plan (if applicable)
src/agents/openclaw-gateway-tool.test.tssrc/agents/tools/gateway-tool-guard-coverage.test.tsgateway.remote.url,tools.allow,memory.qmd.command, andbrowser.executablePathremain blocked.User-visible / Behavior Changes
gateway config.applyandconfig.patchnow allow only the explicitly allowlisted prompt/model/mention-gating paths.Diagram (if applicable)
Security Impact (required)
Yes, explain risk + mitigation: the gateway tool can now mutate fewer config paths than before. This is a fail-closed reduction in surface area, with regression coverage added for newly reported sensitive paths.Repro + Verification
Environment
Steps
CI=1 pnpm exec vitest run --config test/vitest/vitest.agents.config.ts src/agents/openclaw-gateway-tool.test.tsCI=1 pnpm exec vitest run --config .artifacts/gateway-guard-vitest.config.tsgit diff --stat origin/main..origin-base-checkExpected
Actual
src/agents/openclaw-gateway-tool.test.ts: 25 tests passed.src/agents/tools/gateway-tool-guard-coverage.test.ts: 36 tests passed.src/agents/openclaw-gateway-tool.test.tssrc/agents/tools/gateway-tool-guard-coverage.test.tssrc/agents/tools/gateway-tool.tsEvidence
Attach at least one:
Human Verification (required)
What you personally verified (not just CI), and how:
gateway.remote.urlrewrites, blocked global tools policy rewrites, and allowed prompt/model/mention-gating edits through the new allowlist.mainbranch to current upstream history.Review Conversations
Compatibility / Migration
Risks and Mitigations
List only real risks for this PR. Add/remove entries as needed. If none, write
None.