Skip to content

2026-07-13 - Privilege Escalation allowing a Read-Only API Token to obtain Read & Write access in API Token Authentication for Jira, Confluence, and Bitbucket

Summary

A read-only API token could, under certain configurations, be used to obtain a Read & Write API token for the same user, resulting in a privilege escalation.

Advisory Release Date

7/13/2026

Affected Products

API Token Authentication for Jira
API Token Authentication for Confluence
API Token Authentication for Bitbucket

Affected Versions

1.5.0 – 2.7.9 (Jira, Confluence)
1.6.1 – 2.7.9 (Bitbucket)

Fixed Version

2.8.0

CVSS Score

6.5 (Medium) base / 5.9 (Medium) temporal

CVSS Vector

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N/E:P/RL:O/RC:C

Summary

This advisory discloses a medium-severity security vulnerability affecting versions up to and including 2.7.9 of the API Token Authentication for Jira, Confluence, and Bitbucket. Please upgrade your app installation to version 2.8.0 or later to fix this vulnerability.

Details

API tokens can be issued with a Read-Only scope, which is intended as a lower-trust credential, safe to hand to automation, monitoring, or third parties that cannot modify data. This issue meant that, under certain configurations, a Read-Only token could be used to obtain a token with Read & Write scope for the same user, granting write capability that the Read-Only token itself was denied.

The issue could be exploited when all of the following were true:

  • An actor was in possession of a valid Read-Only API token, and

  • On the Permission tab in the app settings, "Allow everyone to create tokens for themselves" was enabled (or restricted to one or more groups only instead) and

  • On the Permission tab in the app settings, "Users may only create Read Only tokens," was not enabled.

In instances where "Users may only create Read Only tokens" was enabled, regular users could not obtain a Read & Write token and are therefore not exposed to the privilege-escalation impact described here.

Neither administrator privileges nor a more privileged network position than anyone with a legitimate read-only token wouldn’t have anyway (i.e. we assume someone in possession of a read-only token could already access your Jira/Confluence/Bitbucket to use this token) are required to exploit this issue.

What You Need to Do

Step 1: Mitigation

Update the app to version 2.8.0 or later. After upgrading, read-only tokens can no longer be used to create new tokens of any scope.

If you cannot upgrade immediately, enable "Users may only create Read Only tokens" in the app's permission settings. Note that this also prevents legitimate users authenticating regularly (i.e. not with an API token from our app) from creating read-write API tokens for themselves and it still allows existing read-only API tokens to create new read-only API tokens.

To prevent that, disable “Allow everyone to create tokens for themselves”, which also prevents legitimate users authenticating regularly (i.e. not with an API token from our app) from creating API tokens for themselves. You can still create such API tokens for these users as a system administrator.

As an additional precaution, review and rotate existing read-only tokens, particularly those shared with automation or third parties. The paragraph below describes how to review existing user tokens.

Step 2: Review existing tokens

Please note that the information gathered using these methods do not contain the information whether they have been created using a read-only token since the app unfortunately does not log this data. Rather, they can serve as a starting point for an investigation into whether this user should legitimately have a read-write token while also having read-only tokens.

We are at this point not aware of any exploitation of this vulnerability, but recommend conducting a review anyway out of an abundance of caution.

Retrieve Token Details from Database

The following section contains queries in PostgreSQL syntax to help you identify the users and tokens that are affected.

Read-Only and Read & Write Token Count with User Details

The query below returns the number of read-only and read-write tokens per user, along with some user details, provided that each user has tokens with both types of scopes. You could remove the WHERE condition in the first query per platform (WHERE tc.read_only_tokens > 0 AND tc.read_write_tokens > 0) if you want to list users who have either type. Please be aware that the full name and email in the results may be incorrect when the user has the same username across multiple directories. However, the username and key are always valid.

Jira
List All Users Who Have Read-Only and Read & Write Tokens
  1. WITH token_counts AS (
  2. SELECT "USER_KEY" AS user_key,
  3. SUM(CASE WHEN "TOKEN_SCOPE" = 1 THEN 1 ELSE 0 END) AS read_only_tokens,
  4. SUM(CASE WHEN "TOKEN_SCOPE" = 2 THEN 1 ELSE 0 END) AS read_write_tokens
  5. FROM "AO_01D1A0_ATA_TOKEN"
  6. GROUP BY "USER_KEY"
  7. )
  8. SELECT DISTINCT ON (tc.user_key)
  9. tc.user_key,
  10. COALESCE(cu.user_name, au.lower_user_name) AS username,
  11. cu.display_name AS full_name, cu.email_address AS email, cu.active AS user_active,
  12. tc.read_only_tokens, tc.read_write_tokens
  13. FROM token_counts tc
  14. LEFT JOIN app_user au ON au.user_key = tc.user_key
  15. LEFT JOIN cwd_user cu ON cu.lower_user_name = au.lower_user_name
  16. WHERE tc.read_only_tokens > 0 AND tc.read_write_tokens > 0
  17. ORDER BY tc.user_key, cu.active DESC NULLS LAST, cu.id;
