“Exposing patches before CVEs since 2025”
Tuesday, September 1, 2026
Aug 11, 2026, 11:48 AM — apache/airflow
Commit: d1e5f1a61340581c0a5b3bc0bf7af57cbb95ec58
Author: Jonathan Brown
The Calendar view endpoint computes planned runs for cron-based timetables by iterating croniter until the year boundary with no upper bound, unlike the generic timetable path which caps at MAX_PLANNED_RUNS. Any authenticated user with Dag read access can create/view a Dag with a high-frequency cron schedule (e.g., a seconds-resolution cron) and trigger minutes of CPU consumption per request just by opening the Calendar tab, allowing them to pin/exhaust an API server worker with minimal effort.
for dt in dates_iter:
if dt is None or dt.year != year:
break
if dag.end_date and dt > dag.end_date:
1. Create/have access to a Dag scheduled with a seconds-resolution cron expression like "* * * * * *" and no end_date, with start_date early in the year.
2. As any user with Dag read access, call GET /ui/calendar/{dag_id} (or open the Calendar tab in the UI) near the start of the year.
3. The server-side computation iterates croniter ~31.5 million times synchronously in the request handler, consuming ~5.5 minutes of CPU per request. Repeating this request multiple times (or with multiple such Dags) can pin/exhaust API server worker capacity, denying service to other users.
Aug 11, 2026, 06:47 AM — keycloak/keycloak
Commit: 60c4d5e9321ff5462a772ceb896f8cb2e639e04b
Author: Pedro Igor
When creating a new user via the admin REST API, group memberships specified in the UserRepresentation were assigned without checking whether the admin had 'manage-membership' permission on the target groups. This allowed an admin with only user-creation privileges (but not group membership management) to add newly created users to arbitrary groups, including groups with elevated privileges, bypassing fine-grained admin permission (FGAP) checks.
public static void createGroups(KeycloakSession session, UserRepresentation userRep, RealmModel newRealm, UserModel user) {
if (userRep.getGroups() != null) {
for (String path : userRep.getGroups()) {
GroupModel group = KeycloakModelUtils.findGroupByPath(session, newRealm, path);
user.joinGroup(group);
}
}
}
As an admin who only has the 'manage' scope on the Users resource type (no 'manage-membership' on any group), send:
POST /admin/realms/{realm}/users
{
"username": "newuser",
"groups": ["/privileged-group"]
}
Before the patch, the user is created and joined to '/privileged-group' even though the admin lacks MANAGE_MEMBERSHIP permission on that group, effectively escalating the new user's privileges. After the patch, this request returns 403 Forbidden unless the admin has MANAGE_MEMBERSHIP on '/privileged-group'.
Aug 11, 2026, 06:17 AM — keycloak/keycloak
Commit: 2a890e22cbfe01b34fa2600aa090cb52fe11c83e
Author: Pedro Igor
Before the patch, a client could supply a claim_token with claims using the reserved 'kc.' prefix (e.g., kc.client.id, kc.realm.name, kc.time.date_time), which would override or spoof server-controlled evaluation context attributes used by authorization policies. Additionally, when both a UMA permission ticket and a claim_token contained claims with the same key, the claim_token value silently overrode the ticket value, allowing a requesting party to override claims that a resource server intentionally set when creating the permission ticket, potentially bypassing intended policy restrictions.
claims = JsonSerialization.readValue(Base64Url.decode(request.getClaimToken()), Map.class);
request.setClaims(claims);
...
attributes.put("kc.client.id", ...) // set after claims, but claim_token could still inject kc.* keys via ticket/claim merge without filtering
POST /realms/{realm}/protocol/openid-connect/token with grant_type=urn:ietf:params:oauth:grant-type:uma-ticket and claim_token=BASE64({"kc.client.id":["admin-client"],"kc.realm.name":["trusted-realm"]}) — before the patch these attacker-supplied 'kc.' claims would be merged into the evaluation context alongside (or overriding, depending on ordering/ticket claims) the genuine server-set values, allowing policies that check kc.client.id or kc.realm.name to be deceived and grant access they shouldn't. After the patch, all claims starting with 'kc.' are stripped from claim_token/ticket-derived claims before evaluation, and ticket claims take precedence over claim_token on key collision.
Aug 10, 2026, 05:52 PM — keycloak/keycloak
Patch landed 5 days 2 hours 20 minutes after CVE published
Commit: c933d20b973d21f56d3dabf6ecec37f1cd9d743a
Author: Stefan Guilhen
The searchLDAPByAttributes method used the LDAP_ENTRY_DN attribute value provided as a search parameter directly as the search base DN without validating that it lies within the configured usersDn subtree. This allowed an attacker (e.g. via the Admin REST API searchByAttributes endpoint) to query arbitrary LDAP entries outside the configured user search base, potentially exposing service accounts or other directory objects not meant to be treated as Keycloak users. The patch validates that the provided DN is a descendant of the configured usersDn before using it as the search base, returning an empty stream otherwise.
} else if (LDAPConstants.LDAP_ENTRY_DN.equals(attrName)) {
ldapQuery.setSearchDn(entry.getValue());
ldapQuery.setSearchScope(SearchControls.OBJECT_SCOPE);
}
Send an Admin REST API request: GET /admin/realms/{realm}/users?q=LDAP_ENTRY_DN:cn=outsideuser,ou=ServiceAccounts,dc=example,dc=org where the DN points to an LDAP entry outside the configured usersDn (e.g., ou=Users,dc=example,dc=org). Before the patch, Keycloak would perform an LDAP OBJECT_SCOPE search directly on that arbitrary DN and return the entry as a user object, exposing directory entries outside the intended user search scope.
Aug 10, 2026, 05:30 PM — argoproj/argo-cd
Commit: a37f1f1ad303dae728381260dfe35edeb7d55ab3
Author: Peter Jiang
The ServerSideDiff API decided whether to mask Secret data based on the caller-supplied liveResources\[i\].Kind/Group metadata rather than the actual kind of the target/live manifest being diffed. An attacker with get access to an Application could submit a liveResources entry with a spoofed non-Secret Kind (e.g., ConfigMap) paired with a real Secret target manifest at the same index, causing the server to skip masking and return the Secret's plaintext data (e.g., passwords, tokens) in the diff response.
if kind == kube.SecretKind && group == "" {
// mask logic based on caller-supplied kind/group
}
POST to ServerSideDiff with:
LiveResources: [{Group: "", Kind: "ConfigMap", Namespace: "default", Name: "decoy-config", LiveState: <decoy ConfigMap JSON>}]
TargetManifests: ["{\"apiVersion\":\"v1\",\"kind\":\"Secret\",\"metadata\":{\"name\":\"real-secret\",\"namespace\":\"default\"},\"type\":\"Opaque\",\"data\":{\"password\":\"czNjcjN0LWxlYWtlZA==\"}}"]
Because liveResources[0].Kind is spoofed as ConfigMap (not Secret), the server skips the Secret-masking branch even though the dry-run target is a real Secret, causing the response TargetState to contain the raw base64 secret value 'czNjcjN0LWxlYWtlZA==' instead of '++masked++'.
Aug 10, 2026, 04:03 PM — keycloak/keycloak
Patch landed 5 days 31 minutes after CVE published
Commit: 725d8aec32cb595f31b9cce349b331b3c971285b
Author: Stefan Guilhen
PathMatcher.matches() performed direct string comparisons against the raw target URI without normalizing matrix parameters, double slashes, dot segments, percent-encoding, or trailing slashes. An attacker could mutate a protected path (e.g. /api/admin) using techniques like matrix params, encoded characters, or path traversal so that it no longer exactly matched the restricted resource pattern but still routed to the same backend resource on the server, causing the request to instead match a permissive catch-all policy (e.g. /*) and bypass intended authorization/deny policies.
if (exactMatch(expectedUri, targetUri)) {
matchingUri = expectedUri;
}
...
if (targetUri.endsWith(protectedSuffix)) {
matchingAnySuffixPath = entry;
}
Given a UMA/Policy Enforcer configuration with a restricted resource pattern '/api/admin' (deny policy) and a catch-all '/*' resource (grant policy), an attacker requests one of the following mutated paths which the underlying server still routes to /api/admin but which fail the PathMatcher's exact string match, causing it to fall through to the permissive '/*' policy: GET /api/admin;jsessionid=abc123, GET /api//admin, GET /api/admin/, GET /api/%61dmin, or GET /api/foo/../admin. Because these do not exactly equal '/api/admin' as raw strings, the restrictive deny policy is skipped and the request is authorized under the catch-all grant policy, bypassing access control.
Aug 10, 2026, 11:59 AM — keycloak/keycloak
Commit: 1c2a84e20593a2beec385c36f730303c98500f4d
Author: Stefan Guilhen
The SCIM user schema mapping used `Attributes.nameSet()` to enumerate all user profile attributes for reads and filtering, rather than `getReadable()`, which respects per-attribute view permissions configured in the user profile. This allowed admin-only attributes (e.g., attributes restricted to admin role via UPAttributePermissions) to be exposed and searchable through the SCIM API by users who should not have view access, bypassing the user-profile permission model.
Set<String> names = new HashSet<>(attributes.nameSet());
...
for (String name : profile.getAttributes().nameSet()) {
...
for (String modelName : attributes.nameSet()) {
Configure a user-profile attribute (e.g., 'department') with view permissions restricted to ROLE_ADMIN only. Then, as a non-admin SCIM client with only VIEW_USERS/QUERY_USERS scope, issue: GET /scim/v2/Users/{id}?attributes=urn:keycloak:params:scim:schemas:extension:realm:1.0:User:department or a filter query GET /scim/v2/Users?filter=urn:keycloak:params:scim:schemas:extension:realm:1.0:User:department eq "secret-value" — before the patch, the admin-restricted attribute value is returned/matched despite the requester lacking view permission on that profile attribute.
Aug 10, 2026, 11:49 AM — keycloak/keycloak
Patch landed 4 days 17 hours 17 minutes after CVE published
Commit: 4e18ea7c45f518f73fcc72f9ba2bbc1e05a5e3dd
Author: Stefan Guilhen
The IdentityBrokerService failed to enforce the linkOnly restriction on the IdP-initiated SSO path, allowing an identity provider configured to only be used for account linking (not standalone login) to still authenticate users directly via SAML IdP-initiated SSO. This bypasses an administrator-configured security restriction meant to prevent an IdP from being used as a primary authentication mechanism, potentially allowing unauthorized login through a broker that was explicitly restricted to link-only usage.
if (federatedUser == null) {
logger.debugf("Federated user not found for provider '%s' and broker username '%s'", providerAlias, context.getUsername());
// proceeds to create/login without checking identityProviderConfig.isLinkOnly()
}
1. Admin configures a SAML IdP 'saml-leaf' with linkOnly=true so it can only be used to link accounts, not log in directly. 2. A user with an active SSO session on the provider realm navigates directly to the IdP-initiated SSO endpoint: GET /auth/realms/<provider-realm>/protocol/saml/clients/samlbroker 3. The provider IdP posts a signed SAML Response directly to POST /auth/realms/<consumer-realm>/broker/saml-leaf/endpoint/clients/<client_id>, bypassing the normal kc_idp_hint login flow where linkOnly is checked. 4. Before the patch, this request succeeds and logs the user in via the broker despite linkOnly=true, effectively bypassing the link-only restriction. After the patch, the request is rejected with 'Could not send authentication request to identity provider.'
Aug 10, 2026, 11:30 AM — keycloak/keycloak
Commit: 940f1e6b45b95407e962fb50f1afad6f24e0a922
Author: Ponshankar
The SearchQueryUtils.getFields() method accessed chars\[i+1\] without checking bounds when encountering a backslash escape character, causing an ArrayIndexOutOfBoundsException if the backslash was the last character in the query. Since this method parses user-supplied search query strings (e.g., admin console user search), a malformed query with a trailing backslash could trigger an unhandled exception, potentially causing a 500 error or disrupting request processing.
while (i < chars.length && chars[i] != ':') {
if (chars[i] == '\\') {
if (chars[i+1] == '\"') {
i++;
}
Send a search request with query string ending in an unterminated escape sequence, e.g. GET /admin/realms/{realm}/users?search=key\\ or search=key:val\\ — this causes chars[i+1] to be accessed when i is the last index in the array, throwing ArrayIndexOutOfBoundsException instead of a graceful validation error.
Aug 10, 2026, 09:45 AM — openclaw/openclaw
Commit: 1ff50d7e7d9db463682db10042d42177ef9d0488
Author: Pavan Kumar Gondhi
The Matrix plugin's 'permissions' action (used for verification-management operations like verification-bootstrap, which can reset cross-signing keys and set recovery keys) was gated only by an 'encryption' and 'verification' config flag, without checking whether the sender was actually the bot owner. Any Matrix user able to send messages/commands to the bot could invoke sensitive verification actions such as forcing a cross-signing reset or listing verification state, potentially compromising encryption trust or account security. The patch adds an explicit senderIsOwner check both when advertising the action in tool discovery and when dispatching it, throwing a ToolAuthorizationError for non-owners.
if (params.encryptionEnabled && params.gate("verification")) {
actions.add("permissions");
}
...
if (action === "permissions") {
const operation = normalizeLowercaseStringOrEmpty(...)
A non-owner Matrix user sends a message triggering the bot's action handler with:
{ action: "permissions", accountId: "ops", params: { operation: "verification-bootstrap", forceResetCrossSigning: true, recoveryKey: "attacker-key" } }
Prior to the patch, since senderIsOwner was not checked, this call would proceed to handleMatrixAction and reset cross-signing / set a recovery key even though the sender was not the bot owner, potentially hijacking encryption verification state. After the patch, the same call throws 'Matrix verification actions require owner access.' and is never dispatched.
Aug 10, 2026, 05:40 AM — openclaw/openclaw
Commit: eb9cac065fb52b73800accb5a664a25dc96e65e7
Author: Peter Steinberger
The memory-host SDK maintained its own stale SECRET_PATTERNS table for redacting sensitive data in error messages, which lacked coverage for payment card numbers, CVV codes, and other payment credentials that the canonical redactor (src/logging/redact.ts) covers. Additionally, ACP's error redaction depended on module load order via a fragile injection hook, and when operator-configured redactPatterns were non-empty they fully replaced (rather than extended) built-in provider-token patterns, causing default secret patterns to go unredacted. This meant sensitive data such as credit card numbers or provider tokens could leak unredacted into error logs/output at these security boundaries.
// packages/memory-host-sdk/src/host/error-utils.ts const SECRET_PATTERNS = [ /* stale subset, no PAYMENT_CREDENTIAL_* card/CVV patterns */ ]; // operator redactPatterns replace defaults entirely instead of extending them
Trigger a memory-host error whose message embeds a card number, e.g. throw new Error('Payment failed for card 4111111111111111 CVV 123'); before the patch, error-utils.ts's local SECRET_PATTERNS table has no card/CVV pattern, so the raw card number and CVV are written unredacted to logs/error output. Separately, configure logging.redactPatterns with a single custom pattern in an ACP-integrated deployment — pre-patch, this replaces the built-in provider-token regexes entirely, so an API key like sk-ABCDEF123456 embedded in an error is no longer redacted even though redaction is 'enabled'.
Aug 10, 2026, 05:09 AM — openclaw/openclaw
Commit: d85f5c117677a152e9cf55c2963bfef42f814bbd
Author: Pavan Kumar Gondhi
The MS Teams group message admission logic only fell back to the allowlist re-check when the sender access denial reason was exactly 'group_policy_not_allowlisted'. If the sender access group was missing or referenced an unsupported/unrecognized group type (e.g., a Discord-typed access group), the denial reason code would differ, causing the code to skip the deny path entirely and treat the message as admitted, allowing unauthorized senders' messages to be processed and persisted. The fix makes any '!senderAccess.allowed' result authoritative, ensuring fail-closed behavior regardless of the specific reason code.
if (!senderAccess.allowed && senderAccess.reasonCode === "group_policy_not_allowlisted") {
const allowMatch = resolveMSTeamsAllowlistMatch({
allowFrom: effectiveGroupAllowFrom,
senderId,
Configure msteams channel with groupPolicy: 'allowlist' and groupAllowFrom: ['accessGroup:operators'], but define 'operators' as an unsupported access group type (e.g., a discord.channelAudience config) or omit it entirely. An attacker sends a group message from an account not in any allowlist. Because senderAccess.reasonCode will not equal 'group_policy_not_allowlisted' (it will be something like 'access_group_missing' or 'access_group_unsupported'), the code skips the denial branch, and the attacker's message is admitted and processed by the agent instead of being dropped, bypassing the intended sender allowlist.
Aug 9, 2026, 04:26 PM — openclaw/openclaw
Commit: df725434f361c4b8e9115014113c490219fdade2
Author: Peter Steinberger
The chat display projection only sanitized audio blocks with `entry.source.type === 'base64'` and string `source.data`, failing to strip top-level `data` fields, non-string `source.data`, or filesystem/local path references (e.g. `file://`, `~/`, `C:\\`, UNC paths) embedded in audio block fields like `path`, `file`, `url`, `openUrl`. Authenticated WebSocket and HTTP/SSE history clients could therefore retrieve raw audio bytes or host-local filesystem paths that were persisted in transcript blocks, leaking sensitive local file locations or raw audio payloads that should have been redacted at the shared history boundary.
if (type === "audio" && entry.source && typeof entry.source === "object") {
const source = { ...(entry.source as Record<string, unknown>) };
if (source.type === "base64" && typeof source.data === "string") {
...
}
}
Persist a transcript message with a top-level audio data field or local path reference, e.g.:
{ role: "user", content: [ { type: "audio", mimeType: "audio/wav", data: "<base64 audio bytes>" }, { type: "audio", path: "/tmp/secret-recording.wav", url: "file:///Users/victim/private-audio.wav" } ] }
Before the patch, requesting chat.history over WebSocket or the HTTP/SSE history endpoint returns these blocks unmodified, exposing the raw base64 audio bytes and the host-local file path/URL to any authenticated client with history access.
Aug 8, 2026, 11:19 PM — openclaw/openclaw
Commit: 3f1f30d939940e3ae6d0bea0c224790fc996e547
Author: Masato Hoshino
The debug proxy capture cloned every response body and read it with no bound on inter-chunk wait time. A malicious or stalled remote server could send response headers plus a single chunk and then go silent indefinitely, leaving the clone() tee branch open forever; since a tee branch only settles when both branches cancel or reach EOF, this caused the caller's own cancellation and the subsequent transport release to hang, tying up connection/transport resources. The patch bounds the read with an idle timeout, cancelling the stalled branch and recording the exchange as 'stalled' metadata instead of blocking indefinitely.
const reader = body.getReader();
...
while (true) {
const { done, value } = await reader.read();
if (done) { break; }
...
}
A remote server (or MITM'd upstream) responds with valid headers and one small body chunk (e.g. Content-Type: application/json, body 'partial') over a streamed/chunked response, then never sends more data and never closes the connection. With the debug proxy capture enabled, this causes the capture's clone-body reader to await reader.read() forever, which keeps the response clone() tee branch open, which in turn prevents the caller's cancellation and the guarded transport release from ever completing — effectively hanging the connection/transport indefinitely per stalled request, allowing an attacker-controlled or unreliable backend to exhaust transport/connection resources over many requests.
Aug 8, 2026, 07:52 PM — openclaw/openclaw
Patch landed 8 days 22 hours 21 minutes after CVE published
Commit: 9c3e4ce43143859357d6f2905d87b6abe28454c3
Author: wanyongstar
normalizeActRequest recursively processed nested 'batch' actions without any depth limit, allowing a specially crafted POST /act request body (up to the 1MB limit) with tens of thousands of nested batch wrappers to cause unbounded recursion and crash the process with a RangeError (stack overflow) before validation checks could run. The patch introduces a depth parameter bound by ACT_MAX_BATCH_DEPTH, rejecting deeply nested payloads early with a clear error instead of overflowing the call stack.
function normalizeBatchAction(value: unknown): BrowserActRequest {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error("batch actions must be objects");
}
return normalizeActRequest(value as Record<string, unknown>, { source: "batch" });
}
POST /act with a JSON body constructed by nesting {"kind":"batch","actions":[...]} 30,000 levels deep around a leaf {"kind":"click","ref":"1"} action (total size under the 1MB body limit). Before the patch, normalizeActRequest recurses into each nested batch level, exceeding the JS call stack and throwing 'RangeError: Maximum call stack size exceeded', crashing/denying the request handling instead of returning a clean 400 validation error, potentially destabilizing the server process.
Aug 8, 2026, 07:24 PM — openclaw/openclaw
Commit: fe908cf309a8acf68ab1d73caded8c649c3abded
Author: wanyongstar
The decodeMatrixEnvAccountToken function used Number.isFinite to validate hex-decoded code points before passing them to String.fromCodePoint, but Number.isFinite does not bound values to the valid Unicode range (0x0-0x10FFFF). A crafted environment variable name with a hex escape exceeding 0x10FFFF causes String.fromCodePoint to throw an uncaught RangeError, crashing listMatrixEnvAccountIds and preventing discovery of all Matrix accounts during startup/doctor checks. The patch adds an explicit upper-bound check (codePoint > 0x10ffff) to reject such tokens gracefully.
const codePoint = hex ? Number.parseInt(hex, 16) : Number.NaN;
if (!Number.isFinite(codePoint)) {
return undefined;
}
const char = String.fromCodePoint(codePoint);
Set an environment variable named `MATRIX_A_X110000_B_HOMESERVER=https://matrix.example.org` (or with `_XFFFFFFFF_`) and call listMatrixEnvAccountIds(process.env). The hex value 0x110000 exceeds the max Unicode code point 0x10FFFF, so String.fromCodePoint(0x110000) throws `RangeError: Invalid code point 1114112`, causing the function to throw uncaught and crashing discovery of every Matrix account configured via environment variables at startup or during doctor checks.
Aug 8, 2026, 03:51 AM — openclaw/openclaw
Commit: 0eeb9db21620ca63da1df7023eb9fe3d2615118b
Author: zengLingbiao
The voice-call provider clients (Twilio/Telnyx/Plivo) built error messages by directly embedding the raw provider HTTP error response body, which can echo back credential-bearing request details such as the Authorization header value, API keys, or account tokens. These unredacted error messages were thrown and could propagate into application logs or user-facing diagnostics, exposing secrets to anyone with access to logs or error output. The patch adds `redactSensitiveText` to strip credential-like content from the error snippet before it is used in thrown errors.
export async function readProviderErrorResponseSnippet(response: Response): Promise<string> {
const prefix = await readResponseTextPrefix(response, PROVIDER_ERROR_RESPONSE_MAX_BYTES);
return prefix.truncated ? appendTruncatedSuffix(prefix.text) : prefix.text;
}
A malicious or misconfigured provider endpoint returns a 401 response body such as:
{"message":"Authentication failed","detail":"Authorization: Basic QUMxMjM6c3VwZXItc2VjcmV0LWF1dGgtdG9rZW4 rejected; retry with api_key=sk-live-1234567890abcdef"}
Before the patch, guardedJsonApiRequest throws `new Error('provider error: 401 ' + errorText)`, embedding the raw Basic auth token and api_key directly in the thrown Error message, which then flows into application logs / user-facing error diagnostics — leaking the caller's credential material. After the patch, `redactSensitiveText` replaces the secret substrings with '***' before the error is constructed.
Aug 7, 2026, 02:43 PM — keycloak/keycloak
Commit: 4fcf214c588a988f3a9cf2536ae69621e56b58e2
Author: Marie Daly
The Dynamic Client Registration update endpoint allowed a client authenticated only with a Registration Access Token (RAT) - a token scoped to managing that single client's own registration - to change the client's protocol field (e.g., from openid-connect to saml). This let an attacker in possession of an OIDC client's RAT silently convert it into a SAML client, bypassing protocol-specific validations and policies enforced on client type, effectively an unintended privilege/scope escalation. The patch adds a check that rejects any update request containing a different protocol value when the caller authenticated via RAT.
ClientResource.updateClientServiceAccount(session, client, rep.isServiceAccountsEnabled()); RepresentationToModel.updateClient(rep, client, session); RepresentationToModel.updateClientProtocolMappers(rep, client);
1. Register an OIDC client via DCR (POST /clients-registrations/openid-connect) to obtain a registration_access_token bound to that client.
2. Using only that RAT (not an admin token), send: PUT /clients-registrations/openid-connect/{clientId} with body {"clientId": "<same>", "protocol": "saml", ...} and Authorization: Bearer <registration_access_token>.
3. Before the patch, the server accepts the update and converts the client's protocol to 'saml', bypassing intended admin-only control over protocol type. After the patch, the server returns 400 invalid_client_metadata 'Protocol cannot be changed via registration access token'.
Aug 7, 2026, 12:38 PM — keycloak/keycloak
Patch landed 1 day 18 hours 6 minutes after CVE published
Commit: bdeb57f739041af094d1ead342edf26f775272de
Author: Peter Skopek
The ProtocolMappersClientRegistrationPolicy only validated new protocol mappers against the allowed provider list, but for existing mappers (identified by ID) it skipped the type check entirely, only comparing configuration. This allowed a client (via Dynamic Client Registration, DCR) to swap an existing allowed mapper's type to a disallowed/dangerous mapper type (e.g., a hardcoded role mapper) while keeping the same mapper ID, bypassing the client registration policy restrictions and potentially escalating privileges via hardcoded roles or claims.
if (allowedMapperProviders.contains(mapperType)) { continue; }
if (clientModel == null) { failWithProtocolMapperTypeNotAllowedError(...); return; }
// existing mapper lookup by ID, then only config compared, no type check
Map<String, String> modelConfig = mapperModel.getConfig();
1. Register a client via DCR with an allowed mapper type (e.g., UserAttributeMapper) named 'swap-mapper'. 2. Obtain the registration access token and fetch the client representation via clientResource.toRepresentation(). 3. In the update payload, change the same mapper's 'protocolMapper' field to a disallowed type such as HardcodedRole.PROVIDER_ID (e.g., granting realm-management.manage-users), while keeping the same mapper ID. 4. Submit the update via DCR PUT request. Before the patch, this succeeds because only config equality is checked for existing mappers, letting an attacker silently swap in a malicious hardcoded-role mapper to escalate privileges. After the patch, the update is rejected with 403 'ProtocolMapper type not allowed'.
Aug 7, 2026, 11:59 AM — keycloak/keycloak
Commit: d60cebd37a415fbb37b700b2e9b909641c240c2e
Author: Awambeng
The AccountIssuedVerifiableCredentialResource#delete endpoint deleted issued verifiable credentials by ID using a global lookup (session.users().removeIssuedVerifiableCredential(credentialId)) without verifying that the credential belonged to the authenticated user. Any authenticated user with the manage-account or manage-verifiable-credentials role could delete another user's issued verifiable credential simply by supplying that credential's ID, since the credential ID was not scoped to the requesting user.
boolean removed = session.users().removeIssuedVerifiableCredential(credentialId);
if (!removed) {
throw new NotFoundException("Issued credential not found");
}
As User A (with manage-account role), issue: DELETE /realms/{realm}/account/issued-verifiable-credentials/{credentialIdOwnedByUserB} with Authorization: Bearer <UserA_token>. Because the underlying removeIssuedVerifiableCredential(credentialId) performs a primary-key lookup without checking ownership, User B's credential is deleted even though User A never owned it, resulting in unauthorized deletion of another user's data (204 response instead of 403/404).
Aug 7, 2026, 10:58 AM — openclaw/openclaw
Patch landed 7 days 10 hours 27 minutes after CVE published
Commit: 8994c7799ba3c8700f0fc0f7201e4695ee140a20
Author: Peter Steinberger
The sub-agent hard-deny list (blocking tools like sessions_send, message, conversations_*, gateway) could be overridden by configuring those tool names in an operator's allow/alsoAllow config, allowing a sub-agent session to regain direct message-sending and session-control capabilities that are supposed to be permanently restricted to enforce the announce-chain communication contract. Additionally, the message tool was only disabled at spawn time for hidden launches, so resumed or dashboard-visible sub-agent sessions could rebuild their tool set without that restriction and message users directly, bypassing the intended isolation boundary.
const explicitAllow = new Set([...(allow ?? []), ...(alsoAllow ?? [])].map(normalizeToolName));
const deny = [
...resolveSubagentDenyListForRole(capabilities.role).filter(
(toolName) => !explicitAllow.has(normalizeToolName(toolName)),
),
...(configured?.deny ?? []),
];
An operator (or a malicious/compromised tool-profile preset) sets in config:
```
tools: { subagents: { tools: { alsoAllow: ["sessions_send", "message"] } } }
```
Because resolveSubagentToolPolicyForSession filtered the hard-deny list against alsoAllow entries, a spawned sub-agent session would then have sessions_send and message tools available, allowing it to directly message users or other sessions instead of going through the required announce chain — bypassing the intended sub-agent isolation/security boundary. After the patch, the hard-deny list is applied unconditionally regardless of allow/alsoAllow config, so this override no longer works.
Aug 7, 2026, 10:57 AM — openclaw/openclaw
Patch landed 7 days 13 hours 25 minutes after CVE published
Commit: e910324f101c77529641a9a3a613dde20a76bd96
Author: Peter Steinberger
Gateway connection URLs can embed basic-auth credentials or token/API-key query parameters. Before this patch, several diagnostic surfaces (`openclaw status`, `status --all` text/JSON, and `logs` CLI error output) printed the raw connection URL verbatim, including embedded secrets, and probe failure text/thrown errors could also echo credential-bearing URLs. This exposes live credentials to terminal output, log files, or bug reports pasted by operators.
const targetText = params.remoteUrlMissing
? `fallback ${params.gatewayConnection.url}`
: params.gatewayConnection.url;
...
error: resolveProbeFailureMessage(result),
...
const details = buildGatewayConnectionDetails({ url: opts.url });
Run `openclaw status --all --json` (or `openclaw logs --url wss://user:[email protected]/ws?token=secret&key=api-key`) against a remote gateway configured with a URL like `wss://user:[email protected]/ws?token=secret&key=api-key&X-Amz-Signature=signed`. Before the patch, the tool's status/JSON output or logs error diagnostics would include the raw URL string containing `password`, `token=secret`, `key=api-key`, and the signature verbatim, leaking credentials to anyone viewing the terminal output, CI logs, or a pasted bug report.
Aug 6, 2026, 11:21 PM — openclaw/openclaw
Commit: d64f5b6a096088f1739ecd0e8c2f7182a0e90ce7
Author: Leon-SK668
The Google Chat API client read error response bodies from remote/proxy servers and included the raw text verbatim in surfaced error messages without redaction. If a malicious or misconfigured upstream/proxy server reflected the request's Authorization header (bearer token) back in the error response body, that token would be exposed in error messages returned to users, logs, or downstream consumers. The fix applies forced redaction (redactToolPayloadText) to the error response text before it is used in any error message.
async function readGoogleChatErrorResponse(response: Response, label: string): Promise<string> {
return (
(await readResponseTextSnippet(response, {...})) ?? ""
);
}
A malicious/compromised proxy or upstream server responds to a Google Chat API request with a 403 status and body: {"error":{"message":"Chat permission denied"},"reflectedHeader":"Authorization: Bearer <secret-token>"}. Before the patch, deleteGoogleChatMessage (and other API calls) would throw an Error whose message contains the full string 'Authorization: Bearer <secret-token>', exposing the real bearer token to anyone viewing the error (logs, UI, agent output). After the patch, redactToolPayloadText strips the token before it reaches the error message.
Aug 6, 2026, 11:14 PM — grafana/grafana
📈 Patch landed 24 days 22 hours 16 minutes before CVE published
Commit: e88041c9aee3ae2923327884b9c04be0325fe223
Author: Collin Fingar
The dashboard snapshot apiserver resolved snapshots by a global key without verifying that the snapshot belonged to the caller's organization/namespace. An authenticated user with snapshot delete permission in their own org could supply another org's public snapshot key to the `deletekey` subresource or `DELETE` endpoint, allowing them to read the secret deleteKey or delete a snapshot belonging to a different organization without proper authorization.
snap, err := s.Service.GetDashboardSnapshot(ctx, &dashboardsnapshots.GetDashboardSnapshotQuery{Key: name})
if err != nil || snap == nil {
return nil, false, err
}
// no org/namespace check before delete or before returning deleteKey
As a user authenticated in org 2 with snapshot delete RBAC permission, call GET /apis/dashboard.grafana.app/v0alpha1/namespaces/org-2/snapshots/<org-1-snapshot-key>/deletekey using the public snapshot key of a snapshot belonging to org 1. Before the patch, this returns the secret deleteKey for the org-1 snapshot; the caller can then issue DELETE /apis/dashboard.grafana.app/v0alpha1/namespaces/org-2/snapshots/<org-1-snapshot-key> to delete the org-1 snapshot, despite having no legitimate access to org 1's resources.
Aug 6, 2026, 02:26 PM — keycloak/keycloak
Patch landed 22 hours 53 minutes after CVE published
Commit: a569ef539a9d73e8b53634d6f368254a7052e2a8
Author: Alexander Schwartz
The Micrometer user event metrics listener created a distinct time-series counter for each unique combination of tags, including the 'error' tag (and clientId tag) which is derived from user-controllable input such as arbitrary exception messages or client IDs. An attacker could trigger many different error messages or use many distinct client IDs to generate an unbounded number of unique metric tag values, leading to unbounded memory growth in the metrics registry and potential OOM in Keycloak or downstream observability systems. The patch mitigates this by generifying error codes (e.g., using Errors.GENERIC/INVALID_SCOPE/CLIENT_NOT_FOUND instead of raw exception messages) and adding a MeterFilter that caps the number of distinct tag values (default 10000) for the error and clientId tags, denying new tag values once the limit is reached.
event.error(e.getMessage());
...
String msg = String.format("No client with clientId: %s found.", clientId);
event.error(msg);
...
String msg = String.format("Scope id %s does not exist for client %s.", scopeRepresentation, consent.getClient().getName());
event.error(msg);
An attacker repeatedly calls the account REST API endpoint (e.g., DELETE /realms/{realm}/account/applications/{clientId}/consent) with thousands of distinct, attacker-controlled clientId values such as clientId1, clientId2, ..., clientidN. Each request triggers event.error(msg) with a unique error message string like 'No client with clientId: clientId12345 found.', which becomes a unique 'error' tag value in the keycloak.user Micrometer counter, causing the metrics registry to accumulate unbounded unique time series and eventually exhaust JVM memory.