all:
  children:
    zuul_unreachable:
      hosts: {}
  hosts:
    debian-bookworm:
      ansible_connection: ssh
      ansible_host: 2a13:1a81:8000:3124:f816:3eff:feaf:c1c1
      ansible_port: 22
      ansible_python_interpreter: auto
      ansible_user: zuul
      nodepool:
        az: nova
        cloud: regiocloud-a-ci
        external_id: 7831f24d-279f-4ee4-9d8b-5b0c935690e6
        host_id: ec4f54fa666de9bc553bc59c41683f12345b665d0b1af00d715e7318
        interface_ip: 2a13:1a81:8000:3124:f816:3eff:feaf:c1c1
        label: debian-bookworm
        node_properties: {}
        private_ipv4: 10.128.7.246
        private_ipv6: null
        provider: regiocloud-a
        public_ipv4: 81.163.192.28
        public_ipv6: 2a13:1a81:8000:3124:f816:3eff:feaf:c1c1
        region: null
        slot: null
      tox_environment:
        NOSE_HTML_OUT_FILE: nose_results.html
        NOSE_WITH_HTML_OUTPUT: 1
        NOSE_WITH_XUNIT: 1
      tox_envlist: test
  vars:
    tox_environment:
      NOSE_HTML_OUT_FILE: nose_results.html
      NOSE_WITH_HTML_OUTPUT: 1
      NOSE_WITH_XUNIT: 1
    tox_envlist: test
    zuul:
      _inheritance_path:
      - '<Job base explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: osism/zuul-config/zuul.d/jobs.yaml@main#2>'
      - '<Job unittests explicit: None implied: {MatchAny:{ImpliedBranchMatcher:master}}
        source: osism/openinfra-zuul-jobs/zuul.d/general-jobs.yaml@master#9>'
      - '<Job tox explicit: None implied: {MatchAny:{ImpliedBranchMatcher:master}}
        source: osism/openinfra-zuul-jobs/zuul.d/python-jobs.yaml@master#1>'
      - '<Job openstack-project-manager-tox explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: osism/openstack-project-manager/.zuul.yaml@main#7>'
      - '<Job openstack-project-manager-tox explicit: None implied: None source: osism/openstack-project-manager/.zuul.yaml@main#14>'
      ansible_version: '9'
      attempts: 1
      branch: main
      build: c7b034f7604b4c16bce7e05097c3cddf
      build_refs:
      - branch: main
        change: '245'
        change_message: "Update dependency python-ldap to v3.4.5 [SECURITY]\n\nThis
          PR contains the following updates:\n\n| Package | Change | Age | Confidence
          |\n|---|---|---|---|\n| [python-ldap](https://www.python-ldap.org/) ([source](https://redirect.github.com/python-ldap/python-ldap),
          [changelog](https://redirect.github.com/python-ldap/python-ldap/blob/main/CHANGES))
          | `==3.4.4` -> `==3.4.5` | [![age](https://developer.mend.io/api/mc/badges/age/pypi/python-ldap/3.4.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
          | [![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/python-ldap/3.4.4/3.4.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
          |\n\n### GitHub Vulnerability Alerts\n\n#### [CVE-2025-61911](https://redirect.github.com/python-ldap/python-ldap/security/advisories/GHSA-r7r6-cc7p-4v5m)\n\n###
          Summary\nThe sanitization method `ldap.filter.escape_filter_chars` can be
          tricked to skip escaping of special characters when a crafted `list` or
          `dict` is supplied as the `assertion_value` parameter, and the non-default
          `escape_mode=1` is configured.\n\n### Details\nThe method `ldap.filter.escape_filter_chars`
          supports 3 different escaping modes. `escape_mode=0` (default) and `escape_mode=2`
          happen to raise exceptions when a `list` or `dict` object is supplied as
          the `assertion_value` parameter. However, `escape_mode=1` happily computes
          without performing adequate logic to ensure a fully escaped return value.\n\n###
          PoC\n```\n>>> import ldap.filter\n```\n**Exploitable**\n```\n>>> ldap.filter.escape_filter_chars([\"abc@*()/xyz\"],
          escape_mode=1)\n'abc@*()/xyz'\n>>> ldap.filter.escape_filter_chars({\"abc@*()/xyz\":
          1}, escape_mode=1)\n'abc@*()/xyz'\n```\n**Not exploitable**\n```\n>>> ldap.filter.escape_filter_chars(\"abc@*()/xyz\",
          escape_mode=1)\n'abc@\\\\2a\\\\28\\\\29\\\\2fxyz'\n>>> ldap.filter.escape_filter_chars([\"abc@*()/xyz\"],
          escape_mode=0)\nTraceback (most recent call last):\n  File \"<stdin>\",
          line 1, in <module>\n  File \"/usr/local/lib64/python3.12/site-packages/ldap/filter.py\",
          line 41, in escape_filter_chars\n    s = assertion_value.replace('\\\\',
          r'\\5c')\n        ^^^^^^^^^^^^^^^^^^^^^^^\nAttributeError: 'list' object
          has no attribute 'replace'\n>>> ldap.filter.escape_filter_chars([\"abc@*()/xyz\"],
          escape_mode=2)\nTraceback (most recent call last):\n  File \"<stdin>\",
          line 1, in <module>\n  File \"/usr/local/lib64/python3.12/site-packages/ldap/filter.py\",
          line 36, in escape_filter_chars\n    r.append(\"\\\\%02x\" % ord(c))\n                        ^^^^^^\nTypeError:
          ord() expected a character, but string of length 11 found\n```\n\n### Impact\nIf
          an application relies on the vulnerable method in the `python-ldap` library
          to escape untrusted user input, an attacker might be able to abuse the vulnerability
          to launch ldap injection attacks which could potentially disclose or manipulate
          ldap data meant to be inaccessible to them.\n\nWith Python being a dynamically
          typed language, and the commonly used `JSON` format supporting `list` and
          `dict`, it is to be expected that Python applications may commonly forward
          unchecked and potentially malicious `list` and `dict` objects to the vulnerable
          sanitization method.\n\nThe vulnerable `escape_mode=1` configuration does
          not appear to be widely used.\n\n### Suggested Fix\nAdd a type check at
          the start of the `ldap.filter.escape_filter_chars` method to raise an exception
          when the supplied `assertion_value` parameter is not of type `str`.\n\n####
          [CVE-2025-61912](https://redirect.github.com/python-ldap/python-ldap/security/advisories/GHSA-p34h-wq7j-h5v6)\n\n###
          Summary\n\n`ldap.dn.escape_dn_chars()` escapes `\\x00` incorrectly by emitting
          a backslash followed by a literal NUL byte instead of the RFC-4514 hex form
          `\\00`. Any application that uses this helper to construct DNs from untrusted
          input can be made to consistently fail before a request is sent to the LDAP
          server (e.g., AD), resulting in a client-side denial of service.\n\n###
          Details\n\nAffected function: `ldap.dn.escape_dn_chars(s)`\n\nFile: Lib/ldap/dn.py\n\nBuggy
          behavior:\nFor NUL, the function does:\n\n`s = s.replace('\\000', '\\\\\\000')
          \ # backslash + literal NUL`\n\nThis produces Python strings which, when
          passed to python-ldap APIs (e.g., `add_s`, `modify_s`, r`ename_s`, or used
          as search bases), contain an embedded NUL. python-ldap then raises ValueError:
          embedded null character (or otherwise fails) before any network I/O.\nWith
          correct RFC-4514 encoding (`\\00`), the client proceeds and the server can
          apply its own syntax rules (e.g., AD will reject NUL in CN with result:
          34), proving the failure originates in the escaping helper.\n\nWhy it matters:
          Projects follow the docs which state this function \u201Cshould be used
          when building LDAP DN strings from arbitrary input.\u201D The function\u2019s
          guarantee is therefore relied upon as a safety API. A single NUL in attacker-controlled
          input reliably breaks client workflows (crash/unhandled exception, stuck
          retries, poison queue record), i.e., a DoS.\n\nStandards: RFC 4514 requires
          special characters and controls to be escaped using hex form; a literal
          NUL is not a valid DN character.\n\nMinimal fix: Escape NUL as hex:\n\n`s
          = s.replace('\\x00', r'\\00')`\n\n### PoC\n\nPrereqs: Any python-ldap install
          and a reachable LDAP server (for the second half). The first half (client-side
          failure) does not require a live server.\n\n```import ldap\nfrom ldap.dn
          import escape_dn_chars, str2dn\n\nl = ldap.initialize(\"ldap://10.0.1.11\")
          \             # your lab DC/LDAP\nl.protocol_version = 3\nl.set_option(ldap.OPT_REFERRALS,
          0)\nl.simple_bind_s(r\"DSEC\\dani.aga\", \"PassAa1\")         \n\n# ---
          Attacker-controlled value contains NUL ---\ncn = \"bad\\0name\"\nescaped_cn
          = escape_dn_chars(cn)\ndn = f\"CN={escaped_cn},OU=Users,DC=dsec,DC=local\"\nattrs
          = [('objectClass', [b'user']), ('sAMAccountName', [b'badsam'])]\n\nprint(\"===
          BUGGY DN (contains literal NUL) ===\")\nprint(\"escaped_cn repr:\", repr(escaped_cn))\nprint(\"dn
          repr:\", repr(dn))\nprint(\"contains NUL?:\", \"\\x00\" in dn, \"at index:\",
          dn.find(\"\\x00\"))\n\nprint(\"=> add_s(buggy DN): expected client-side
          failure (no server contact)\")\ntry:\n    l.add_s(dn, attrs)\n    print(\"add_s(buggy):
          succeeded (unexpected)\")\nexcept Exception as e:\n    print(\"add_s(buggy):\",
          type(e).__name__, e)  # ValueError: embedded null character\n\n# --- Correct
          hex escape demonstrates the client proceeds to the server ---\nsafe_dn =
          dn.replace(\"\\x00\", r\"\\00\")                 # RFC 4514-compliant\nprint(\"\\n===
          HEX-ESCAPED DN (\\\\00) ===\")\nprint(\"safe_dn repr:\", repr(safe_dn))\nprint(\"=>
          sanity parse:\", str2dn(safe_dn))           # parses locally\n\nprint(\"=>
          add_s(safe DN): reaches server (AD will likely reject with 34)\")\ntry:\n
          \   l.add_s(safe_dn, attrs)\n    print(\"add_s(safe): success (unlikely
          without required attrs/rights)\")\nexcept ldap.LDAPError as e:\n    print(\"add_s(safe):\",
          e.__class__.__name__, e)  # e.g., result 34 Invalid DN syntax (AD forbids
          NUL in CN)\n```\n\nObserved result (example):\n\n`add_s(buggy): ValueError
          embedded null character` \u2190 client-side DoS\n\n`add_s(safe): INVALID_DN_SYNTAX
          (result 34, BAD_NAME)` \u2190 request reached server; rejection due to server
          policy, not client bug\n\n### Impact\n\nType: Denial of Service (client-side).\n\nWho
          is impacted: Any application that uses ldap.dn.escape_dn_chars() to build
          DNs from (partially) untrusted input\u2014e.g., user `creation/rename tools`,
          `sync/ETL jobs`, portals allowing self-service attributes, device onboarding,
          batch imports. A single crafted value with `\\x00` reliably forces exceptions/failures
          and can crash handlers or jam pipelines with poison records.\n\n---\n\n###
          Release Notes\n\n<details>\n<summary>python-ldap/python-ldap (python-ldap)</summary>\n\n###
          [`v3.4.5`](https://redirect.github.com/python-ldap/python-ldap/releases/tag/python-ldap-3.4.5):
          3.4.5\n\n[Compare Source](https://redirect.github.com/python-ldap/python-ldap/compare/python-ldap-3.4.4...python-ldap-3.4.5)\n\nReleased
          3.4.5 2025-10-10\n\nSecurity fixes:\n\n- CVE-2025-61911 (GHSA-r7r6-cc7p-4v5m):
          Enforce `str` input in\n  `ldap.filter.escape_filter_chars` with `escape_mode=1`;
          ensure proper\n  escaping. (thanks to lukas-eu)\n- CVE-2025-61912 (GHSA-p34h-wq7j-h5v6):
          Correct NUL escaping in\n  `ldap.dn.escape_dn_chars` to `\\00` per RFC 4514.
          (thanks to aradona91)\n\nFixes:\n\n- ReconnectLDAPObject now properly reconnects
          on UNAVAILABLE, CONNECT\\_ERROR\n  and TIMEOUT exceptions (previously only
          SERVER\\_DOWN), fixing reconnection\n  issues especially during server restarts\n-
          Fixed syncrepl.py to use named constants instead of raw decimal values\n
          \ for result types\n- Fixed error handling in SearchNoOpMixIn to prevent
          a undefined variable error\n\nTests:\n\n- Added comprehensive reconnection
          test cases including concurrent operation\n  handling and server restart
          scenarios\n\nDoc/\n\n- Updated installation docs and fixed various documentation
          typos\n- Added ReadTheDocs configuration file\n\nInfrastructure:\n\n- Add
          testing and document support for Python 3.13\n\n</details>\n\n---\n\n###
          Configuration\n\n\U0001F4C5 **Schedule**: Branch creation - \"\" (UTC),
          Automerge - At any time (no schedule defined).\n\n\U0001F6A6 **Automerge**:
          Disabled by config. Please merge this manually once you are satisfied.\n\n\u267B
          **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry
          checkbox.\n\n\U0001F515 **Ignore**: Close this PR and you won't be reminded
          about these updates again.\n\n---\n\n - [ ] <!-- rebase-check -->If you
          want to rebase/retry this PR, check this box\n\n---\n\nThis PR was generated
          by [Mend Renovate](https://mend.io/renovate/). View the [repository job
          log](https://developer.mend.io/github/osism/openstack-project-manager).\n<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNDMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE0My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZSJdfQ==-->\n"
        change_url: https://github.com/osism/openstack-project-manager/pull/245
        commit_id: 63995bd4078a10a4db68156603c28c6a8233a3f1
        patchset: 63995bd4078a10a4db68156603c28c6a8233a3f1
        project:
          canonical_hostname: github.com
          canonical_name: github.com/osism/openstack-project-manager
          name: osism/openstack-project-manager
          short_name: openstack-project-manager
        src_dir: src/github.com/osism/openstack-project-manager
        topic: null
      buildset: acb57cc9ce2747d7acec28f5648514c8
      buildset_refs:
      - branch: main
        change: '245'
        change_message: "Update dependency python-ldap to v3.4.5 [SECURITY]\n\nThis
          PR contains the following updates:\n\n| Package | Change | Age | Confidence
          |\n|---|---|---|---|\n| [python-ldap](https://www.python-ldap.org/) ([source](https://redirect.github.com/python-ldap/python-ldap),
          [changelog](https://redirect.github.com/python-ldap/python-ldap/blob/main/CHANGES))
          | `==3.4.4` -> `==3.4.5` | [![age](https://developer.mend.io/api/mc/badges/age/pypi/python-ldap/3.4.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
          | [![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/python-ldap/3.4.4/3.4.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
          |\n\n### GitHub Vulnerability Alerts\n\n#### [CVE-2025-61911](https://redirect.github.com/python-ldap/python-ldap/security/advisories/GHSA-r7r6-cc7p-4v5m)\n\n###
          Summary\nThe sanitization method `ldap.filter.escape_filter_chars` can be
          tricked to skip escaping of special characters when a crafted `list` or
          `dict` is supplied as the `assertion_value` parameter, and the non-default
          `escape_mode=1` is configured.\n\n### Details\nThe method `ldap.filter.escape_filter_chars`
          supports 3 different escaping modes. `escape_mode=0` (default) and `escape_mode=2`
          happen to raise exceptions when a `list` or `dict` object is supplied as
          the `assertion_value` parameter. However, `escape_mode=1` happily computes
          without performing adequate logic to ensure a fully escaped return value.\n\n###
          PoC\n```\n>>> import ldap.filter\n```\n**Exploitable**\n```\n>>> ldap.filter.escape_filter_chars([\"abc@*()/xyz\"],
          escape_mode=1)\n'abc@*()/xyz'\n>>> ldap.filter.escape_filter_chars({\"abc@*()/xyz\":
          1}, escape_mode=1)\n'abc@*()/xyz'\n```\n**Not exploitable**\n```\n>>> ldap.filter.escape_filter_chars(\"abc@*()/xyz\",
          escape_mode=1)\n'abc@\\\\2a\\\\28\\\\29\\\\2fxyz'\n>>> ldap.filter.escape_filter_chars([\"abc@*()/xyz\"],
          escape_mode=0)\nTraceback (most recent call last):\n  File \"<stdin>\",
          line 1, in <module>\n  File \"/usr/local/lib64/python3.12/site-packages/ldap/filter.py\",
          line 41, in escape_filter_chars\n    s = assertion_value.replace('\\\\',
          r'\\5c')\n        ^^^^^^^^^^^^^^^^^^^^^^^\nAttributeError: 'list' object
          has no attribute 'replace'\n>>> ldap.filter.escape_filter_chars([\"abc@*()/xyz\"],
          escape_mode=2)\nTraceback (most recent call last):\n  File \"<stdin>\",
          line 1, in <module>\n  File \"/usr/local/lib64/python3.12/site-packages/ldap/filter.py\",
          line 36, in escape_filter_chars\n    r.append(\"\\\\%02x\" % ord(c))\n                        ^^^^^^\nTypeError:
          ord() expected a character, but string of length 11 found\n```\n\n### Impact\nIf
          an application relies on the vulnerable method in the `python-ldap` library
          to escape untrusted user input, an attacker might be able to abuse the vulnerability
          to launch ldap injection attacks which could potentially disclose or manipulate
          ldap data meant to be inaccessible to them.\n\nWith Python being a dynamically
          typed language, and the commonly used `JSON` format supporting `list` and
          `dict`, it is to be expected that Python applications may commonly forward
          unchecked and potentially malicious `list` and `dict` objects to the vulnerable
          sanitization method.\n\nThe vulnerable `escape_mode=1` configuration does
          not appear to be widely used.\n\n### Suggested Fix\nAdd a type check at
          the start of the `ldap.filter.escape_filter_chars` method to raise an exception
          when the supplied `assertion_value` parameter is not of type `str`.\n\n####
          [CVE-2025-61912](https://redirect.github.com/python-ldap/python-ldap/security/advisories/GHSA-p34h-wq7j-h5v6)\n\n###
          Summary\n\n`ldap.dn.escape_dn_chars()` escapes `\\x00` incorrectly by emitting
          a backslash followed by a literal NUL byte instead of the RFC-4514 hex form
          `\\00`. Any application that uses this helper to construct DNs from untrusted
          input can be made to consistently fail before a request is sent to the LDAP
          server (e.g., AD), resulting in a client-side denial of service.\n\n###
          Details\n\nAffected function: `ldap.dn.escape_dn_chars(s)`\n\nFile: Lib/ldap/dn.py\n\nBuggy
          behavior:\nFor NUL, the function does:\n\n`s = s.replace('\\000', '\\\\\\000')
          \ # backslash + literal NUL`\n\nThis produces Python strings which, when
          passed to python-ldap APIs (e.g., `add_s`, `modify_s`, r`ename_s`, or used
          as search bases), contain an embedded NUL. python-ldap then raises ValueError:
          embedded null character (or otherwise fails) before any network I/O.\nWith
          correct RFC-4514 encoding (`\\00`), the client proceeds and the server can
          apply its own syntax rules (e.g., AD will reject NUL in CN with result:
          34), proving the failure originates in the escaping helper.\n\nWhy it matters:
          Projects follow the docs which state this function \u201Cshould be used
          when building LDAP DN strings from arbitrary input.\u201D The function\u2019s
          guarantee is therefore relied upon as a safety API. A single NUL in attacker-controlled
          input reliably breaks client workflows (crash/unhandled exception, stuck
          retries, poison queue record), i.e., a DoS.\n\nStandards: RFC 4514 requires
          special characters and controls to be escaped using hex form; a literal
          NUL is not a valid DN character.\n\nMinimal fix: Escape NUL as hex:\n\n`s
          = s.replace('\\x00', r'\\00')`\n\n### PoC\n\nPrereqs: Any python-ldap install
          and a reachable LDAP server (for the second half). The first half (client-side
          failure) does not require a live server.\n\n```import ldap\nfrom ldap.dn
          import escape_dn_chars, str2dn\n\nl = ldap.initialize(\"ldap://10.0.1.11\")
          \             # your lab DC/LDAP\nl.protocol_version = 3\nl.set_option(ldap.OPT_REFERRALS,
          0)\nl.simple_bind_s(r\"DSEC\\dani.aga\", \"PassAa1\")         \n\n# ---
          Attacker-controlled value contains NUL ---\ncn = \"bad\\0name\"\nescaped_cn
          = escape_dn_chars(cn)\ndn = f\"CN={escaped_cn},OU=Users,DC=dsec,DC=local\"\nattrs
          = [('objectClass', [b'user']), ('sAMAccountName', [b'badsam'])]\n\nprint(\"===
          BUGGY DN (contains literal NUL) ===\")\nprint(\"escaped_cn repr:\", repr(escaped_cn))\nprint(\"dn
          repr:\", repr(dn))\nprint(\"contains NUL?:\", \"\\x00\" in dn, \"at index:\",
          dn.find(\"\\x00\"))\n\nprint(\"=> add_s(buggy DN): expected client-side
          failure (no server contact)\")\ntry:\n    l.add_s(dn, attrs)\n    print(\"add_s(buggy):
          succeeded (unexpected)\")\nexcept Exception as e:\n    print(\"add_s(buggy):\",
          type(e).__name__, e)  # ValueError: embedded null character\n\n# --- Correct
          hex escape demonstrates the client proceeds to the server ---\nsafe_dn =
          dn.replace(\"\\x00\", r\"\\00\")                 # RFC 4514-compliant\nprint(\"\\n===
          HEX-ESCAPED DN (\\\\00) ===\")\nprint(\"safe_dn repr:\", repr(safe_dn))\nprint(\"=>
          sanity parse:\", str2dn(safe_dn))           # parses locally\n\nprint(\"=>
          add_s(safe DN): reaches server (AD will likely reject with 34)\")\ntry:\n
          \   l.add_s(safe_dn, attrs)\n    print(\"add_s(safe): success (unlikely
          without required attrs/rights)\")\nexcept ldap.LDAPError as e:\n    print(\"add_s(safe):\",
          e.__class__.__name__, e)  # e.g., result 34 Invalid DN syntax (AD forbids
          NUL in CN)\n```\n\nObserved result (example):\n\n`add_s(buggy): ValueError
          embedded null character` \u2190 client-side DoS\n\n`add_s(safe): INVALID_DN_SYNTAX
          (result 34, BAD_NAME)` \u2190 request reached server; rejection due to server
          policy, not client bug\n\n### Impact\n\nType: Denial of Service (client-side).\n\nWho
          is impacted: Any application that uses ldap.dn.escape_dn_chars() to build
          DNs from (partially) untrusted input\u2014e.g., user `creation/rename tools`,
          `sync/ETL jobs`, portals allowing self-service attributes, device onboarding,
          batch imports. A single crafted value with `\\x00` reliably forces exceptions/failures
          and can crash handlers or jam pipelines with poison records.\n\n---\n\n###
          Release Notes\n\n<details>\n<summary>python-ldap/python-ldap (python-ldap)</summary>\n\n###
          [`v3.4.5`](https://redirect.github.com/python-ldap/python-ldap/releases/tag/python-ldap-3.4.5):
          3.4.5\n\n[Compare Source](https://redirect.github.com/python-ldap/python-ldap/compare/python-ldap-3.4.4...python-ldap-3.4.5)\n\nReleased
          3.4.5 2025-10-10\n\nSecurity fixes:\n\n- CVE-2025-61911 (GHSA-r7r6-cc7p-4v5m):
          Enforce `str` input in\n  `ldap.filter.escape_filter_chars` with `escape_mode=1`;
          ensure proper\n  escaping. (thanks to lukas-eu)\n- CVE-2025-61912 (GHSA-p34h-wq7j-h5v6):
          Correct NUL escaping in\n  `ldap.dn.escape_dn_chars` to `\\00` per RFC 4514.
          (thanks to aradona91)\n\nFixes:\n\n- ReconnectLDAPObject now properly reconnects
          on UNAVAILABLE, CONNECT\\_ERROR\n  and TIMEOUT exceptions (previously only
          SERVER\\_DOWN), fixing reconnection\n  issues especially during server restarts\n-
          Fixed syncrepl.py to use named constants instead of raw decimal values\n
          \ for result types\n- Fixed error handling in SearchNoOpMixIn to prevent
          a undefined variable error\n\nTests:\n\n- Added comprehensive reconnection
          test cases including concurrent operation\n  handling and server restart
          scenarios\n\nDoc/\n\n- Updated installation docs and fixed various documentation
          typos\n- Added ReadTheDocs configuration file\n\nInfrastructure:\n\n- Add
          testing and document support for Python 3.13\n\n</details>\n\n---\n\n###
          Configuration\n\n\U0001F4C5 **Schedule**: Branch creation - \"\" (UTC),
          Automerge - At any time (no schedule defined).\n\n\U0001F6A6 **Automerge**:
          Disabled by config. Please merge this manually once you are satisfied.\n\n\u267B
          **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry
          checkbox.\n\n\U0001F515 **Ignore**: Close this PR and you won't be reminded
          about these updates again.\n\n---\n\n - [ ] <!-- rebase-check -->If you
          want to rebase/retry this PR, check this box\n\n---\n\nThis PR was generated
          by [Mend Renovate](https://mend.io/renovate/). View the [repository job
          log](https://developer.mend.io/github/osism/openstack-project-manager).\n<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNDMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE0My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZSJdfQ==-->\n"
        change_url: https://github.com/osism/openstack-project-manager/pull/245
        commit_id: 63995bd4078a10a4db68156603c28c6a8233a3f1
        patchset: 63995bd4078a10a4db68156603c28c6a8233a3f1
        project:
          canonical_hostname: github.com
          canonical_name: github.com/osism/openstack-project-manager
          name: osism/openstack-project-manager
          short_name: openstack-project-manager
        src_dir: src/github.com/osism/openstack-project-manager
        topic: null
      change: '245'
      change_message: "Update dependency python-ldap to v3.4.5 [SECURITY]\n\nThis
        PR contains the following updates:\n\n| Package | Change | Age | Confidence
        |\n|---|---|---|---|\n| [python-ldap](https://www.python-ldap.org/) ([source](https://redirect.github.com/python-ldap/python-ldap),
        [changelog](https://redirect.github.com/python-ldap/python-ldap/blob/main/CHANGES))
        | `==3.4.4` -> `==3.4.5` | [![age](https://developer.mend.io/api/mc/badges/age/pypi/python-ldap/3.4.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
        | [![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/python-ldap/3.4.4/3.4.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
        |\n\n### GitHub Vulnerability Alerts\n\n#### [CVE-2025-61911](https://redirect.github.com/python-ldap/python-ldap/security/advisories/GHSA-r7r6-cc7p-4v5m)\n\n###
        Summary\nThe sanitization method `ldap.filter.escape_filter_chars` can be
        tricked to skip escaping of special characters when a crafted `list` or `dict`
        is supplied as the `assertion_value` parameter, and the non-default `escape_mode=1`
        is configured.\n\n### Details\nThe method `ldap.filter.escape_filter_chars`
        supports 3 different escaping modes. `escape_mode=0` (default) and `escape_mode=2`
        happen to raise exceptions when a `list` or `dict` object is supplied as the
        `assertion_value` parameter. However, `escape_mode=1` happily computes without
        performing adequate logic to ensure a fully escaped return value.\n\n### PoC\n```\n>>>
        import ldap.filter\n```\n**Exploitable**\n```\n>>> ldap.filter.escape_filter_chars([\"abc@*()/xyz\"],
        escape_mode=1)\n'abc@*()/xyz'\n>>> ldap.filter.escape_filter_chars({\"abc@*()/xyz\":
        1}, escape_mode=1)\n'abc@*()/xyz'\n```\n**Not exploitable**\n```\n>>> ldap.filter.escape_filter_chars(\"abc@*()/xyz\",
        escape_mode=1)\n'abc@\\\\2a\\\\28\\\\29\\\\2fxyz'\n>>> ldap.filter.escape_filter_chars([\"abc@*()/xyz\"],
        escape_mode=0)\nTraceback (most recent call last):\n  File \"<stdin>\", line
        1, in <module>\n  File \"/usr/local/lib64/python3.12/site-packages/ldap/filter.py\",
        line 41, in escape_filter_chars\n    s = assertion_value.replace('\\\\', r'\\5c')\n
        \       ^^^^^^^^^^^^^^^^^^^^^^^\nAttributeError: 'list' object has no attribute
        'replace'\n>>> ldap.filter.escape_filter_chars([\"abc@*()/xyz\"], escape_mode=2)\nTraceback
        (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File
        \"/usr/local/lib64/python3.12/site-packages/ldap/filter.py\", line 36, in
        escape_filter_chars\n    r.append(\"\\\\%02x\" % ord(c))\n                        ^^^^^^\nTypeError:
        ord() expected a character, but string of length 11 found\n```\n\n### Impact\nIf
        an application relies on the vulnerable method in the `python-ldap` library
        to escape untrusted user input, an attacker might be able to abuse the vulnerability
        to launch ldap injection attacks which could potentially disclose or manipulate
        ldap data meant to be inaccessible to them.\n\nWith Python being a dynamically
        typed language, and the commonly used `JSON` format supporting `list` and
        `dict`, it is to be expected that Python applications may commonly forward
        unchecked and potentially malicious `list` and `dict` objects to the vulnerable
        sanitization method.\n\nThe vulnerable `escape_mode=1` configuration does
        not appear to be widely used.\n\n### Suggested Fix\nAdd a type check at the
        start of the `ldap.filter.escape_filter_chars` method to raise an exception
        when the supplied `assertion_value` parameter is not of type `str`.\n\n####
        [CVE-2025-61912](https://redirect.github.com/python-ldap/python-ldap/security/advisories/GHSA-p34h-wq7j-h5v6)\n\n###
        Summary\n\n`ldap.dn.escape_dn_chars()` escapes `\\x00` incorrectly by emitting
        a backslash followed by a literal NUL byte instead of the RFC-4514 hex form
        `\\00`. Any application that uses this helper to construct DNs from untrusted
        input can be made to consistently fail before a request is sent to the LDAP
        server (e.g., AD), resulting in a client-side denial of service.\n\n### Details\n\nAffected
        function: `ldap.dn.escape_dn_chars(s)`\n\nFile: Lib/ldap/dn.py\n\nBuggy behavior:\nFor
        NUL, the function does:\n\n`s = s.replace('\\000', '\\\\\\000')  # backslash
        + literal NUL`\n\nThis produces Python strings which, when passed to python-ldap
        APIs (e.g., `add_s`, `modify_s`, r`ename_s`, or used as search bases), contain
        an embedded NUL. python-ldap then raises ValueError: embedded null character
        (or otherwise fails) before any network I/O.\nWith correct RFC-4514 encoding
        (`\\00`), the client proceeds and the server can apply its own syntax rules
        (e.g., AD will reject NUL in CN with result: 34), proving the failure originates
        in the escaping helper.\n\nWhy it matters: Projects follow the docs which
        state this function \u201Cshould be used when building LDAP DN strings from
        arbitrary input.\u201D The function\u2019s guarantee is therefore relied upon
        as a safety API. A single NUL in attacker-controlled input reliably breaks
        client workflows (crash/unhandled exception, stuck retries, poison queue record),
        i.e., a DoS.\n\nStandards: RFC 4514 requires special characters and controls
        to be escaped using hex form; a literal NUL is not a valid DN character.\n\nMinimal
        fix: Escape NUL as hex:\n\n`s = s.replace('\\x00', r'\\00')`\n\n### PoC\n\nPrereqs:
        Any python-ldap install and a reachable LDAP server (for the second half).
        The first half (client-side failure) does not require a live server.\n\n```import
        ldap\nfrom ldap.dn import escape_dn_chars, str2dn\n\nl = ldap.initialize(\"ldap://10.0.1.11\")
        \             # your lab DC/LDAP\nl.protocol_version = 3\nl.set_option(ldap.OPT_REFERRALS,
        0)\nl.simple_bind_s(r\"DSEC\\dani.aga\", \"PassAa1\")         \n\n# --- Attacker-controlled
        value contains NUL ---\ncn = \"bad\\0name\"\nescaped_cn = escape_dn_chars(cn)\ndn
        = f\"CN={escaped_cn},OU=Users,DC=dsec,DC=local\"\nattrs = [('objectClass',
        [b'user']), ('sAMAccountName', [b'badsam'])]\n\nprint(\"=== BUGGY DN (contains
        literal NUL) ===\")\nprint(\"escaped_cn repr:\", repr(escaped_cn))\nprint(\"dn
        repr:\", repr(dn))\nprint(\"contains NUL?:\", \"\\x00\" in dn, \"at index:\",
        dn.find(\"\\x00\"))\n\nprint(\"=> add_s(buggy DN): expected client-side failure
        (no server contact)\")\ntry:\n    l.add_s(dn, attrs)\n    print(\"add_s(buggy):
        succeeded (unexpected)\")\nexcept Exception as e:\n    print(\"add_s(buggy):\",
        type(e).__name__, e)  # ValueError: embedded null character\n\n# --- Correct
        hex escape demonstrates the client proceeds to the server ---\nsafe_dn = dn.replace(\"\\x00\",
        r\"\\00\")                 # RFC 4514-compliant\nprint(\"\\n=== HEX-ESCAPED
        DN (\\\\00) ===\")\nprint(\"safe_dn repr:\", repr(safe_dn))\nprint(\"=> sanity
        parse:\", str2dn(safe_dn))           # parses locally\n\nprint(\"=> add_s(safe
        DN): reaches server (AD will likely reject with 34)\")\ntry:\n    l.add_s(safe_dn,
        attrs)\n    print(\"add_s(safe): success (unlikely without required attrs/rights)\")\nexcept
        ldap.LDAPError as e:\n    print(\"add_s(safe):\", e.__class__.__name__, e)
        \ # e.g., result 34 Invalid DN syntax (AD forbids NUL in CN)\n```\n\nObserved
        result (example):\n\n`add_s(buggy): ValueError embedded null character` \u2190
        client-side DoS\n\n`add_s(safe): INVALID_DN_SYNTAX (result 34, BAD_NAME)`
        \u2190 request reached server; rejection due to server policy, not client
        bug\n\n### Impact\n\nType: Denial of Service (client-side).\n\nWho is impacted:
        Any application that uses ldap.dn.escape_dn_chars() to build DNs from (partially)
        untrusted input\u2014e.g., user `creation/rename tools`, `sync/ETL jobs`,
        portals allowing self-service attributes, device onboarding, batch imports.
        A single crafted value with `\\x00` reliably forces exceptions/failures and
        can crash handlers or jam pipelines with poison records.\n\n---\n\n### Release
        Notes\n\n<details>\n<summary>python-ldap/python-ldap (python-ldap)</summary>\n\n###
        [`v3.4.5`](https://redirect.github.com/python-ldap/python-ldap/releases/tag/python-ldap-3.4.5):
        3.4.5\n\n[Compare Source](https://redirect.github.com/python-ldap/python-ldap/compare/python-ldap-3.4.4...python-ldap-3.4.5)\n\nReleased
        3.4.5 2025-10-10\n\nSecurity fixes:\n\n- CVE-2025-61911 (GHSA-r7r6-cc7p-4v5m):
        Enforce `str` input in\n  `ldap.filter.escape_filter_chars` with `escape_mode=1`;
        ensure proper\n  escaping. (thanks to lukas-eu)\n- CVE-2025-61912 (GHSA-p34h-wq7j-h5v6):
        Correct NUL escaping in\n  `ldap.dn.escape_dn_chars` to `\\00` per RFC 4514.
        (thanks to aradona91)\n\nFixes:\n\n- ReconnectLDAPObject now properly reconnects
        on UNAVAILABLE, CONNECT\\_ERROR\n  and TIMEOUT exceptions (previously only
        SERVER\\_DOWN), fixing reconnection\n  issues especially during server restarts\n-
        Fixed syncrepl.py to use named constants instead of raw decimal values\n  for
        result types\n- Fixed error handling in SearchNoOpMixIn to prevent a undefined
        variable error\n\nTests:\n\n- Added comprehensive reconnection test cases
        including concurrent operation\n  handling and server restart scenarios\n\nDoc/\n\n-
        Updated installation docs and fixed various documentation typos\n- Added ReadTheDocs
        configuration file\n\nInfrastructure:\n\n- Add testing and document support
        for Python 3.13\n\n</details>\n\n---\n\n### Configuration\n\n\U0001F4C5 **Schedule**:
        Branch creation - \"\" (UTC), Automerge - At any time (no schedule defined).\n\n\U0001F6A6
        **Automerge**: Disabled by config. Please merge this manually once you are
        satisfied.\n\n\u267B **Rebasing**: Whenever PR becomes conflicted, or you
        tick the rebase/retry checkbox.\n\n\U0001F515 **Ignore**: Close this PR and
        you won't be reminded about these updates again.\n\n---\n\n - [ ] <!-- rebase-check
        -->If you want to rebase/retry this PR, check this box\n\n---\n\nThis PR was
        generated by [Mend Renovate](https://mend.io/renovate/). View the [repository
        job log](https://developer.mend.io/github/osism/openstack-project-manager).\n<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNDMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE0My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZSJdfQ==-->\n"
      change_url: https://github.com/osism/openstack-project-manager/pull/245
      child_jobs: []
      commit_id: 63995bd4078a10a4db68156603c28c6a8233a3f1
      event_id: 289fb0f0-a63f-11f0-968b-728cf7118b4a
      executor:
        hostname: 521e9411259a
        inventory_file: /var/lib/zuul/builds/c7b034f7604b4c16bce7e05097c3cddf/ansible/inventory.yaml
        log_root: /var/lib/zuul/builds/c7b034f7604b4c16bce7e05097c3cddf/work/logs
        result_data_file: /var/lib/zuul/builds/c7b034f7604b4c16bce7e05097c3cddf/work/results.json
        src_root: /var/lib/zuul/builds/c7b034f7604b4c16bce7e05097c3cddf/work/src
        work_root: /var/lib/zuul/builds/c7b034f7604b4c16bce7e05097c3cddf/work
      include_vars: []
      items:
      - branch: main
        change: '245'
        change_message: "Update dependency python-ldap to v3.4.5 [SECURITY]\n\nThis
          PR contains the following updates:\n\n| Package | Change | Age | Confidence
          |\n|---|---|---|---|\n| [python-ldap](https://www.python-ldap.org/) ([source](https://redirect.github.com/python-ldap/python-ldap),
          [changelog](https://redirect.github.com/python-ldap/python-ldap/blob/main/CHANGES))
          | `==3.4.4` -> `==3.4.5` | [![age](https://developer.mend.io/api/mc/badges/age/pypi/python-ldap/3.4.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
          | [![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/python-ldap/3.4.4/3.4.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
          |\n\n### GitHub Vulnerability Alerts\n\n#### [CVE-2025-61911](https://redirect.github.com/python-ldap/python-ldap/security/advisories/GHSA-r7r6-cc7p-4v5m)\n\n###
          Summary\nThe sanitization method `ldap.filter.escape_filter_chars` can be
          tricked to skip escaping of special characters when a crafted `list` or
          `dict` is supplied as the `assertion_value` parameter, and the non-default
          `escape_mode=1` is configured.\n\n### Details\nThe method `ldap.filter.escape_filter_chars`
          supports 3 different escaping modes. `escape_mode=0` (default) and `escape_mode=2`
          happen to raise exceptions when a `list` or `dict` object is supplied as
          the `assertion_value` parameter. However, `escape_mode=1` happily computes
          without performing adequate logic to ensure a fully escaped return value.\n\n###
          PoC\n```\n>>> import ldap.filter\n```\n**Exploitable**\n```\n>>> ldap.filter.escape_filter_chars([\"abc@*()/xyz\"],
          escape_mode=1)\n'abc@*()/xyz'\n>>> ldap.filter.escape_filter_chars({\"abc@*()/xyz\":
          1}, escape_mode=1)\n'abc@*()/xyz'\n```\n**Not exploitable**\n```\n>>> ldap.filter.escape_filter_chars(\"abc@*()/xyz\",
          escape_mode=1)\n'abc@\\\\2a\\\\28\\\\29\\\\2fxyz'\n>>> ldap.filter.escape_filter_chars([\"abc@*()/xyz\"],
          escape_mode=0)\nTraceback (most recent call last):\n  File \"<stdin>\",
          line 1, in <module>\n  File \"/usr/local/lib64/python3.12/site-packages/ldap/filter.py\",
          line 41, in escape_filter_chars\n    s = assertion_value.replace('\\\\',
          r'\\5c')\n        ^^^^^^^^^^^^^^^^^^^^^^^\nAttributeError: 'list' object
          has no attribute 'replace'\n>>> ldap.filter.escape_filter_chars([\"abc@*()/xyz\"],
          escape_mode=2)\nTraceback (most recent call last):\n  File \"<stdin>\",
          line 1, in <module>\n  File \"/usr/local/lib64/python3.12/site-packages/ldap/filter.py\",
          line 36, in escape_filter_chars\n    r.append(\"\\\\%02x\" % ord(c))\n                        ^^^^^^\nTypeError:
          ord() expected a character, but string of length 11 found\n```\n\n### Impact\nIf
          an application relies on the vulnerable method in the `python-ldap` library
          to escape untrusted user input, an attacker might be able to abuse the vulnerability
          to launch ldap injection attacks which could potentially disclose or manipulate
          ldap data meant to be inaccessible to them.\n\nWith Python being a dynamically
          typed language, and the commonly used `JSON` format supporting `list` and
          `dict`, it is to be expected that Python applications may commonly forward
          unchecked and potentially malicious `list` and `dict` objects to the vulnerable
          sanitization method.\n\nThe vulnerable `escape_mode=1` configuration does
          not appear to be widely used.\n\n### Suggested Fix\nAdd a type check at
          the start of the `ldap.filter.escape_filter_chars` method to raise an exception
          when the supplied `assertion_value` parameter is not of type `str`.\n\n####
          [CVE-2025-61912](https://redirect.github.com/python-ldap/python-ldap/security/advisories/GHSA-p34h-wq7j-h5v6)\n\n###
          Summary\n\n`ldap.dn.escape_dn_chars()` escapes `\\x00` incorrectly by emitting
          a backslash followed by a literal NUL byte instead of the RFC-4514 hex form
          `\\00`. Any application that uses this helper to construct DNs from untrusted
          input can be made to consistently fail before a request is sent to the LDAP
          server (e.g., AD), resulting in a client-side denial of service.\n\n###
          Details\n\nAffected function: `ldap.dn.escape_dn_chars(s)`\n\nFile: Lib/ldap/dn.py\n\nBuggy
          behavior:\nFor NUL, the function does:\n\n`s = s.replace('\\000', '\\\\\\000')
          \ # backslash + literal NUL`\n\nThis produces Python strings which, when
          passed to python-ldap APIs (e.g., `add_s`, `modify_s`, r`ename_s`, or used
          as search bases), contain an embedded NUL. python-ldap then raises ValueError:
          embedded null character (or otherwise fails) before any network I/O.\nWith
          correct RFC-4514 encoding (`\\00`), the client proceeds and the server can
          apply its own syntax rules (e.g., AD will reject NUL in CN with result:
          34), proving the failure originates in the escaping helper.\n\nWhy it matters:
          Projects follow the docs which state this function \u201Cshould be used
          when building LDAP DN strings from arbitrary input.\u201D The function\u2019s
          guarantee is therefore relied upon as a safety API. A single NUL in attacker-controlled
          input reliably breaks client workflows (crash/unhandled exception, stuck
          retries, poison queue record), i.e., a DoS.\n\nStandards: RFC 4514 requires
          special characters and controls to be escaped using hex form; a literal
          NUL is not a valid DN character.\n\nMinimal fix: Escape NUL as hex:\n\n`s
          = s.replace('\\x00', r'\\00')`\n\n### PoC\n\nPrereqs: Any python-ldap install
          and a reachable LDAP server (for the second half). The first half (client-side
          failure) does not require a live server.\n\n```import ldap\nfrom ldap.dn
          import escape_dn_chars, str2dn\n\nl = ldap.initialize(\"ldap://10.0.1.11\")
          \             # your lab DC/LDAP\nl.protocol_version = 3\nl.set_option(ldap.OPT_REFERRALS,
          0)\nl.simple_bind_s(r\"DSEC\\dani.aga\", \"PassAa1\")         \n\n# ---
          Attacker-controlled value contains NUL ---\ncn = \"bad\\0name\"\nescaped_cn
          = escape_dn_chars(cn)\ndn = f\"CN={escaped_cn},OU=Users,DC=dsec,DC=local\"\nattrs
          = [('objectClass', [b'user']), ('sAMAccountName', [b'badsam'])]\n\nprint(\"===
          BUGGY DN (contains literal NUL) ===\")\nprint(\"escaped_cn repr:\", repr(escaped_cn))\nprint(\"dn
          repr:\", repr(dn))\nprint(\"contains NUL?:\", \"\\x00\" in dn, \"at index:\",
          dn.find(\"\\x00\"))\n\nprint(\"=> add_s(buggy DN): expected client-side
          failure (no server contact)\")\ntry:\n    l.add_s(dn, attrs)\n    print(\"add_s(buggy):
          succeeded (unexpected)\")\nexcept Exception as e:\n    print(\"add_s(buggy):\",
          type(e).__name__, e)  # ValueError: embedded null character\n\n# --- Correct
          hex escape demonstrates the client proceeds to the server ---\nsafe_dn =
          dn.replace(\"\\x00\", r\"\\00\")                 # RFC 4514-compliant\nprint(\"\\n===
          HEX-ESCAPED DN (\\\\00) ===\")\nprint(\"safe_dn repr:\", repr(safe_dn))\nprint(\"=>
          sanity parse:\", str2dn(safe_dn))           # parses locally\n\nprint(\"=>
          add_s(safe DN): reaches server (AD will likely reject with 34)\")\ntry:\n
          \   l.add_s(safe_dn, attrs)\n    print(\"add_s(safe): success (unlikely
          without required attrs/rights)\")\nexcept ldap.LDAPError as e:\n    print(\"add_s(safe):\",
          e.__class__.__name__, e)  # e.g., result 34 Invalid DN syntax (AD forbids
          NUL in CN)\n```\n\nObserved result (example):\n\n`add_s(buggy): ValueError
          embedded null character` \u2190 client-side DoS\n\n`add_s(safe): INVALID_DN_SYNTAX
          (result 34, BAD_NAME)` \u2190 request reached server; rejection due to server
          policy, not client bug\n\n### Impact\n\nType: Denial of Service (client-side).\n\nWho
          is impacted: Any application that uses ldap.dn.escape_dn_chars() to build
          DNs from (partially) untrusted input\u2014e.g., user `creation/rename tools`,
          `sync/ETL jobs`, portals allowing self-service attributes, device onboarding,
          batch imports. A single crafted value with `\\x00` reliably forces exceptions/failures
          and can crash handlers or jam pipelines with poison records.\n\n---\n\n###
          Release Notes\n\n<details>\n<summary>python-ldap/python-ldap (python-ldap)</summary>\n\n###
          [`v3.4.5`](https://redirect.github.com/python-ldap/python-ldap/releases/tag/python-ldap-3.4.5):
          3.4.5\n\n[Compare Source](https://redirect.github.com/python-ldap/python-ldap/compare/python-ldap-3.4.4...python-ldap-3.4.5)\n\nReleased
          3.4.5 2025-10-10\n\nSecurity fixes:\n\n- CVE-2025-61911 (GHSA-r7r6-cc7p-4v5m):
          Enforce `str` input in\n  `ldap.filter.escape_filter_chars` with `escape_mode=1`;
          ensure proper\n  escaping. (thanks to lukas-eu)\n- CVE-2025-61912 (GHSA-p34h-wq7j-h5v6):
          Correct NUL escaping in\n  `ldap.dn.escape_dn_chars` to `\\00` per RFC 4514.
          (thanks to aradona91)\n\nFixes:\n\n- ReconnectLDAPObject now properly reconnects
          on UNAVAILABLE, CONNECT\\_ERROR\n  and TIMEOUT exceptions (previously only
          SERVER\\_DOWN), fixing reconnection\n  issues especially during server restarts\n-
          Fixed syncrepl.py to use named constants instead of raw decimal values\n
          \ for result types\n- Fixed error handling in SearchNoOpMixIn to prevent
          a undefined variable error\n\nTests:\n\n- Added comprehensive reconnection
          test cases including concurrent operation\n  handling and server restart
          scenarios\n\nDoc/\n\n- Updated installation docs and fixed various documentation
          typos\n- Added ReadTheDocs configuration file\n\nInfrastructure:\n\n- Add
          testing and document support for Python 3.13\n\n</details>\n\n---\n\n###
          Configuration\n\n\U0001F4C5 **Schedule**: Branch creation - \"\" (UTC),
          Automerge - At any time (no schedule defined).\n\n\U0001F6A6 **Automerge**:
          Disabled by config. Please merge this manually once you are satisfied.\n\n\u267B
          **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry
          checkbox.\n\n\U0001F515 **Ignore**: Close this PR and you won't be reminded
          about these updates again.\n\n---\n\n - [ ] <!-- rebase-check -->If you
          want to rebase/retry this PR, check this box\n\n---\n\nThis PR was generated
          by [Mend Renovate](https://mend.io/renovate/). View the [repository job
          log](https://developer.mend.io/github/osism/openstack-project-manager).\n<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNDMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE0My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZSJdfQ==-->\n"
        change_url: https://github.com/osism/openstack-project-manager/pull/245
        commit_id: 63995bd4078a10a4db68156603c28c6a8233a3f1
        patchset: 63995bd4078a10a4db68156603c28c6a8233a3f1
        project:
          canonical_hostname: github.com
          canonical_name: github.com/osism/openstack-project-manager
          name: osism/openstack-project-manager
          short_name: openstack-project-manager
          src_dir: src/github.com/osism/openstack-project-manager
        topic: null
      job: openstack-project-manager-tox
      jobtags: []
      max_attempts: 3
      message: VXBkYXRlIGRlcGVuZGVuY3kgcHl0aG9uLWxkYXAgdG8gdjMuNC41IFtTRUNVUklUWV0KClRoaXMgUFIgY29udGFpbnMgdGhlIGZvbGxvd2luZyB1cGRhdGVzOgoKfCBQYWNrYWdlIHwgQ2hhbmdlIHwgQWdlIHwgQ29uZmlkZW5jZSB8CnwtLS18LS0tfC0tLXwtLS18CnwgW3B5dGhvbi1sZGFwXShodHRwczovL3d3dy5weXRob24tbGRhcC5vcmcvKSAoW3NvdXJjZV0oaHR0cHM6Ly9yZWRpcmVjdC5naXRodWIuY29tL3B5dGhvbi1sZGFwL3B5dGhvbi1sZGFwKSwgW2NoYW5nZWxvZ10oaHR0cHM6Ly9yZWRpcmVjdC5naXRodWIuY29tL3B5dGhvbi1sZGFwL3B5dGhvbi1sZGFwL2Jsb2IvbWFpbi9DSEFOR0VTKSkgfCBgPT0zLjQuNGAgLT4gYD09My40LjVgIHwgWyFbYWdlXShodHRwczovL2RldmVsb3Blci5tZW5kLmlvL2FwaS9tYy9iYWRnZXMvYWdlL3B5cGkvcHl0aG9uLWxkYXAvMy40LjU/c2xpbT10cnVlKV0oaHR0cHM6Ly9kb2NzLnJlbm92YXRlYm90LmNvbS9tZXJnZS1jb25maWRlbmNlLykgfCBbIVtjb25maWRlbmNlXShodHRwczovL2RldmVsb3Blci5tZW5kLmlvL2FwaS9tYy9iYWRnZXMvY29uZmlkZW5jZS9weXBpL3B5dGhvbi1sZGFwLzMuNC40LzMuNC41P3NsaW09dHJ1ZSldKGh0dHBzOi8vZG9jcy5yZW5vdmF0ZWJvdC5jb20vbWVyZ2UtY29uZmlkZW5jZS8pIHwKCiMjIyBHaXRIdWIgVnVsbmVyYWJpbGl0eSBBbGVydHMKCiMjIyMgW0NWRS0yMDI1LTYxOTExXShodHRwczovL3JlZGlyZWN0LmdpdGh1Yi5jb20vcHl0aG9uLWxkYXAvcHl0aG9uLWxkYXAvc2VjdXJpdHkvYWR2aXNvcmllcy9HSFNBLXI3cjYtY2M3cC00djVtKQoKIyMjIFN1bW1hcnkKVGhlIHNhbml0aXphdGlvbiBtZXRob2QgYGxkYXAuZmlsdGVyLmVzY2FwZV9maWx0ZXJfY2hhcnNgIGNhbiBiZSB0cmlja2VkIHRvIHNraXAgZXNjYXBpbmcgb2Ygc3BlY2lhbCBjaGFyYWN0ZXJzIHdoZW4gYSBjcmFmdGVkIGBsaXN0YCBvciBgZGljdGAgaXMgc3VwcGxpZWQgYXMgdGhlIGBhc3NlcnRpb25fdmFsdWVgIHBhcmFtZXRlciwgYW5kIHRoZSBub24tZGVmYXVsdCBgZXNjYXBlX21vZGU9MWAgaXMgY29uZmlndXJlZC4KCiMjIyBEZXRhaWxzClRoZSBtZXRob2QgYGxkYXAuZmlsdGVyLmVzY2FwZV9maWx0ZXJfY2hhcnNgIHN1cHBvcnRzIDMgZGlmZmVyZW50IGVzY2FwaW5nIG1vZGVzLiBgZXNjYXBlX21vZGU9MGAgKGRlZmF1bHQpIGFuZCBgZXNjYXBlX21vZGU9MmAgaGFwcGVuIHRvIHJhaXNlIGV4Y2VwdGlvbnMgd2hlbiBhIGBsaXN0YCBvciBgZGljdGAgb2JqZWN0IGlzIHN1cHBsaWVkIGFzIHRoZSBgYXNzZXJ0aW9uX3ZhbHVlYCBwYXJhbWV0ZXIuIEhvd2V2ZXIsIGBlc2NhcGVfbW9kZT0xYCBoYXBwaWx5IGNvbXB1dGVzIHdpdGhvdXQgcGVyZm9ybWluZyBhZGVxdWF0ZSBsb2dpYyB0byBlbnN1cmUgYSBmdWxseSBlc2NhcGVkIHJldHVybiB2YWx1ZS4KCiMjIyBQb0MKYGBgCj4+PiBpbXBvcnQgbGRhcC5maWx0ZXIKYGBgCioqRXhwbG9pdGFibGUqKgpgYGAKPj4+IGxkYXAuZmlsdGVyLmVzY2FwZV9maWx0ZXJfY2hhcnMoWyJhYmNAKigpL3h5eiJdLCBlc2NhcGVfbW9kZT0xKQonYWJjQCooKS94eXonCj4+PiBsZGFwLmZpbHRlci5lc2NhcGVfZmlsdGVyX2NoYXJzKHsiYWJjQCooKS94eXoiOiAxfSwgZXNjYXBlX21vZGU9MSkKJ2FiY0AqKCkveHl6JwpgYGAKKipOb3QgZXhwbG9pdGFibGUqKgpgYGAKPj4+IGxkYXAuZmlsdGVyLmVzY2FwZV9maWx0ZXJfY2hhcnMoImFiY0AqKCkveHl6IiwgZXNjYXBlX21vZGU9MSkKJ2FiY0BcXDJhXFwyOFxcMjlcXDJmeHl6Jwo+Pj4gbGRhcC5maWx0ZXIuZXNjYXBlX2ZpbHRlcl9jaGFycyhbImFiY0AqKCkveHl6Il0sIGVzY2FwZV9tb2RlPTApClRyYWNlYmFjayAobW9zdCByZWNlbnQgY2FsbCBsYXN0KToKICBGaWxlICI8c3RkaW4+IiwgbGluZSAxLCBpbiA8bW9kdWxlPgogIEZpbGUgIi91c3IvbG9jYWwvbGliNjQvcHl0aG9uMy4xMi9zaXRlLXBhY2thZ2VzL2xkYXAvZmlsdGVyLnB5IiwgbGluZSA0MSwgaW4gZXNjYXBlX2ZpbHRlcl9jaGFycwogICAgcyA9IGFzc2VydGlvbl92YWx1ZS5yZXBsYWNlKCdcXCcsIHInXDVjJykKICAgICAgICBeXl5eXl5eXl5eXl5eXl5eXl5eXl5eXgpBdHRyaWJ1dGVFcnJvcjogJ2xpc3QnIG9iamVjdCBoYXMgbm8gYXR0cmlidXRlICdyZXBsYWNlJwo+Pj4gbGRhcC5maWx0ZXIuZXNjYXBlX2ZpbHRlcl9jaGFycyhbImFiY0AqKCkveHl6Il0sIGVzY2FwZV9tb2RlPTIpClRyYWNlYmFjayAobW9zdCByZWNlbnQgY2FsbCBsYXN0KToKICBGaWxlICI8c3RkaW4+IiwgbGluZSAxLCBpbiA8bW9kdWxlPgogIEZpbGUgIi91c3IvbG9jYWwvbGliNjQvcHl0aG9uMy4xMi9zaXRlLXBhY2thZ2VzL2xkYXAvZmlsdGVyLnB5IiwgbGluZSAzNiwgaW4gZXNjYXBlX2ZpbHRlcl9jaGFycwogICAgci5hcHBlbmQoIlxcJTAyeCIgJSBvcmQoYykpCiAgICAgICAgICAgICAgICAgICAgICAgIF5eXl5eXgpUeXBlRXJyb3I6IG9yZCgpIGV4cGVjdGVkIGEgY2hhcmFjdGVyLCBidXQgc3RyaW5nIG9mIGxlbmd0aCAxMSBmb3VuZApgYGAKCiMjIyBJbXBhY3QKSWYgYW4gYXBwbGljYXRpb24gcmVsaWVzIG9uIHRoZSB2dWxuZXJhYmxlIG1ldGhvZCBpbiB0aGUgYHB5dGhvbi1sZGFwYCBsaWJyYXJ5IHRvIGVzY2FwZSB1bnRydXN0ZWQgdXNlciBpbnB1dCwgYW4gYXR0YWNrZXIgbWlnaHQgYmUgYWJsZSB0byBhYnVzZSB0aGUgdnVsbmVyYWJpbGl0eSB0byBsYXVuY2ggbGRhcCBpbmplY3Rpb24gYXR0YWNrcyB3aGljaCBjb3VsZCBwb3RlbnRpYWxseSBkaXNjbG9zZSBvciBtYW5pcHVsYXRlIGxkYXAgZGF0YSBtZWFudCB0byBiZSBpbmFjY2Vzc2libGUgdG8gdGhlbS4KCldpdGggUHl0aG9uIGJlaW5nIGEgZHluYW1pY2FsbHkgdHlwZWQgbGFuZ3VhZ2UsIGFuZCB0aGUgY29tbW9ubHkgdXNlZCBgSlNPTmAgZm9ybWF0IHN1cHBvcnRpbmcgYGxpc3RgIGFuZCBgZGljdGAsIGl0IGlzIHRvIGJlIGV4cGVjdGVkIHRoYXQgUHl0aG9uIGFwcGxpY2F0aW9ucyBtYXkgY29tbW9ubHkgZm9yd2FyZCB1bmNoZWNrZWQgYW5kIHBvdGVudGlhbGx5IG1hbGljaW91cyBgbGlzdGAgYW5kIGBkaWN0YCBvYmplY3RzIHRvIHRoZSB2dWxuZXJhYmxlIHNhbml0aXphdGlvbiBtZXRob2QuCgpUaGUgdnVsbmVyYWJsZSBgZXNjYXBlX21vZGU9MWAgY29uZmlndXJhdGlvbiBkb2VzIG5vdCBhcHBlYXIgdG8gYmUgd2lkZWx5IHVzZWQuCgojIyMgU3VnZ2VzdGVkIEZpeApBZGQgYSB0eXBlIGNoZWNrIGF0IHRoZSBzdGFydCBvZiB0aGUgYGxkYXAuZmlsdGVyLmVzY2FwZV9maWx0ZXJfY2hhcnNgIG1ldGhvZCB0byByYWlzZSBhbiBleGNlcHRpb24gd2hlbiB0aGUgc3VwcGxpZWQgYGFzc2VydGlvbl92YWx1ZWAgcGFyYW1ldGVyIGlzIG5vdCBvZiB0eXBlIGBzdHJgLgoKIyMjIyBbQ1ZFLTIwMjUtNjE5MTJdKGh0dHBzOi8vcmVkaXJlY3QuZ2l0aHViLmNvbS9weXRob24tbGRhcC9weXRob24tbGRhcC9zZWN1cml0eS9hZHZpc29yaWVzL0dIU0EtcDM0aC13cTdqLWg1djYpCgojIyMgU3VtbWFyeQoKYGxkYXAuZG4uZXNjYXBlX2RuX2NoYXJzKClgIGVzY2FwZXMgYFx4MDBgIGluY29ycmVjdGx5IGJ5IGVtaXR0aW5nIGEgYmFja3NsYXNoIGZvbGxvd2VkIGJ5IGEgbGl0ZXJhbCBOVUwgYnl0ZSBpbnN0ZWFkIG9mIHRoZSBSRkMtNDUxNCBoZXggZm9ybSBgXDAwYC4gQW55IGFwcGxpY2F0aW9uIHRoYXQgdXNlcyB0aGlzIGhlbHBlciB0byBjb25zdHJ1Y3QgRE5zIGZyb20gdW50cnVzdGVkIGlucHV0IGNhbiBiZSBtYWRlIHRvIGNvbnNpc3RlbnRseSBmYWlsIGJlZm9yZSBhIHJlcXVlc3QgaXMgc2VudCB0byB0aGUgTERBUCBzZXJ2ZXIgKGUuZy4sIEFEKSwgcmVzdWx0aW5nIGluIGEgY2xpZW50LXNpZGUgZGVuaWFsIG9mIHNlcnZpY2UuCgojIyMgRGV0YWlscwoKQWZmZWN0ZWQgZnVuY3Rpb246IGBsZGFwLmRuLmVzY2FwZV9kbl9jaGFycyhzKWAKCkZpbGU6IExpYi9sZGFwL2RuLnB5CgpCdWdneSBiZWhhdmlvcjoKRm9yIE5VTCwgdGhlIGZ1bmN0aW9uIGRvZXM6CgpgcyA9IHMucmVwbGFjZSgnXDAwMCcsICdcXFwwMDAnKSAgIyBiYWNrc2xhc2ggKyBsaXRlcmFsIE5VTGAKClRoaXMgcHJvZHVjZXMgUHl0aG9uIHN0cmluZ3Mgd2hpY2gsIHdoZW4gcGFzc2VkIHRvIHB5dGhvbi1sZGFwIEFQSXMgKGUuZy4sIGBhZGRfc2AsIGBtb2RpZnlfc2AsIHJgZW5hbWVfc2AsIG9yIHVzZWQgYXMgc2VhcmNoIGJhc2VzKSwgY29udGFpbiBhbiBlbWJlZGRlZCBOVUwuIHB5dGhvbi1sZGFwIHRoZW4gcmFpc2VzIFZhbHVlRXJyb3I6IGVtYmVkZGVkIG51bGwgY2hhcmFjdGVyIChvciBvdGhlcndpc2UgZmFpbHMpIGJlZm9yZSBhbnkgbmV0d29yayBJL08uCldpdGggY29ycmVjdCBSRkMtNDUxNCBlbmNvZGluZyAoYFwwMGApLCB0aGUgY2xpZW50IHByb2NlZWRzIGFuZCB0aGUgc2VydmVyIGNhbiBhcHBseSBpdHMgb3duIHN5bnRheCBydWxlcyAoZS5nLiwgQUQgd2lsbCByZWplY3QgTlVMIGluIENOIHdpdGggcmVzdWx0OiAzNCksIHByb3ZpbmcgdGhlIGZhaWx1cmUgb3JpZ2luYXRlcyBpbiB0aGUgZXNjYXBpbmcgaGVscGVyLgoKV2h5IGl0IG1hdHRlcnM6IFByb2plY3RzIGZvbGxvdyB0aGUgZG9jcyB3aGljaCBzdGF0ZSB0aGlzIGZ1bmN0aW9uIOKAnHNob3VsZCBiZSB1c2VkIHdoZW4gYnVpbGRpbmcgTERBUCBETiBzdHJpbmdzIGZyb20gYXJiaXRyYXJ5IGlucHV0LuKAnSBUaGUgZnVuY3Rpb27igJlzIGd1YXJhbnRlZSBpcyB0aGVyZWZvcmUgcmVsaWVkIHVwb24gYXMgYSBzYWZldHkgQVBJLiBBIHNpbmdsZSBOVUwgaW4gYXR0YWNrZXItY29udHJvbGxlZCBpbnB1dCByZWxpYWJseSBicmVha3MgY2xpZW50IHdvcmtmbG93cyAoY3Jhc2gvdW5oYW5kbGVkIGV4Y2VwdGlvbiwgc3R1Y2sgcmV0cmllcywgcG9pc29uIHF1ZXVlIHJlY29yZCksIGkuZS4sIGEgRG9TLgoKU3RhbmRhcmRzOiBSRkMgNDUxNCByZXF1aXJlcyBzcGVjaWFsIGNoYXJhY3RlcnMgYW5kIGNvbnRyb2xzIHRvIGJlIGVzY2FwZWQgdXNpbmcgaGV4IGZvcm07IGEgbGl0ZXJhbCBOVUwgaXMgbm90IGEgdmFsaWQgRE4gY2hhcmFjdGVyLgoKTWluaW1hbCBmaXg6IEVzY2FwZSBOVUwgYXMgaGV4OgoKYHMgPSBzLnJlcGxhY2UoJ1x4MDAnLCByJ1wwMCcpYAoKIyMjIFBvQwoKUHJlcmVxczogQW55IHB5dGhvbi1sZGFwIGluc3RhbGwgYW5kIGEgcmVhY2hhYmxlIExEQVAgc2VydmVyIChmb3IgdGhlIHNlY29uZCBoYWxmKS4gVGhlIGZpcnN0IGhhbGYgKGNsaWVudC1zaWRlIGZhaWx1cmUpIGRvZXMgbm90IHJlcXVpcmUgYSBsaXZlIHNlcnZlci4KCmBgYGltcG9ydCBsZGFwCmZyb20gbGRhcC5kbiBpbXBvcnQgZXNjYXBlX2RuX2NoYXJzLCBzdHIyZG4KCmwgPSBsZGFwLmluaXRpYWxpemUoImxkYXA6Ly8xMC4wLjEuMTEiKSAgICAgICAgICAgICAgIyB5b3VyIGxhYiBEQy9MREFQCmwucHJvdG9jb2xfdmVyc2lvbiA9IDMKbC5zZXRfb3B0aW9uKGxkYXAuT1BUX1JFRkVSUkFMUywgMCkKbC5zaW1wbGVfYmluZF9zKHIiRFNFQ1xkYW5pLmFnYSIsICJQYXNzQWExIikgICAgICAgICAKCiMgLS0tIEF0dGFja2VyLWNvbnRyb2xsZWQgdmFsdWUgY29udGFpbnMgTlVMIC0tLQpjbiA9ICJiYWRcMG5hbWUiCmVzY2FwZWRfY24gPSBlc2NhcGVfZG5fY2hhcnMoY24pCmRuID0gZiJDTj17ZXNjYXBlZF9jbn0sT1U9VXNlcnMsREM9ZHNlYyxEQz1sb2NhbCIKYXR0cnMgPSBbKCdvYmplY3RDbGFzcycsIFtiJ3VzZXInXSksICgnc0FNQWNjb3VudE5hbWUnLCBbYidiYWRzYW0nXSldCgpwcmludCgiPT09IEJVR0dZIEROIChjb250YWlucyBsaXRlcmFsIE5VTCkgPT09IikKcHJpbnQoImVzY2FwZWRfY24gcmVwcjoiLCByZXByKGVzY2FwZWRfY24pKQpwcmludCgiZG4gcmVwcjoiLCByZXByKGRuKSkKcHJpbnQoImNvbnRhaW5zIE5VTD86IiwgIlx4MDAiIGluIGRuLCAiYXQgaW5kZXg6IiwgZG4uZmluZCgiXHgwMCIpKQoKcHJpbnQoIj0+IGFkZF9zKGJ1Z2d5IEROKTogZXhwZWN0ZWQgY2xpZW50LXNpZGUgZmFpbHVyZSAobm8gc2VydmVyIGNvbnRhY3QpIikKdHJ5OgogICAgbC5hZGRfcyhkbiwgYXR0cnMpCiAgICBwcmludCgiYWRkX3MoYnVnZ3kpOiBzdWNjZWVkZWQgKHVuZXhwZWN0ZWQpIikKZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgcHJpbnQoImFkZF9zKGJ1Z2d5KToiLCB0eXBlKGUpLl9fbmFtZV9fLCBlKSAgIyBWYWx1ZUVycm9yOiBlbWJlZGRlZCBudWxsIGNoYXJhY3RlcgoKIyAtLS0gQ29ycmVjdCBoZXggZXNjYXBlIGRlbW9uc3RyYXRlcyB0aGUgY2xpZW50IHByb2NlZWRzIHRvIHRoZSBzZXJ2ZXIgLS0tCnNhZmVfZG4gPSBkbi5yZXBsYWNlKCJceDAwIiwgciJcMDAiKSAgICAgICAgICAgICAgICAgIyBSRkMgNDUxNC1jb21wbGlhbnQKcHJpbnQoIlxuPT09IEhFWC1FU0NBUEVEIEROIChcXDAwKSA9PT0iKQpwcmludCgic2FmZV9kbiByZXByOiIsIHJlcHIoc2FmZV9kbikpCnByaW50KCI9PiBzYW5pdHkgcGFyc2U6Iiwgc3RyMmRuKHNhZmVfZG4pKSAgICAgICAgICAgIyBwYXJzZXMgbG9jYWxseQoKcHJpbnQoIj0+IGFkZF9zKHNhZmUgRE4pOiByZWFjaGVzIHNlcnZlciAoQUQgd2lsbCBsaWtlbHkgcmVqZWN0IHdpdGggMzQpIikKdHJ5OgogICAgbC5hZGRfcyhzYWZlX2RuLCBhdHRycykKICAgIHByaW50KCJhZGRfcyhzYWZlKTogc3VjY2VzcyAodW5saWtlbHkgd2l0aG91dCByZXF1aXJlZCBhdHRycy9yaWdodHMpIikKZXhjZXB0IGxkYXAuTERBUEVycm9yIGFzIGU6CiAgICBwcmludCgiYWRkX3Moc2FmZSk6IiwgZS5fX2NsYXNzX18uX19uYW1lX18sIGUpICAjIGUuZy4sIHJlc3VsdCAzNCBJbnZhbGlkIEROIHN5bnRheCAoQUQgZm9yYmlkcyBOVUwgaW4gQ04pCmBgYAoKT2JzZXJ2ZWQgcmVzdWx0IChleGFtcGxlKToKCmBhZGRfcyhidWdneSk6IFZhbHVlRXJyb3IgZW1iZWRkZWQgbnVsbCBjaGFyYWN0ZXJgIOKGkCBjbGllbnQtc2lkZSBEb1MKCmBhZGRfcyhzYWZlKTogSU5WQUxJRF9ETl9TWU5UQVggKHJlc3VsdCAzNCwgQkFEX05BTUUpYCDihpAgcmVxdWVzdCByZWFjaGVkIHNlcnZlcjsgcmVqZWN0aW9uIGR1ZSB0byBzZXJ2ZXIgcG9saWN5LCBub3QgY2xpZW50IGJ1ZwoKIyMjIEltcGFjdAoKVHlwZTogRGVuaWFsIG9mIFNlcnZpY2UgKGNsaWVudC1zaWRlKS4KCldobyBpcyBpbXBhY3RlZDogQW55IGFwcGxpY2F0aW9uIHRoYXQgdXNlcyBsZGFwLmRuLmVzY2FwZV9kbl9jaGFycygpIHRvIGJ1aWxkIEROcyBmcm9tIChwYXJ0aWFsbHkpIHVudHJ1c3RlZCBpbnB1dOKAlGUuZy4sIHVzZXIgYGNyZWF0aW9uL3JlbmFtZSB0b29sc2AsIGBzeW5jL0VUTCBqb2JzYCwgcG9ydGFscyBhbGxvd2luZyBzZWxmLXNlcnZpY2UgYXR0cmlidXRlcywgZGV2aWNlIG9uYm9hcmRpbmcsIGJhdGNoIGltcG9ydHMuIEEgc2luZ2xlIGNyYWZ0ZWQgdmFsdWUgd2l0aCBgXHgwMGAgcmVsaWFibHkgZm9yY2VzIGV4Y2VwdGlvbnMvZmFpbHVyZXMgYW5kIGNhbiBjcmFzaCBoYW5kbGVycyBvciBqYW0gcGlwZWxpbmVzIHdpdGggcG9pc29uIHJlY29yZHMuCgotLS0KCiMjIyBSZWxlYXNlIE5vdGVzCgo8ZGV0YWlscz4KPHN1bW1hcnk+cHl0aG9uLWxkYXAvcHl0aG9uLWxkYXAgKHB5dGhvbi1sZGFwKTwvc3VtbWFyeT4KCiMjIyBbYHYzLjQuNWBdKGh0dHBzOi8vcmVkaXJlY3QuZ2l0aHViLmNvbS9weXRob24tbGRhcC9weXRob24tbGRhcC9yZWxlYXNlcy90YWcvcHl0aG9uLWxkYXAtMy40LjUpOiAzLjQuNQoKW0NvbXBhcmUgU291cmNlXShodHRwczovL3JlZGlyZWN0LmdpdGh1Yi5jb20vcHl0aG9uLWxkYXAvcHl0aG9uLWxkYXAvY29tcGFyZS9weXRob24tbGRhcC0zLjQuNC4uLnB5dGhvbi1sZGFwLTMuNC41KQoKUmVsZWFzZWQgMy40LjUgMjAyNS0xMC0xMAoKU2VjdXJpdHkgZml4ZXM6CgotIENWRS0yMDI1LTYxOTExIChHSFNBLXI3cjYtY2M3cC00djVtKTogRW5mb3JjZSBgc3RyYCBpbnB1dCBpbgogIGBsZGFwLmZpbHRlci5lc2NhcGVfZmlsdGVyX2NoYXJzYCB3aXRoIGBlc2NhcGVfbW9kZT0xYDsgZW5zdXJlIHByb3BlcgogIGVzY2FwaW5nLiAodGhhbmtzIHRvIGx1a2FzLWV1KQotIENWRS0yMDI1LTYxOTEyIChHSFNBLXAzNGgtd3E3ai1oNXY2KTogQ29ycmVjdCBOVUwgZXNjYXBpbmcgaW4KICBgbGRhcC5kbi5lc2NhcGVfZG5fY2hhcnNgIHRvIGBcMDBgIHBlciBSRkMgNDUxNC4gKHRoYW5rcyB0byBhcmFkb25hOTEpCgpGaXhlczoKCi0gUmVjb25uZWN0TERBUE9iamVjdCBub3cgcHJvcGVybHkgcmVjb25uZWN0cyBvbiBVTkFWQUlMQUJMRSwgQ09OTkVDVFxfRVJST1IKICBhbmQgVElNRU9VVCBleGNlcHRpb25zIChwcmV2aW91c2x5IG9ubHkgU0VSVkVSXF9ET1dOKSwgZml4aW5nIHJlY29ubmVjdGlvbgogIGlzc3VlcyBlc3BlY2lhbGx5IGR1cmluZyBzZXJ2ZXIgcmVzdGFydHMKLSBGaXhlZCBzeW5jcmVwbC5weSB0byB1c2UgbmFtZWQgY29uc3RhbnRzIGluc3RlYWQgb2YgcmF3IGRlY2ltYWwgdmFsdWVzCiAgZm9yIHJlc3VsdCB0eXBlcwotIEZpeGVkIGVycm9yIGhhbmRsaW5nIGluIFNlYXJjaE5vT3BNaXhJbiB0byBwcmV2ZW50IGEgdW5kZWZpbmVkIHZhcmlhYmxlIGVycm9yCgpUZXN0czoKCi0gQWRkZWQgY29tcHJlaGVuc2l2ZSByZWNvbm5lY3Rpb24gdGVzdCBjYXNlcyBpbmNsdWRpbmcgY29uY3VycmVudCBvcGVyYXRpb24KICBoYW5kbGluZyBhbmQgc2VydmVyIHJlc3RhcnQgc2NlbmFyaW9zCgpEb2MvCgotIFVwZGF0ZWQgaW5zdGFsbGF0aW9uIGRvY3MgYW5kIGZpeGVkIHZhcmlvdXMgZG9jdW1lbnRhdGlvbiB0eXBvcwotIEFkZGVkIFJlYWRUaGVEb2NzIGNvbmZpZ3VyYXRpb24gZmlsZQoKSW5mcmFzdHJ1Y3R1cmU6CgotIEFkZCB0ZXN0aW5nIGFuZCBkb2N1bWVudCBzdXBwb3J0IGZvciBQeXRob24gMy4xMwoKPC9kZXRhaWxzPgoKLS0tCgojIyMgQ29uZmlndXJhdGlvbgoK8J+ThSAqKlNjaGVkdWxlKio6IEJyYW5jaCBjcmVhdGlvbiAtICIiIChVVEMpLCBBdXRvbWVyZ2UgLSBBdCBhbnkgdGltZSAobm8gc2NoZWR1bGUgZGVmaW5lZCkuCgrwn5qmICoqQXV0b21lcmdlKio6IERpc2FibGVkIGJ5IGNvbmZpZy4gUGxlYXNlIG1lcmdlIHRoaXMgbWFudWFsbHkgb25jZSB5b3UgYXJlIHNhdGlzZmllZC4KCuKZuyAqKlJlYmFzaW5nKio6IFdoZW5ldmVyIFBSIGJlY29tZXMgY29uZmxpY3RlZCwgb3IgeW91IHRpY2sgdGhlIHJlYmFzZS9yZXRyeSBjaGVja2JveC4KCvCflJUgKipJZ25vcmUqKjogQ2xvc2UgdGhpcyBQUiBhbmQgeW91IHdvbid0IGJlIHJlbWluZGVkIGFib3V0IHRoZXNlIHVwZGF0ZXMgYWdhaW4uCgotLS0KCiAtIFsgXSA8IS0tIHJlYmFzZS1jaGVjayAtLT5JZiB5b3Ugd2FudCB0byByZWJhc2UvcmV0cnkgdGhpcyBQUiwgY2hlY2sgdGhpcyBib3gKCi0tLQoKVGhpcyBQUiB3YXMgZ2VuZXJhdGVkIGJ5IFtNZW5kIFJlbm92YXRlXShodHRwczovL21lbmQuaW8vcmVub3ZhdGUvKS4gVmlldyB0aGUgW3JlcG9zaXRvcnkgam9iIGxvZ10oaHR0cHM6Ly9kZXZlbG9wZXIubWVuZC5pby9naXRodWIvb3Npc20vb3BlbnN0YWNrLXByb2plY3QtbWFuYWdlcikuCjwhLS1yZW5vdmF0ZS1kZWJ1ZzpleUpqY21WaGRHVmtTVzVXWlhJaU9pSTBNUzR4TkRNdU1TSXNJblZ3WkdGMFpXUkpibFpsY2lJNklqUXhMakUwTXk0eElpd2lkR0Z5WjJWMFFuSmhibU5vSWpvaWJXRnBiaUlzSW14aFltVnNjeUk2V3lKeVpXNXZkbUYwWlNKZGZRPT0tLT4K
      patchset: 63995bd4078a10a4db68156603c28c6a8233a3f1
      pipeline: check
      playbook_context:
        playbook_projects:
          trusted/project_0/github.com/osism/zuul-config:
            canonical_name: github.com/osism/zuul-config
            checkout: main
            commit: caa0df55ed55158f32903d317317ac95db4da6ce
          trusted/project_1/github.com/osism/openinfra-zuul-jobs:
            canonical_name: github.com/osism/openinfra-zuul-jobs
            checkout: master
            commit: 397240f42127868e42e86e6aeb120c2cb092d117
          untrusted/project_0/github.com/osism/openinfra-zuul-jobs:
            canonical_name: github.com/osism/openinfra-zuul-jobs
            checkout: master
            commit: 397240f42127868e42e86e6aeb120c2cb092d117
          untrusted/project_1/github.com/osism/zuul-config:
            canonical_name: github.com/osism/zuul-config
            checkout: main
            commit: caa0df55ed55158f32903d317317ac95db4da6ce
          untrusted/project_2/github.com/osism/openstack-project-manager:
            canonical_name: github.com/osism/openstack-project-manager
            checkout: main
            commit: 97dfdf8b35a1cc6aa51fd1554f754e311694e70f
        playbooks:
        - path: untrusted/project_0/github.com/osism/openinfra-zuul-jobs/playbooks/tox/run.yaml
          roles:
          - checkout: main
            checkout_description: zuul branch
            link_name: ansible/playbook_0/role_0/zuul-config
            link_target: untrusted/project_1/github.com/osism/zuul-config
            role_path: ansible/playbook_0/role_0/zuul-config/roles
          - checkout: master
            checkout_description: playbook branch
            link_name: ansible/playbook_0/role_1/openinfra-zuul-jobs
            link_target: untrusted/project_0/github.com/osism/openinfra-zuul-jobs
            role_path: ansible/playbook_0/role_1/openinfra-zuul-jobs/roles
      post_review: false
      post_timeout: null
      pre_timeout: null
      project:
        canonical_hostname: github.com
        canonical_name: github.com/osism/openstack-project-manager
        name: osism/openstack-project-manager
        short_name: openstack-project-manager
        src_dir: src/github.com/osism/openstack-project-manager
      projects:
        github.com/osism/openstack-project-manager:
          canonical_hostname: github.com
          canonical_name: github.com/osism/openstack-project-manager
          checkout: main
          checkout_description: zuul branch
          commit: 97dfdf8b35a1cc6aa51fd1554f754e311694e70f
          name: osism/openstack-project-manager
          required: false
          short_name: openstack-project-manager
          src_dir: src/github.com/osism/openstack-project-manager
      ref: refs/pull/245/head
      resources: {}
      tenant: osism
      timeout: 1800
      topic: null
      voting: true
