Repository navigation
Fix crash on --action get by reading paged_search response dicts (Fixes #5) - #6
Merged
Merged
Conversation
#5) paged_search() yields raw LDAP response dictionaries, not ldap3 Entry objects, so `.entry_dn` and `['objectSid'].raw_values` raised AttributeError in LAPSio.get(). Read `dn` and `raw_attributes` from the response dict instead, skip searchResRef referrals, and return early when the target computer is not found so the follow-up search is not issued with a None search base.
This was referenced Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linked Issue
Closes #5Root Cause
LAPSio.get()treated the values yielded byldap_session.extend.standard.paged_search()as ldap3Entryobjects and accessedresponses[0].entry_dnandresponses[0]['objectSid'].raw_values[0].paged_search()never yieldsEntryobjects:paged_search_generator()yields the items ofconnection.response, which are the raw decoded LDAP response dictionaries with the keysdn,raw_dn,attributes,raw_attributesandtype.Entryobjects only ever live inconnection.entries, which is whyLAPSio.set()— which readsself.ldap_session.entries[0]after a plainsearch()— was never affected. The mismatch raisedAttributeError: 'dict' object has no attribute 'entry_dn'on every--action getrun that targets a specific computer, and the surroundingexcept IndexErrordid not catch it.Note on scope: this is not an ldap3 2.9 regression.
paged_searchyields response dicts in 2.8.1 as well (identicalyield responses.pop()/connection.response = responsescode inldap3/extend/standard/PagedSearch.py), so pinningldap3<2.9would not have avoided the crash. The bug was in pyLAPS, at every ldap3 version.The same block held a second defect on the same code path: when the computer was not found,
dnstayedNone, the code printed[!] Target computer does not exist!and then fell through to a secondpaged_search(None, ...), crashing withTypeError: cannot convert 'NoneType' object to bytes. Fixing only the dict access would have moved the crash rather than removed it, so both are corrected here.Fix Description
Replace the
Entry-style access inLAPSio.get()with a loop over the paged-search responses that readsentry['dn']andentry['raw_attributes']['objectSid'], skippingsearchResRefreferral entries (paged_searchinterleaves them withsearchResEntryitems, soresponses[0]is not guaranteed to be a result).objectSidis read defensively via.get()so a response without the attribute yieldssid = Noneinstead of aKeyError. When nosearchResEntryis found, the function now prints the same two existing messages and returns an empty list instead of issuing a search with aNonebase.Rejected alternative: pinning
ldap3<2.9inrequirements.txt, as suggested in the issue. Verified against ldap3 2.8.1 thatpaged_searchreturns dicts there too, so the pin would not fix the crash and would only hold the project back on an old dependency.No user-facing output strings changed.
How Verified
Runtime. Drove
LAPSio.get()against an ldap3MOCK_SYNCin-memory directory (OFFLINE_AD_2012_R2schema, oneCN=DC01,OU=Domain Controllers,DC=domain,DC=localcomputer carryingsAMAccountName,objectSidandms-Mcs-AdmPwd) on ldap3 2.9.1 / Python 3.12, exercising the three code paths.Before the fix:
After the fix:
The returned dicts keep the shape
__main__expects (r["sAMAccountName"][0].decode(...),r["ms-Mcs-AdmPwd"][0].decode(...)), so the caller's sort and print are unchanged.Static.
paged_search_generator()inldap3/extend/standard/PagedSearch.pyextendsresponsesfromconnection.responseand yields those items directly;connection.responseentries are dicts, neverEntryobjects. Confirmed by printingtype(responses[0])(<class 'dict'>) andsorted(responses[0].keys())(['attributes', 'dn', 'raw_attributes', 'raw_dn', 'type']) against the mock server.CI:
python3 -m py_compile pyLAPS.pyandpython3 pyLAPS.py --helpboth pass.Test Coverage
None. The repository has no test suite and no test dependency; CI runs
python3 pyLAPS.py --helponly. Adding one would mean introducing a test harness and an ldap3 mock-directory fixture, which is beyond a one-function bug fix. The mock-directory driver described above was used to verify the fix and is available if a test suite is added; filing that as separate follow-up work.Scope of Change
pyLAPS.py(LAPSio.get(), 13 insertions / 6 deletions)return []on the not-found path, which removes the second crash on that same path; output strings are identical.Risk and Rollout
Confined to
LAPSio.get().--action setis untouched. Worst case for a regression is thegetpath, which crashed unconditionally before this change, so there is nothing working to break. Safe to merge directly.Notes
Two pre-existing defects were found in the same file and deliberately left out of this PR to keep it single-purpose; both warrant their own issues:
LAPSio.get()(L293, L310, L317) andLAPSio.set()(L265) use the module-level globalldap_sessioninstead ofself.ldap_session. It works only because__main__happens to bind that global; importingpyLAPSas a module and callingLAPSio.get()raisesNameError.LAPSio.set()(L268–L273) reassigns the loop variableresultsinside its ownfor entry in resultsloop, then testslen(results) == 0on it, so the "Could not query target computer properties" guard never behaves as intended.