List All Read & Write Tokens of Those Users
  1. WITH candidates AS (
  2. SELECT DISTINCT ON (t."ID")
  3. t."ID" AS token_id, t."USER_KEY" AS user_key,
  4. COALESCE(cu.user_name, au.lower_user_name) AS username,
  5. cu.display_name AS full_name, cu.email_address AS email,
  6. t."DESCRIPTION" AS description,
  7. t."CREATED" AS created, t."LAST_ACCESSED" AS last_accessed, t."VALID_UNTIL" AS valid_until
  8. FROM "AO_01D1A0_ATA_TOKEN" t
  9. LEFT JOIN app_user au ON au.user_key = t."USER_KEY"
  10. LEFT JOIN cwd_user cu ON cu.lower_user_name = au.lower_user_name
  11. WHERE t."TOKEN_SCOPE" = 2
  12. AND t."USER_KEY" IN (SELECT "USER_KEY" FROM "AO_01D1A0_ATA_TOKEN" WHERE "TOKEN_SCOPE" = 1)
  13. -- optional, hide expired: AND ("VALID_UNTIL" <= 0 OR "VALID_UNTIL" >= (EXTRACT(EPOCH FROM now()) * 1000)::bigint)
  14. -- optional, hide never-used: AND "LAST_ACCESSED" <> 0
  15. ORDER BY t."ID", cu.active DESC NULLS LAST, cu.id
  16. )
  17. SELECT token_id, user_key, username, full_name, email,
  18. (valid_until > 0 AND valid_until < (EXTRACT(EPOCH FROM now()) * 1000)::bigint) AS is_expired,
  19. CASE WHEN valid_until <= 0 THEN NULL ELSE to_timestamp(valid_until / 1000) END AS valid_until_at,
  20. to_timestamp(created / 1000) AS created_at,
  21. CASE WHEN last_accessed = 0 THEN NULL ELSE to_timestamp(last_accessed / 1000) END AS last_accessed_at,
  22. description
  23. FROM candidates
  24. ORDER BY is_expired ASC, last_accessed DESC;
Confluence
List All Users Who Have Read-Only and Read & Write Tokens
  1. WITH token_counts AS (
  2. SELECT "USER_KEY" AS user_key,
  3. SUM(CASE WHEN "TOKEN_SCOPE" = 1 THEN 1 ELSE 0 END) AS read_only_tokens,
  4. SUM(CASE WHEN "TOKEN_SCOPE" = 2 THEN 1 ELSE 0 END) AS read_write_tokens
  5. FROM "AO_01D1A0_ATA_TOKEN"
  6. GROUP BY "USER_KEY"
  7. )
  8. SELECT DISTINCT ON (tc.user_key)
  9. tc.user_key,
  10. um.username AS username,
  11. cu.display_name AS full_name, cu.email_address AS email, cu.active AS user_active,
  12. tc.read_only_tokens, tc.read_write_tokens
  13. FROM token_counts tc
  14. LEFT JOIN user_mapping um ON um.user_key = tc.user_key
  15. LEFT JOIN cwd_user cu ON cu.lower_user_name = um.lower_username
  16. WHERE tc.read_only_tokens > 0 AND tc.read_write_tokens > 0
  17. ORDER BY tc.user_key, cu.active DESC NULLS LAST, cu.id;
List All Read & Write Tokens of Those Users
  1. WITH candidates AS (
  2. SELECT DISTINCT ON (t."ID")
  3. t."ID" AS token_id, t."USER_KEY" AS user_key,
  4. um.username AS username,
  5. cu.display_name AS full_name, cu.email_address AS email,
  6. t."DESCRIPTION" AS description,
  7. t."CREATED" AS created, t."LAST_ACCESSED" AS last_accessed, t."VALID_UNTIL" AS valid_until
  8. FROM "AO_01D1A0_ATA_TOKEN" t
  9. LEFT JOIN user_mapping um ON um.user_key = t."USER_KEY"
  10. LEFT JOIN cwd_user cu ON cu.lower_user_name = um.lower_username
  11. WHERE t."TOKEN_SCOPE" = 2
  12. AND t."USER_KEY" IN (SELECT "USER_KEY" FROM "AO_01D1A0_ATA_TOKEN" WHERE "TOKEN_SCOPE" = 1)
  13. -- optional, hide expired: AND ("VALID_UNTIL" <= 0 OR "VALID_UNTIL" >= (EXTRACT(EPOCH FROM now()) * 1000)::bigint)
  14. -- optional, hide never-used: AND "LAST_ACCESSED" <> 0
  15. ORDER BY t."ID", cu.active DESC NULLS LAST, cu.id
  16. )
  17. SELECT token_id, user_key, username, full_name, email,
  18. (valid_until > 0 AND valid_until < (EXTRACT(EPOCH FROM now()) * 1000)::bigint) AS is_expired,
  19. CASE WHEN valid_until <= 0 THEN NULL ELSE to_timestamp(valid_until / 1000) END AS valid_until_at,
  20. to_timestamp(created / 1000) AS created_at,
  21. CASE WHEN last_accessed = 0 THEN NULL ELSE to_timestamp(last_accessed / 1000) END AS last_accessed_at,
  22. description
  23. FROM candidates
  24. ORDER BY is_expired ASC, last_accessed DESC;
