CVE-2019-19687
Description
OpenStack Keystone 15.0.0 and 16.0.0 is affected by Data Leakage in the list credentials API. Any user with a role on a project is able to list any credentials with the /v3/credentials API when enforce_scope is false. Users with a role on a project are able to view any other users' credentials, which could (for example) leak sign-on information for Time-based One Time Passwords (TOTP). Deployments with enforce_scope set to false are affected. (There will be a slight performance impact for the list credentials API once this issue is fixed.)
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
OpenStack Keystone list credentials API leaks all credentials to any project user when enforce_scope is false, potentially exposing TOTP secrets.
The vulnerability is an authorization bypass in OpenStack Keystone's list credentials API (/v3/credentials). When the enforce_scope configuration option is set to false (the default), the API does not properly restrict credential listing to only the user's own credentials, allowing any user with a role on a project to list all credentials within that project. This includes credentials of other users [1].
To exploit this, an attacker only needs a valid token for a user that has any role (e.g., member, reader) on a target project. No additional privileges are required. The attacker can then call the list credentials API to retrieve all credentials stored for that project, including TOTP keys [1].
The impact is significant as credentials may include Time-based One Time Password (TOTP) secrets, enabling an attacker to compromise multi-factor authentication and gain unauthorized access to the project. The leak also exposes any other credential types stored in Keystone [1].
The issue affects Keystone 15.0.0 and 16.0.0. Patches have been committed to enforce proper scope checking when listing credentials [2][3][4]. Administrators are advised to set enforce_scope to true or apply the patches. A slight performance impact is expected after the fix [1].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
keystonePyPI | >= 15.0.0, < 15.0.1 | 15.0.1 |
keystonePyPI | >= 16.0.0, < 16.0.1 | 16.0.1 |
Affected products
2- OpenStack/Keystonedescription
Patches
317947516b009Fix credential list for project members
3 files changed · +151 −7
keystone/api/credentials.py+16 −7 modified@@ -101,13 +101,22 @@ def _list_credentials(self): # If the request was filtered, make sure to return only the # credentials specific to that user. This makes it so that users with # roles on projects can't see credentials that aren't theirs. - if (not self.oslo_context.system_scope and - CONF.oslo_policy.enforce_scope): - filtered_refs = [] - for ref in refs: - if ref['user_id'] == target['credential']['user_id']: - filtered_refs.append(ref) - refs = filtered_refs + filtered_refs = [] + for ref in refs: + # Check each credential again to make sure the user has access to + # it, either by owning it, being a project admin with + # enforce_scope=false, being a system user, or having some other + # custom policy that allows access. + try: + cred = PROVIDERS.credential_api.get_credential(ref['id']) + ENFORCER.enforce_call( + action='identity:get_credential', + target_attr={'credential': cred} + ) + filtered_refs.append(ref) + except exception.Forbidden: + pass + refs = filtered_refs refs = [self._blob_to_json(r) for r in refs] return self.wrap_collection(refs, hints=hints)
keystone/tests/unit/protection/v3/test_credentials.py+112 −0 modified@@ -1138,3 +1138,115 @@ def _override_policy(self): 'identity:delete_credential': cp.SYSTEM_ADMIN_OR_CRED_OWNER } f.write(jsonutils.dumps(overridden_policies)) + + +class ProjectReaderTestsEnforceScopeFalse(base_classes.TestCaseWithBootstrap, + common_auth.AuthTestMixin, + _UserCredentialTests, + _ProjectUsersTests): + + def setUp(self): + super(ProjectReaderTestsEnforceScopeFalse, self).setUp() + self.loadapp() + self.useFixture(ksfixtures.Policy(self.config_fixture)) + self.config_fixture.config(group='oslo_policy', enforce_scope=False) + + project_reader = unit.new_user_ref( + domain_id=CONF.identity.default_domain_id + ) + self.user_id = PROVIDERS.identity_api.create_user( + project_reader + )['id'] + project = unit.new_project_ref( + domain_id=CONF.identity.default_domain_id + ) + self.project_id = PROVIDERS.resource_api.create_project( + project['id'], project + )['id'] + PROVIDERS.assignment_api.create_grant( + self.bootstrapper.reader_role_id, user_id=self.user_id, + project_id=self.project_id + ) + + auth = self.build_authentication_request( + user_id=self.user_id, + password=project_reader['password'], + project_id=self.project_id + ) + + # Grab a token using the persona we're testing and prepare headers + # for requests we'll be making in the tests. + with self.test_client() as c: + r = c.post('/v3/auth/tokens', json=auth) + self.token_id = r.headers['X-Subject-Token'] + self.headers = {'X-Auth-Token': self.token_id} + + +class ProjectMemberTestsEnforceScopeFalse(base_classes.TestCaseWithBootstrap, + common_auth.AuthTestMixin, + _UserCredentialTests, + _ProjectUsersTests): + + def setUp(self): + super(ProjectMemberTestsEnforceScopeFalse, self).setUp() + self.loadapp() + self.useFixture(ksfixtures.Policy(self.config_fixture)) + self.config_fixture.config(group='oslo_policy', enforce_scope=False) + + project_member = unit.new_user_ref( + domain_id=CONF.identity.default_domain_id + ) + self.user_id = PROVIDERS.identity_api.create_user( + project_member + )['id'] + project = unit.new_project_ref( + domain_id=CONF.identity.default_domain_id + ) + self.project_id = PROVIDERS.resource_api.create_project( + project['id'], project + )['id'] + PROVIDERS.assignment_api.create_grant( + self.bootstrapper.member_role_id, user_id=self.user_id, + project_id=self.project_id + ) + + auth = self.build_authentication_request( + user_id=self.user_id, + password=project_member['password'], + project_id=self.project_id + ) + + # Grab a token using the persona we're testing and prepare headers + # for requests we'll be making in the tests. + with self.test_client() as c: + r = c.post('/v3/auth/tokens', json=auth) + self.token_id = r.headers['X-Subject-Token'] + self.headers = {'X-Auth-Token': self.token_id} + + +class ProjectAdminTestsEnforceScopeFalse(base_classes.TestCaseWithBootstrap, + common_auth.AuthTestMixin, + _UserCredentialTests, + _SystemUserCredentialTests): + + def setUp(self): + super(ProjectAdminTestsEnforceScopeFalse, self).setUp() + self.loadapp() + self.useFixture(ksfixtures.Policy(self.config_fixture)) + self.config_fixture.config(group='oslo_policy', enforce_scope=False) + + # Reuse the system administrator account created during + # ``keystone-manage bootstrap`` + self.user_id = self.bootstrapper.admin_user_id + auth = self.build_authentication_request( + user_id=self.user_id, + password=self.bootstrapper.admin_password, + project_id=self.bootstrapper.project_id + ) + + # Grab a token using the persona we're testing and prepare headers + # for requests we'll be making in the tests. + with self.test_client() as c: + r = c.post('/v3/auth/tokens', json=auth) + self.token_id = r.headers['X-Subject-Token'] + self.headers = {'X-Auth-Token': self.token_id}
releasenotes/notes/bug-1855080-08b28181b7cb2470.yaml+23 −0 added@@ -0,0 +1,23 @@ +--- +critical: + - | + [`bug 1855080 <https://bugs.launchpad.net/keystone/+bug/1855080>`_] + An error in the policy target filtering inadvertently allowed any user to + list any credential object with the /v3/credentials API when + ``[oslo_policy]/enforce_scope`` was set to false, which is the default. + This has been addressed: users with non-admin roles on a project may not + list other users' credentials. However, users with the admin role on a + project may still list any users credentials when + ``[oslo_policy]/enforce_scope`` is false due to `bug 968696 + <https://bugs.launchpad.net/keystone/+bug/968696>`_. +security: + - | + [`bug 1855080 <https://bugs.launchpad.net/keystone/+bug/1855080>`_] + An error in the policy target filtering inadvertently allowed any user to + list any credential object with the /v3/credentials API when + ``[oslo_policy]/enforce_scope`` was set to false, which is the default. + This has been addressed: users with non-admin roles on a project may not + list other users' credentials. However, users with the admin role on a + project may still list any users credentials when + ``[oslo_policy]/enforce_scope`` is false due to `bug 968696 + <https://bugs.launchpad.net/keystone/+bug/968696>`_.
17c337dbdbfbFix credential list for project members
3 files changed · +151 −7
keystone/api/credentials.py+16 −7 modified@@ -101,13 +101,22 @@ def _list_credentials(self): # If the request was filtered, make sure to return only the # credentials specific to that user. This makes it so that users with # roles on projects can't see credentials that aren't theirs. - if (not self.oslo_context.system_scope and - CONF.oslo_policy.enforce_scope): - filtered_refs = [] - for ref in refs: - if ref['user_id'] == target['credential']['user_id']: - filtered_refs.append(ref) - refs = filtered_refs + filtered_refs = [] + for ref in refs: + # Check each credential again to make sure the user has access to + # it, either by owning it, being a project admin with + # enforce_scope=false, being a system user, or having some other + # custom policy that allows access. + try: + cred = PROVIDERS.credential_api.get_credential(ref['id']) + ENFORCER.enforce_call( + action='identity:get_credential', + target_attr={'credential': cred} + ) + filtered_refs.append(ref) + except exception.Forbidden: + pass + refs = filtered_refs refs = [self._blob_to_json(r) for r in refs] return self.wrap_collection(refs, hints=hints)
keystone/tests/protection/v3/test_credentials.py+112 −0 modified@@ -1138,3 +1138,115 @@ def _override_policy(self): 'identity:delete_credential': bp.SYSTEM_ADMIN_OR_CRED_OWNER } f.write(jsonutils.dumps(overridden_policies)) + + +class ProjectReaderTestsEnforceScopeFalse(base_classes.TestCaseWithBootstrap, + common_auth.AuthTestMixin, + _UserCredentialTests, + _ProjectUsersTests): + + def setUp(self): + super(ProjectReaderTestsEnforceScopeFalse, self).setUp() + self.loadapp() + self.useFixture(ksfixtures.Policy(self.config_fixture)) + self.config_fixture.config(group='oslo_policy', enforce_scope=False) + + project_reader = unit.new_user_ref( + domain_id=CONF.identity.default_domain_id + ) + self.user_id = PROVIDERS.identity_api.create_user( + project_reader + )['id'] + project = unit.new_project_ref( + domain_id=CONF.identity.default_domain_id + ) + self.project_id = PROVIDERS.resource_api.create_project( + project['id'], project + )['id'] + PROVIDERS.assignment_api.create_grant( + self.bootstrapper.reader_role_id, user_id=self.user_id, + project_id=self.project_id + ) + + auth = self.build_authentication_request( + user_id=self.user_id, + password=project_reader['password'], + project_id=self.project_id + ) + + # Grab a token using the persona we're testing and prepare headers + # for requests we'll be making in the tests. + with self.test_client() as c: + r = c.post('/v3/auth/tokens', json=auth) + self.token_id = r.headers['X-Subject-Token'] + self.headers = {'X-Auth-Token': self.token_id} + + +class ProjectMemberTestsEnforceScopeFalse(base_classes.TestCaseWithBootstrap, + common_auth.AuthTestMixin, + _UserCredentialTests, + _ProjectUsersTests): + + def setUp(self): + super(ProjectMemberTestsEnforceScopeFalse, self).setUp() + self.loadapp() + self.useFixture(ksfixtures.Policy(self.config_fixture)) + self.config_fixture.config(group='oslo_policy', enforce_scope=False) + + project_member = unit.new_user_ref( + domain_id=CONF.identity.default_domain_id + ) + self.user_id = PROVIDERS.identity_api.create_user( + project_member + )['id'] + project = unit.new_project_ref( + domain_id=CONF.identity.default_domain_id + ) + self.project_id = PROVIDERS.resource_api.create_project( + project['id'], project + )['id'] + PROVIDERS.assignment_api.create_grant( + self.bootstrapper.member_role_id, user_id=self.user_id, + project_id=self.project_id + ) + + auth = self.build_authentication_request( + user_id=self.user_id, + password=project_member['password'], + project_id=self.project_id + ) + + # Grab a token using the persona we're testing and prepare headers + # for requests we'll be making in the tests. + with self.test_client() as c: + r = c.post('/v3/auth/tokens', json=auth) + self.token_id = r.headers['X-Subject-Token'] + self.headers = {'X-Auth-Token': self.token_id} + + +class ProjectAdminTestsEnforceScopeFalse(base_classes.TestCaseWithBootstrap, + common_auth.AuthTestMixin, + _UserCredentialTests, + _SystemUserCredentialTests): + + def setUp(self): + super(ProjectAdminTestsEnforceScopeFalse, self).setUp() + self.loadapp() + self.useFixture(ksfixtures.Policy(self.config_fixture)) + self.config_fixture.config(group='oslo_policy', enforce_scope=False) + + # Reuse the system administrator account created during + # ``keystone-manage bootstrap`` + self.user_id = self.bootstrapper.admin_user_id + auth = self.build_authentication_request( + user_id=self.user_id, + password=self.bootstrapper.admin_password, + project_id=self.bootstrapper.project_id + ) + + # Grab a token using the persona we're testing and prepare headers + # for requests we'll be making in the tests. + with self.test_client() as c: + r = c.post('/v3/auth/tokens', json=auth) + self.token_id = r.headers['X-Subject-Token'] + self.headers = {'X-Auth-Token': self.token_id}
releasenotes/notes/bug-1855080-08b28181b7cb2470.yaml+23 −0 added@@ -0,0 +1,23 @@ +--- +critical: + - | + [`bug 1855080 <https://bugs.launchpad.net/keystone/+bug/1855080>`_] + An error in the policy target filtering inadvertently allowed any user to + list any credential object with the /v3/credentials API when + ``[oslo_policy]/enforce_scope`` was set to false, which is the default. + This has been addressed: users with non-admin roles on a project may not + list other users' credentials. However, users with the admin role on a + project may still list any users credentials when + ``[oslo_policy]/enforce_scope`` is false due to `bug 968696 + <https://bugs.launchpad.net/keystone/+bug/968696>`_. +security: + - | + [`bug 1855080 <https://bugs.launchpad.net/keystone/+bug/1855080>`_] + An error in the policy target filtering inadvertently allowed any user to + list any credential object with the /v3/credentials API when + ``[oslo_policy]/enforce_scope`` was set to false, which is the default. + This has been addressed: users with non-admin roles on a project may not + list other users' credentials. However, users with the admin role on a + project may still list any users credentials when + ``[oslo_policy]/enforce_scope`` is false due to `bug 968696 + <https://bugs.launchpad.net/keystone/+bug/968696>`_.
bd3f63787151Fix credential list for project members
3 files changed · +151 −7
keystone/api/credentials.py+16 −7 modified@@ -101,13 +101,22 @@ def _list_credentials(self): # If the request was filtered, make sure to return only the # credentials specific to that user. This makes it so that users with # roles on projects can't see credentials that aren't theirs. - if (not self.oslo_context.system_scope and - CONF.oslo_policy.enforce_scope): - filtered_refs = [] - for ref in refs: - if ref['user_id'] == target['credential']['user_id']: - filtered_refs.append(ref) - refs = filtered_refs + filtered_refs = [] + for ref in refs: + # Check each credential again to make sure the user has access to + # it, either by owning it, being a project admin with + # enforce_scope=false, being a system user, or having some other + # custom policy that allows access. + try: + cred = PROVIDERS.credential_api.get_credential(ref['id']) + ENFORCER.enforce_call( + action='identity:get_credential', + target_attr={'credential': cred} + ) + filtered_refs.append(ref) + except exception.Forbidden: + pass + refs = filtered_refs refs = [self._blob_to_json(r) for r in refs] return self.wrap_collection(refs, hints=hints)
keystone/tests/protection/v3/test_credentials.py+112 −0 modified@@ -1138,3 +1138,115 @@ def _override_policy(self): 'identity:delete_credential': bp.SYSTEM_ADMIN_OR_CRED_OWNER } f.write(jsonutils.dumps(overridden_policies)) + + +class ProjectReaderTestsEnforceScopeFalse(base_classes.TestCaseWithBootstrap, + common_auth.AuthTestMixin, + _UserCredentialTests, + _ProjectUsersTests): + + def setUp(self): + super(ProjectReaderTestsEnforceScopeFalse, self).setUp() + self.loadapp() + self.useFixture(ksfixtures.Policy(self.config_fixture)) + self.config_fixture.config(group='oslo_policy', enforce_scope=False) + + project_reader = unit.new_user_ref( + domain_id=CONF.identity.default_domain_id + ) + self.user_id = PROVIDERS.identity_api.create_user( + project_reader + )['id'] + project = unit.new_project_ref( + domain_id=CONF.identity.default_domain_id + ) + self.project_id = PROVIDERS.resource_api.create_project( + project['id'], project + )['id'] + PROVIDERS.assignment_api.create_grant( + self.bootstrapper.reader_role_id, user_id=self.user_id, + project_id=self.project_id + ) + + auth = self.build_authentication_request( + user_id=self.user_id, + password=project_reader['password'], + project_id=self.project_id + ) + + # Grab a token using the persona we're testing and prepare headers + # for requests we'll be making in the tests. + with self.test_client() as c: + r = c.post('/v3/auth/tokens', json=auth) + self.token_id = r.headers['X-Subject-Token'] + self.headers = {'X-Auth-Token': self.token_id} + + +class ProjectMemberTestsEnforceScopeFalse(base_classes.TestCaseWithBootstrap, + common_auth.AuthTestMixin, + _UserCredentialTests, + _ProjectUsersTests): + + def setUp(self): + super(ProjectMemberTestsEnforceScopeFalse, self).setUp() + self.loadapp() + self.useFixture(ksfixtures.Policy(self.config_fixture)) + self.config_fixture.config(group='oslo_policy', enforce_scope=False) + + project_member = unit.new_user_ref( + domain_id=CONF.identity.default_domain_id + ) + self.user_id = PROVIDERS.identity_api.create_user( + project_member + )['id'] + project = unit.new_project_ref( + domain_id=CONF.identity.default_domain_id + ) + self.project_id = PROVIDERS.resource_api.create_project( + project['id'], project + )['id'] + PROVIDERS.assignment_api.create_grant( + self.bootstrapper.member_role_id, user_id=self.user_id, + project_id=self.project_id + ) + + auth = self.build_authentication_request( + user_id=self.user_id, + password=project_member['password'], + project_id=self.project_id + ) + + # Grab a token using the persona we're testing and prepare headers + # for requests we'll be making in the tests. + with self.test_client() as c: + r = c.post('/v3/auth/tokens', json=auth) + self.token_id = r.headers['X-Subject-Token'] + self.headers = {'X-Auth-Token': self.token_id} + + +class ProjectAdminTestsEnforceScopeFalse(base_classes.TestCaseWithBootstrap, + common_auth.AuthTestMixin, + _UserCredentialTests, + _SystemUserCredentialTests): + + def setUp(self): + super(ProjectAdminTestsEnforceScopeFalse, self).setUp() + self.loadapp() + self.useFixture(ksfixtures.Policy(self.config_fixture)) + self.config_fixture.config(group='oslo_policy', enforce_scope=False) + + # Reuse the system administrator account created during + # ``keystone-manage bootstrap`` + self.user_id = self.bootstrapper.admin_user_id + auth = self.build_authentication_request( + user_id=self.user_id, + password=self.bootstrapper.admin_password, + project_id=self.bootstrapper.project_id + ) + + # Grab a token using the persona we're testing and prepare headers + # for requests we'll be making in the tests. + with self.test_client() as c: + r = c.post('/v3/auth/tokens', json=auth) + self.token_id = r.headers['X-Subject-Token'] + self.headers = {'X-Auth-Token': self.token_id}
releasenotes/notes/bug-1855080-08b28181b7cb2470.yaml+23 −0 added@@ -0,0 +1,23 @@ +--- +critical: + - | + [`bug 1855080 <https://bugs.launchpad.net/keystone/+bug/1855080>`_] + An error in the policy target filtering inadvertently allowed any user to + list any credential object with the /v3/credentials API when + ``[oslo_policy]/enforce_scope`` was set to false, which is the default. + This has been addressed: users with non-admin roles on a project may not + list other users' credentials. However, users with the admin role on a + project may still list any users credentials when + ``[oslo_policy]/enforce_scope`` is false due to `bug 968696 + <https://bugs.launchpad.net/keystone/+bug/968696>`_. +security: + - | + [`bug 1855080 <https://bugs.launchpad.net/keystone/+bug/1855080>`_] + An error in the policy target filtering inadvertently allowed any user to + list any credential object with the /v3/credentials API when + ``[oslo_policy]/enforce_scope`` was set to false, which is the default. + This has been addressed: users with non-admin roles on a project may not + list other users' credentials. However, users with the admin role on a + project may still list any users credentials when + ``[oslo_policy]/enforce_scope`` is false due to `bug 968696 + <https://bugs.launchpad.net/keystone/+bug/968696>`_.
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
22- access.redhat.com/errata/RHSA-2019:4358ghsavendor-advisoryx_refsource_REDHATWEB
- github.com/advisories/GHSA-2j23-fwqm-mgwrghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-19687ghsaADVISORY
- usn.ubuntu.com/4262-1/mitrevendor-advisoryx_refsource_UBUNTU
- www.openwall.com/lists/oss-security/2019/12/11/8ghsamailing-listx_refsource_MLISTWEB
- bugs.launchpad.net/keystone/+bug/1855080ghsax_refsource_MISCWEB
- bugzilla.redhat.com/show_bug.cgighsaWEB
- git.openstack.org/cgit/openstack/keystone/commit/ghsaWEB
- git.openstack.org/cgit/openstack/keystone/commit/ghsaWEB
- git.openstack.org/cgit/openstack/keystone/commit/ghsaWEB
- github.com/openstack/keystone/commit/17947516b0095c51da5cff94771247f2e7c44ee6ghsaWEB
- github.com/openstack/keystone/commit/17c337dbdbfb9d548ad531c2ad0483c9bce5b98fghsaWEB
- github.com/openstack/keystone/commit/bd3f63787151183f4daa43578aa491856fefae5bghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/keystone/PYSEC-2019-29.yamlghsaWEB
- review.opendev.orgghsaWEB
- review.opendev.orgmitrex_refsource_MISC
- review.opendev.orgghsaWEB
- review.opendev.orgmitrex_refsource_MISC
- review.opendev.orgghsaWEB
- review.opendev.orgmitrex_refsource_MISC
- security.openstack.org/ossa/OSSA-2019-006.htmlghsax_refsource_CONFIRMWEB
- usn.ubuntu.com/4262-1ghsaWEB
News mentions
0No linked articles in our index yet.