🏢Active DirectoryComplianceMonitoringAccounts

AD compliance score scope: Reconcile five deductions

Reconcile five AD score deductions with domain-policy and adminCount evidence. Learn why a score of 100 cannot establish overall compliance.

Younes AZABARBy Younes AZABAR9 min read
AD compliance score scope: Reconcile five deductions

AD compliance score scope: What the Number Can Establish

AD compliance score scope determines what a number can support in an audit. In the etc-collector implementation reviewed on October 4, 2026, the score starts at 100 and subtracts points for four domain password-policy values and one count of enabled users carrying adminCount=1. It does not aggregate all findings or evaluate a complete compliance framework.

A score of 100 means that none of these five deduction conditions fired on the supplied inputs. It does not certify Active Directory, prove that every relevant object was collected, or establish that independent controls passed. Before using the number in a report, reconstruct its inputs and their collection evidence.

This analysis is pinned to collector revision 6a04c34b5ce01d3abf747c1d1c2f671eaa1d5e85, specifically public/internal/audit/detectors/ad/compliance/score.go, starting at Detect() on line 23. The examples below are calculations from that code, not measurements of a live domain. No Windows commands were executed against an environment for this article.

How the Five Deductions Work

With a non-null DomainInfo, each condition is evaluated independently. The fifth input is derived from the supplied Users collection. These are product thresholds; using them to reproduce a result does not establish that they are the organisation's approved baseline.

Input consumed by the detectorDeduction conditionPoints removedDeduction key
MinPwdLengthBelow 1410weak_password_length
PwdHistoryLengthBelow 245weak_password_history
LockoutThresholdAbove 10 or equal to 05weak_lockout_policy
MaxPwdAge, in collector whole daysAbove 90 or equal to 05weak_password_age
Number of supplied users with Disabled=false and AdminCount=trueAbove 2010excessive_admins

Equality matters: length 14, history 24, lockout threshold 10, maximum age 90 days and count 20 do not trigger their respective deductions. The counter counts qualifying objects once; it does not calculate a ratio of administrators to users.

If DomainInfo is null, the detector skips all four policy conditions and subtracts 15 under no_domain_info. It then evaluates the same user-count condition. A null object is therefore a different calculation branch, not four confirmed policy failures.

Prove the Reachable Range

With DomainInfo present, the largest possible deduction is 10 + 5 + 5 + 5 + 10 = 35. The smallest is zero. Scores therefore range from 65 to 100, in five-point increments. The defensive clamp to zero is never reached by these conditions.

Without DomainInfo, the score is 85 when the qualifying user count is at most 20, or 75 when it exceeds 20. It cannot take other values in that branch.

For example, all four policy conditions firing with count 20 produces 75; count 21 produces 65. A null domain object with count 20 produces 85. These hypothetical cases explain why a better-looking number can accompany weaker policy evidence.

The finding includes interpretation bands such as 50-69 and 0-49. Only 65 is reachable within the former band; the latter is entirely unreachable. Values below 65 cannot arise from this calculation alone. Those display labels do not expand the detector's coverage or turn its upper band into a compliance verdict.

Detection: Reconcile the Inputs with Independent Reads

Start with the domain, the chosen domain controller, the collection time and the inventory scope. A fresh independent read from another DC or a narrower search base may describe different evidence. Record those differences before treating a mismatch as a detector error.

Microsoft documents Get-ADDefaultDomainPasswordPolicy as retrieving the default policy for a domain. This read-only example uses its documented -Identity and -Server parameters. Replace the illustrative domain and DC with the reviewed targets:

Get-ADDefaultDomainPasswordPolicy -Identity 'example.com' -Server 'dc01.example.com'

Retain the full returned object and compare minimum length, password history, lockout threshold and maximum age with the collector inputs. Preserve duration units: the provider converts the directory maximum-age interval into whole days. Do not compare a raw directory interval or a formatted duration string directly with the integer 90.

The collector reads these policy attributes from the domain object. It does not use each user's resultant password settings object (PSO) for this score. Microsoft documents Get-ADUserResultantPasswordPolicy for that separate account-level question. Keep such evidence outside the reproduced score rather than substituting a selected user's policy for the domain inputs.

Read adminCount Without Renaming the Population

Microsoft's adminCount attribute definition describes an object whose ACLs have been tightened by the system because of direct or transitive membership in an administrative group. That describes a protection history; it is not a current Domain Admins membership query.

The provider converts the exact LDAP string "1" into the user Boolean AdminCount=true. The detector then excludes disabled users. Although the finding's description and scope text say “Domain Admins count,” the actual predicate never resolves that group. Preserve the distinction when explaining the result.

Use the documented Get-ADUser search and property parameters to retain the underlying user records:

