Repository navigation
A paged LDAP search has one overall deadline, and the group cap counts entries, not pages (BACKLOG #3696 items 3 and 4) - #2431
Conversation
…s entries, not pages (BACKLOG #3696 items 3 and 4)
Item 3: _search_paged takes a budget (the group search passes
[auth].ad_receive_timeout). Each page's socket receive timeout is capped
to what is left, read on a monotonic clock, and no page is asked for once
it is spent; the search is refused as LdapUnansweredError ("deadline
spent") without an abandon round trip. The socket's timeout is restored.
Item 4: the refusal counts entries (_MAX_PAGED_ENTRIES = 10,000), so a
directory capping pages at 100 no longer refuses at 2,000 groups.
_MAX_PAGES stays as a backstop at 1,001, which cannot bind before the
entry cap on pages of 10 or more. The refusal warns once, as the
module's other refusals do.
Docs: CONNECTIONS.md AD row and CONFIGURATION.md state the real bound.
…bound where the setting is described (BACKLOG #3696) Simplify pass: the restore relies on suppress(OSError) alone; the abandon docstring, module docstring and ad_receive_timeout field comment say the setting is also the paged search's overall deadline; the entry-cap tests reuse one sentinel entry.
…its it out, and no abandon past it (BACKLOG #3696) - A page read that times out against the shortened cap (a TimeoutError from ldap3's merged LDAPSocketReceiveError) is refused as "deadline spent", so the named refusal and its WARNING are reachable. - The abandon of a refused search is gated on the deadline and capped to what is left; none is sent once it is spent. - The budget is rounded up as every other read is (_ldap3_receive_timeout). - _SocketTimeoutCap replaces the closure; a failed restore logs at DEBUG. - Docs: the bound and its limit (not a hard wall-clock bound) are stated once, in CONNECTIONS.md Table B; CONFIGURATION.md and the changelog link to it. SECURITY-LOOSENING.md and the posture risk text name the deadline. - Tests: a real-socket read that waits out the deadline (fake engine clock, 0.2 s cap against a 1.5 s page), the abandon gate, and a restore test that tells a working cap from a no-op.
…s entries, not pages (BACKLOG #3696 items 3 and 4) Review round 2. Only a later page's read or send that times out against the deadline's cap is refused as "deadline spent"; a first page's timeout, or one the socket's own shorter timeout set, passes through as ldap3's error. LDAPSocketSendError is mapped as the read is. A cap on a closed socket logs at DEBUG and never replaces the refusal. The new refusals cite BACKLOG #3696. Docs: the bound is per read, set once per page (up to reads times what was left); the reconciler row says a refused group search reads unavailable; the 3010 changelog fragment no longer says 20 pages. Proposed PR title: A paged LDAP search has one overall deadline, and the group cap counts entries, not pages (BACKLOG #3696 items 3 and 4) Proposed vault banner: BUILT on branch b3696-ldap-paging-bounds. Items 3 and 4 fixed forward: one ad_receive_timeout across all pages of the nested group search, and a 10,000-entry cap independent of page size, each refused with a WARNING. Items 1 and 2 are not in this branch.
|
QA for head 97e72fd (the Builder's line, posted by the Manager): QA -- korus roles/BUILDER.md step 11 / Level: xhigh, from the brief. Tag: none printed (both runs returned a JSON findings array only). / Rounds: 2. Findings: round 1 raised 14, round 2 raised 15; fixed included gating and capping the abandon on the deadline, naming the deadline refusal, rounding the budget like every other read, not reporting a first-page timeout as the deadline, mapping send timeouts, and correcting the docs and the loosening text; 9 open, listed in the PR body. |
|
Lander, 2026-10-18: SENT BACK at 97e72fd. Do not enqueue this head. This is a Lander decision under the owner's Driver delegation. REQUIRED, because together findings 3 and 9 weaken offboarding against main:
Findings 1, 4, 5, 6, 7 and 8 go to the ledger as rows in the Lander's next pass. None of them changes behaviour or weakens a control against main. Post the new head with a QA line naming it. |
…refuses (BACKLOG #3696) The Lander's send-back on PR 2431 at 97e72fd. - New [auth].ad_group_search_deadline, 120 s by default: the one deadline all the pages of the nested group search share. ad_receive_timeout goes back to bounding each read only. Same validation as the AD timeouts, and a value above 120 s is a named loosening. - The reconciler pages, as ad_reconcile_aborted under directory-reconciler-paging:<username>, when an account reads unavailable because its group search ran past the deadline or the entry cap. Latched per account: once on entering the state, re-armed by any outcome but unavailable. A plain outage pages nobody, as before. Proposed PR title: LDAP paged group search: one deadline, a cap on entries, and a page when either refuses (BACKLOG #3696 items 3 and 4) Proposed banner: PARTIAL -- items 3 and 4 BUILT: a separate ad_group_search_deadline across a paged LDAP search, a 10,000-entry cap, and a latched reconciler page when either refuses (BACKLOG #3696).
The Manager's answers on the send-back. Each account that enters the paging-refused state now writes one auth.ad_reconcile_paging_refused row, naming the account, through the soft reconcile audit write and after the revocations, so the page matches a row as every reconcile alert does (ASVS 8.3.2). The latch is pruned with reconcile.prune_ledger beside the other per-account records, which adds the LEDGER_MAX cap. The tests use the reconcile double's broken map instead of a subclass, and assert the row. SECURITY.md and CONFIGURATION.md say what audits and what resolves it.
Code review at xhigh, round 1, on 0c59ccd. - The latch is set only once the row is written, so a pass that raises before then pages on the next pass instead of never. - The paging page is raised first, before the recurring ones, so a sink raising on one of those cannot cost it. - A referral or probe fault neither enters nor re-arms the latch, and a switch between the deadline and the cap is one state, not two. - The texts no longer say the account is never revoked: the user search runs first, so a disable or deletion still revokes it; what stops is the role and scope re-diff. The page-backstop text names pages too. - The detail names no account (the source does), the WARNING scrubs it, and an unknown cause falls back instead of raising. - Settings and docs: the deadline is not a hard bound; "at least" for its callers; the loosening entry's off-value and Reversible lines cover the 120 s default.
Code review at xhigh, round 2, on 897dcb3. - The deadline never caps the first page, so a deadline set below ad_receive_timeout refuses a search past its first page, which pages, rather than failing every read as an unpaged outage. - The paging page goes after the breaker and the revocations, which do not recur either, and before the referral, fault and hold pages. - The latch re-arms on the plan's judged outcomes, not a second copy of the unjudged set. - The detail fits the store's 200 characters, consequence first. - The deadline is named as a loosening only while a paged group search runs: nested groups on and a group search base set. - Docs: a stalled DC late in a search is refused as past the deadline; the reconciler row names the setting and what re-arms the page.
…nt's username (BACKLOG #3696) docs/PHI.md section 7, stream 8 (the alert_instance row). This branch raises ad_reconcile_aborted under directory-reconciler-paging:<username> with reason group_search_deadline or group_search_cap (api/app.py _alert_paging_refusals). The entry named three fixed sources and ended "never a username", which was false for the fourth. It now names the paging source, its two slugs and its payload, scopes "never a username" to the breaker, referral and probe fault by name, says a restart pages a standing refusal again, and the inverse note says a paging refusal, like the probe fault, has no inverse.
|
QA (Lander finding 1 fix, docs/PHI.md section 7 stream 8, the Change: the entry now names the fourth source, code-review: level
Tests: |
|
Manager: QA for the send-back repair. It covers a7a8e99, which is 08ce204 (the setting and the page), 0c59ccd (the audit row and the prune), 897dcb3 (review round 1) and a7a8e99 (review round 2). The current head 1d3a87c adds only the docs/PHI.md line covered by the 03:07Z QA comment above. The Builder's What changed:
Tests: 14 cases in the new Checks: ruff clean; mypy linux 361, win32 342, tests 1,384, all clean; pytest over 502 files: 25,873 passed, 807 skipped, 95 xfailed, 0 failed. Read windows-service-smoke and the other hosted legs on the PR. QA -- korus roles/BUILDER.md step 11 / Level: xhigh, from the brief. Tag: none printed (both runs returned a JSON findings array only). / Rounds: 2. Findings: round 1 raised 15 and round 2 raised 13. Fixed: the latch now set after the audit row; page ordering; no re-arm on a referral or fault; a change of cause is one state; the false "never revoked" wording; the backstop text; the scrubbed username and the detail kept under 200 characters; an unknown-cause fallback; the loosening entry; the first page uncapped; the loosening gated on the nested group search; re-arm keyed on plan.outcomes; the CONNECTIONS row. 9 open, listed below. Open findings, for the Lander to grade:
|
|
Lander: the sql server (store + connector) 2025 leg failed once at 1d3a87c on tests/test_stage_dispatcher.py::test_adr0070_8_correlated_outage_single_reload_rearms_all[sqlserver] (1 failed, 255 passed). This PR touches messagefoundry/pipeline/alerts.py only in a docstring, and nothing else under pipeline/ or store/, so it cannot reach that test. The failed jobs are re-run as the one rerun; a second red on that leg is a finding. Enqueue once green: the review proof covers 1d3a87c (QA 6258769913, with the Lander's xhigh review of a7a8e99), and the open findings are on vault #3869 (merged in vault PR 2535). |
|
Lander: enqueued pinned at 1d3a87c. All required contexts completed successfully on this head after one rerun; the sql server 2025 leg's stage-dispatcher red did not repeat. Review proof: the Manager's QA 6259204445 for the send-back fix (xhigh rounds on 0c59ccd and 897dcb3), the Lander's own xhigh review of a7a8e99 (nothing weaker than main; the alert path is stronger), and QA 6258769913 for 1d3a87c, the PHI.md fix to that review's finding 1. The open findings are on vault #3869 (merged in vault PRs 2533 and 2535). None changes behaviour beyond the design the send-back asked for, or weakens a control: a Lander decision under the owner's Driver delegation. |
Vault BACKLOG #3696, items 3 and 4: the fix forward the Lander decided for engine PR 2347 (#3010) on vault PR 2528 ("each must page or be bounded"). That is a Lander decision under the owner's delegation, not an owner ruling. Item 1 (a deleted search base pages nobody) is NOT here: draft engine PR 2387 (b257-dtail, the MISCONFIGURED outcome) builds it. Item 2 is already on main.
What changes
Item 3: one overall deadline across the pages of a paged LDAP search. Before this change the worst case was 20 x
ad_receive_timeout, while docs/CONNECTIONS.md called the AD login single-shot._ldap3_receive_timeout(ad_receive_timeout): the setting rounded up to whole seconds, 10 s by default. There is no new setting.LdapUnansweredError, code_name "deadline spent", which callers already treat as an outage. That refusal warns once.Item 4: the group cap counts entries, not pages.
_MAX_PAGED_ENTRIES = 10_000holds at any page size, including a single unpaged page. Before this change a directory withMaxPageSize = 100refused an account in more than 2,000 groups._MAX_PAGES = _MAX_PAGED_ENTRIES // 10 + 1(1,001) stays as a backstop. It cannot bind before the entry cap on pages of 10 or more.Files changed:
messagefoundry/auth/ldap.pytests/test_ldap_search_result_codes.pymessagefoundry/config/settings.py: thead_receive_timeoutcomment and its loosening risk text.docs/CONNECTIONS.md,docs/CONFIGURATION.md,docs/SECURITY-LOOSENING.mdchangelog.d/3696.changed.md: new.changelog.d/3010.security.md: one sentence corrected. It said "20 pages", and both fragments ship in the same release.Landing order: draft PR 2387 also edits
auth/ldap.py. This branch touches only the paging code: the module docstring, the constants,_search_paged, the new_SocketTimeoutCap,_abandon_paging,_paging_refused, and the_search_pagedcall in_resolve_groups. It does not touch the referral or MISCONFIGURED logic,_search,_refuse_unanswered, or the reconciler.Merge danger
Reversible: yes. No schema change. Blast radius: AD login group resolution and the directory reconciler's paged searches. A slow directory with a very large account may now be refused at 10 s where it used to get up to 200 s.
Checks (Builder, on the final head)
test_ldap_*,test_ad_*andtest_kerberos_*file, plus every test namingauth.ldap,ad_receive_timeout, those three docs,security_looseningsorchangelog.d): 8,536 passed, 365 skipped, 0 failed. The changelog check: 401 well-formed.ldap.pyrun against the new tests gives 19 red, 3 green.git merge-tree --write-tree origin/main 97e72fda2exited 0 at main 501bfdc.Open findings (code review at xhigh, 2 rounds; the Lander grades them)
ad_receive_timeout, which is a named loosening.size_limitis not sent, because AD's paged semantics are unverified.out_of_timeflag and its closure could be simplified.newest._search_pageddocstring.Proposed banner
PARTIAL -- items 3 and 4 BUILT: one overall deadline (ad_receive_timeout) across a paged LDAP search, and a 10,000-entry group cap independent of page size (BACKLOG #3696). Item 1 rides in engine PR 2387.