Bitbucket
List All Users Who Have Read-Only and Read & Write Tokens
  1. WITH token_counts AS (
  2. SELECT "USER_KEY" AS user_key,
  3. SUM(CASE WHEN "TOKEN_SCOPE" = 1 THEN 1 ELSE 0 END) AS read_only_tokens,
  4. SUM(CASE WHEN "TOKEN_SCOPE" = 2 THEN 1 ELSE 0 END) AS read_write_tokens
  5. FROM "AO_01D1A0_ATA_TOKEN"
  6. GROUP BY "USER_KEY"
  7. )
  8. SELECT DISTINCT ON (tc.user_key)
  9. tc.user_key,
  10. su.name AS username,
  11. cu.display_name AS full_name, cu.email_address AS email, cu.is_active AS user_active,
  12. tc.read_only_tokens, tc.read_write_tokens
  13. FROM token_counts tc
  14. LEFT JOIN sta_normal_user su ON su.user_id = CAST(tc.user_key AS integer)
  15. LEFT JOIN cwd_user cu ON cu.lower_user_name = LOWER(su.name)
  16. WHERE tc.read_only_tokens > 0 AND tc.read_write_tokens > 0
  17. ORDER BY tc.user_key, cu.is_active DESC NULLS LAST, cu.id;
List All Read & Write Tokens of Those Users
  1. WITH candidates AS (
  2. SELECT DISTINCT ON (t."ID")
  3. t."ID" AS token_id, t."USER_KEY" AS user_key,
  4. su.name AS username,
  5. cu.display_name AS full_name, cu.email_address AS email,
  6. t."DESCRIPTION" AS description,
  7. t."CREATED" AS created, t."LAST_ACCESSED" AS last_accessed, t."VALID_UNTIL" AS valid_until
  8. FROM "AO_01D1A0_ATA_TOKEN" t
  9. LEFT JOIN sta_normal_user su ON su.user_id = CAST(t."USER_KEY" AS integer)
  10. LEFT JOIN cwd_user cu ON cu.lower_user_name = LOWER(su.name)
  11. WHERE t."TOKEN_SCOPE" = 2
  12. AND t."USER_KEY" IN (SELECT "USER_KEY" FROM "AO_01D1A0_ATA_TOKEN" WHERE "TOKEN_SCOPE" = 1)
  13. -- optional, hide expired: AND ("VALID_UNTIL" <= 0 OR "VALID_UNTIL" >= (EXTRACT(EPOCH FROM now()) * 1000)::bigint)
  14. -- optional, hide never-used: AND "LAST_ACCESSED" <> 0
  15. ORDER BY t."ID", cu.is_active DESC NULLS LAST, cu.id
  16. )
  17. SELECT token_id, user_key, username, full_name, email,
  18. (valid_until > 0 AND valid_until < (EXTRACT(EPOCH FROM now()) * 1000)::bigint) AS is_expired,
  19. CASE WHEN valid_until <= 0 THEN NULL ELSE to_timestamp(valid_until / 1000) END AS valid_until_at,
  20. to_timestamp(created / 1000) AS created_at,
  21. CASE WHEN last_accessed = 0 THEN NULL ELSE to_timestamp(last_accessed / 1000) END AS last_accessed_at,
  22. description
  23. FROM candidates
  24. ORDER BY is_expired ASC, last_accessed DESC;

Retrieve Token Details from the Audit Log

The Atlassian audit log contains a record for each token created. You can filter the audit log by category “Token Management” and summary “Token created”:

CleanShot 2026-07-06 at 12.15.49@2x-20260706-101638.png

The author is the user who created the token; the “Affected object(s)” column at the end has the same value. The author is only different if the token has been created by another user who owns the “Create & Delete Token On Behalf Permission”. While filtering the log won’t tell you who created a Read & Write scoped token with a Read-Only token, you could still identify any Read & Write scoped tokens that those users shouldn’t have. Please be aware of your audit log retention period; you might no longer find a record for every token. But contrary to the database queries, it will still contain a record for tokens that might have been deleted (revoked) in the meantime.

Support

If you have any questions or need assistance, please contact us through our support portal.