Get-ADUser -Filter * -SearchBase 'DC=example,DC=com' -Server 'dc01.example.com' -Properties adminCount,Enabled

This is an adapted documentation example, not an executed inventory. From the returned records, reconcile the enabled users whose raw adminCount equals 1 with the collector's qualifying population. Retain object identities and exceptions, not just the final total. A missing attribute, a restricted base or a collection limit needs qualification before a low count is interpreted.

Build a Calculation and Provenance Sheet

For an actual audit, populate the following record with observed values. “Collection confirmed” means the required response and attributes were read successfully for the stated scope; a non-null structure alone does not establish that.

InputCollector sourceEvidence required to confirm collectionCalculation to reproduce
Minimum lengthDomain minPwdLength mapped to MinPwdLengthDomain object identity, returned attribute, successful parsing, DC and timeSubtract 10 when below 14
HistoryDomain pwdHistoryLength mapped to PwdHistoryLengthSame domain identity, attribute and parse evidenceSubtract 5 when below 24
Lockout thresholdDomain lockoutThresholdAttribute response and parse evidence; qualify any zeroSubtract 5 when above 10 or zero
Maximum ageDomain maxPwdAge converted to whole daysRaw value, conversion outcome and units; qualify any zeroSubtract 5 when above 90 or zero
Enabled adminCount populationSupplied Users, parsed adminCount and disabled flagSearch base, population/filter, limits, returned attributes and object identitiesSubtract 10 when count exceeds 20

Add the actual value, collection outcome, independent value and resulting deduction to each row. Where evidence is unavailable, write “unknown” in the evidence column while preserving the numerical input the code consumed. Do not silently replace the input with a guessed value.

Remediation: Resolve Evidence Gaps Before Changing Policy

First reconcile the calculation. Preserve the finding's score, deduction map, user statistics and collector revision. Recompute the result with the supplied inputs and identify whether the domain object was present, partial or null. This separates an arithmetic discrepancy from an incomplete observation.

Next qualify collection. A zero can represent a configured value or an absent/unparsed value in this provider. Obtain the relevant domain response and an independent read on the recorded DC. For users, reconcile search scope and limits with the intended population. A successful GetUsers call is necessary evidence, but does not by itself demonstrate that an intentionally limited inventory covers the whole domain.

If collection is incomplete, address the specific collection problem and run a fresh audit. Preserve the previous result as an incomplete observation. Do not describe the move from 85 to 100 after recovering domain information as four remediated policy controls without before-and-after policy evidence.

Then identify the organisation's baseline and its edition, owner and exceptions. Our comparison of ANSSI and CIS password-policy predicates explains why different checks can disagree on overlapping attributes. This score's five deductions are a separate product calculation; they are not a third framework baseline.

For a confirmed configuration gap, apply the approved policy change through the established administration process, retain its change record, repeat the independent read and re-audit the same scope. For an adminCount discrepancy, investigate the actual objects and current privileged memberships separately. Clearing the marker merely to lower the counter does not establish that privileges or ACL protection are correct.

Finally attach independent control evidence. Our mapping of AD and Azure compliance requirements to named identity controls provides context for that work. Logging, protocol protections, access governance and Entra controls need their own results. This score does not inspect those controls, and cannot fill their evidence gaps.

How EtcSec Detects This

COMPLIANCE_SCORE is the sole detector covered here. Its Detect() reads DomainInfo and Users, applies the five conditions above, and always returns one Info finding with Count=1, including at score 100. Count 1 denotes the returned score finding, not one affected user or one failed control.

The details include score, maxScore=100, deductions, totalUsers, enabledUsers and adminCount. User totals describe the supplied collection. They do not establish full directory coverage.

The provider paths explain the limits:

  • public/internal/providers/ldap/client.go:569 creates a domain-information object before querying RootDSE and the domain. Failed or empty responses can leave a partial object with zero values. The four policy values come from the domain object, not a resultant PSO per user.
  • public/internal/providers/ldap/parser.go:155 defines user AdminCount using the exact string comparison adminCount == "1". Its integer helper returns zero for absent values and ignores integer parsing errors.
  • public/internal/audit/engine.go:512 treats a returned GetDomainInfo error as non-fatal and supplies null domain information. That branch yields 85 before the optional 10-point adminCount deduction. In contrast, the earlier GetUsers error path aborts collection.
  • The calculation's reachable values are 65–100 with a domain object, or 75/85 without it. A non-null object is not proof of successful attribute collection, and interpretation bands below 65 are unreachable by this detector.

All code references above are pinned to the reviewed revision. Report the output as a partial product indicator supported by qualified inputs. A score of 100 establishes only that these five deduction conditions did not fire; overall compliance and certification require independently defined controls and evidence.

Explore the identity security pages that support this topic