Skip to content

feat: comprehensive replacement for GetDomain with fallbacks#299

Open
rvazarkar wants to merge 3 commits intoldap_beta_fixesfrom
getdomain_replacement
Open

feat: comprehensive replacement for GetDomain with fallbacks#299
rvazarkar wants to merge 3 commits intoldap_beta_fixesfrom
getdomain_replacement

Conversation

@rvazarkar
Copy link
Copy Markdown
Contributor

@rvazarkar rvazarkar commented Apr 23, 2026

Description

Addresses one of the main offenders of uncontrolled LDAP calls in the codebase. Also fixes several gaps in netonly scenarios, and drastically improves domain info resolution with several fallbacks. Adds an optional flag to the LDAPConfig which allows

Motivation and Context

This PR addresses: [GitHub issue or Jira ticket number]

How Has This Been Tested?

Unit tests for many of the changes, real testing to follow.

Screenshots (if appropriate):

Types of changes

  • Chore (a change that does not modify the application functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

Summary by CodeRabbit

Release Notes

  • New Features

    • Added AllowFallbackToUncontrolledLdap configuration option to control LDAP fallback behavior.
    • Added CurrentUserDomain configuration option for domain specification.
  • Refactor

    • Domain resolution refactored to use asynchronous lookups with domain information caching and improved domain data handling.

…of the biggest ADSI gaps, as well as several improvements to the logic
@rvazarkar rvazarkar self-assigned this Apr 23, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c33fecd4-c8fd-40e6-9cd4-58b65e48c66e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

The pull request introduces an async, controlled domain-resolution pipeline with a new DomainInfo data model. It replaces synchronous LDAP domain lookups with asynchronous alternatives that honor configuration-based security settings and optional fallback behavior, affecting core utilities and multiple processor implementations.

Changes

Cohort / File(s) Summary
Domain Information Model
src/CommonLib/DomainInfo.cs
New sealed data class representing AD domain details with properties for DNS identifiers, resolved security info (SID, NetBIOS), PDC, forest name, and controller hostnames.
LDAP Configuration & Constants
src/CommonLib/LdapConfig.cs, src/CommonLib/Enums/LDAPProperties.cs
Added AllowFallbackToUncontrolledLdap and CurrentUserDomain properties to LdapConfig with updated ToString() output; added FSMORoleOwner and NCName LDAP attribute constants.
LDAP Interface Extension
src/CommonLib/ILdapUtils.cs
Added two async GetDomainInfoAsync overloads returning (Success, DomainInfo) tuples, with controlled LDAP queries and optional fallback behavior specified in documentation.
Core LDAP Utilities
src/CommonLib/LdapUtils.cs
Major refactor introducing multi-tier cached domain resolution (controlled LDAP pool → optional protocol LDAP → optional ADSI → opt-in uncontrolled fallback), new GetDomainInfoAsync implementations, and updated domain discovery callers to consume DomainInfo fields instead of legacy Domain/DirectoryEntry objects.
Connection Management
src/CommonLib/LdapConnectionPool.cs, src/CommonLib/ConnectionPoolManager.cs
Replaced synchronous CreateSearchRequest with async variant and transitioned domain resolution from GetDomain() to GetDomainInfoStaticAsync(), adjusting connection strategies to use new domain-info fields (PDC, controller list).
Processor Updates
src/CommonLib/Processors/GPOLocalGroupProcessor.cs
Migrated sync GetDomain() call to async GetDomainInfoAsync() when retrieving fallback domain information.
Test Infrastructure & Coverage
test/unit/Facades/MockLdapUtils.cs, test/unit/GPOLocalGroupProcessorTest.cs, test/unit/LDAPUtilsTest.cs
Added GetDomainInfoAsync stubs to mock utilities; updated GPO processor test to mock async domain-info API; introduced comprehensive test suite validating LdapConfig defaults, conditional ToString() behavior, domain-resolution short-circuits when fallback disabled, and helper method logic.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A domain dream, now async and bright,
With DomainInfo dancing through the night,
Caches cascade through layers so deep,
Controlled LDAP secrets we loyally keep,
No more waiting—progress takes flight! 🚀

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete and truncated. The 'Motivation and Context' section is missing the issue reference details, and the description text itself cuts off mid-sentence at 'Adds an optional flag to the LDAPConfig which allows'. Complete the truncated description sentence about the LDAPConfig flag, provide the full motivation section, and ensure all template sections are properly filled out before merging.
Docstring Coverage ⚠️ Warning Docstring coverage is 40.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: comprehensive replacement for GetDomain with fallbacks' accurately and specifically describes the main objective of the changeset: replacing GetDomain calls with a new system that includes fallback mechanisms.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch getdomain_replacement

Comment @coderabbitai help to get the list of available commands and usage tips.

@rvazarkar
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
test/unit/Facades/MockLdapUtils.cs (1)

706-712: Consider returning a baseline DomainInfo from the test double.

This mock already models a default TESTLAB.LOCAL environment via GetForest, GetDomainSidFromDomainName, and the identity resolvers. Returning (false, null) here makes the new API the only domain-resolution path that fails by default, which can turn unrelated tests red as production code migrates from GetDomain* to GetDomainInfoAsync(). A minimal happy-path DomainInfo would keep the base fixture behavior consistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/Facades/MockLdapUtils.cs` around lines 706 - 712, The mock's
GetDomainInfoAsync overloads return (false, null) which breaks the established
TESTLAB.LOCAL baseline used by GetForest and GetDomainSidFromDomainName; update
both GetDomainInfoAsync(string) and parameterless GetDomainInfoAsync() to return
Task.FromResult((true, baselineDomainInfo)) where baselineDomainInfo is a
minimal DomainInfo instance that matches the existing test fixture (e.g.,
DomainName "TESTLAB.LOCAL", the same ForestName and DomainSid used by
GetForest/GetDomainSidFromDomainName and any identity resolvers) so tests
continue to see a successful, consistent domain resolution path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/CommonLib/DomainInfo.cs`:
- Around line 12-33: DomainInfo instances are mutable and are being returned
from a static cache in LdapUtils.cs, allowing callers to mutate shared cached
objects; make DomainInfo immutable (remove public setters on Name,
DistinguishedName, ForestName, DomainSid, NetBiosName, PrimaryDomainController
and DomainControllers) and provide a single constructor (or init-only
properties) that fully initializes all properties (keeping DomainControllers as
an IReadOnlyList<string> defaulting to Array.Empty<string>()), then update
LdapUtils.cs to only store and return immutable DomainInfo objects (or to
clone/create a new DomainInfo instance at the cache boundary when
reading/writing) so cached state cannot be mutated by consumers.

In `@src/CommonLib/LdapUtils.cs`:
- Around line 35-36: The static cache _domainInfoCache currently keys only by
domain and can return entries produced by
TryGetDomainInfoViaUncontrolledFallback even when
LdapConfig.AllowFallbackToUncontrolledLdap is false; fix by encoding fallback
provenance in the cache key or marking cached DomainInfo entries with a boolean
(e.g., DomainInfo.IsFallback) and, at every cache read site (places that read
_domainInfoCache and return cached values, and where
TryGetDomainInfoViaUncontrolledFallback writes into the cache), revalidate
against LdapConfig.AllowFallbackToUncontrolledLdap—only return a cached
IsFallback entry when AllowFallbackToUncontrolledLdap is true, otherwise bypass
the cached fallback entry and proceed to controlled lookup or refresh the cache
entry appropriately.
- Around line 1456-1457: The XML doc comments in LdapUtils.cs reference a
non-existent member DomainInfo.FromFallback which causes unresolved cref
warnings; fix by either adding a boolean member/property named FromFallback to
the DomainInfo class (and document it) or update the XML docs to remove or
replace the cref with an existing member (e.g., an actual property like
DomainInfo.IsFallback or simply remove the cref). Update all occurrences that
reference DomainInfo.FromFallback so the cref targets a real symbol or is
removed to eliminate the unresolved-reference warnings.

---

Nitpick comments:
In `@test/unit/Facades/MockLdapUtils.cs`:
- Around line 706-712: The mock's GetDomainInfoAsync overloads return (false,
null) which breaks the established TESTLAB.LOCAL baseline used by GetForest and
GetDomainSidFromDomainName; update both GetDomainInfoAsync(string) and
parameterless GetDomainInfoAsync() to return Task.FromResult((true,
baselineDomainInfo)) where baselineDomainInfo is a minimal DomainInfo instance
that matches the existing test fixture (e.g., DomainName "TESTLAB.LOCAL", the
same ForestName and DomainSid used by GetForest/GetDomainSidFromDomainName and
any identity resolvers) so tests continue to see a successful, consistent domain
resolution path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 21e5791e-c2db-494d-afb2-12809aeda7a6

📥 Commits

Reviewing files that changed from the base of the PR and between d54aebf and 7db7e42.

📒 Files selected for processing (11)
  • src/CommonLib/ConnectionPoolManager.cs
  • src/CommonLib/DomainInfo.cs
  • src/CommonLib/Enums/LDAPProperties.cs
  • src/CommonLib/ILdapUtils.cs
  • src/CommonLib/LdapConfig.cs
  • src/CommonLib/LdapConnectionPool.cs
  • src/CommonLib/LdapUtils.cs
  • src/CommonLib/Processors/GPOLocalGroupProcessor.cs
  • test/unit/Facades/MockLdapUtils.cs
  • test/unit/GPOLocalGroupProcessorTest.cs
  • test/unit/LDAPUtilsTest.cs

Comment thread src/CommonLib/DomainInfo.cs Outdated
Comment on lines +12 to +33
public sealed class DomainInfo
{
/// <summary>Upper-cased DNS name of the domain (e.g. <c>CONTOSO.LOCAL</c>).</summary>
public string Name { get; set; }

/// <summary>Default naming context distinguished name (e.g. <c>DC=contoso,DC=local</c>).</summary>
public string DistinguishedName { get; set; }

/// <summary>Upper-cased DNS name of the forest root domain, when known.</summary>
public string ForestName { get; set; }

/// <summary>Domain SID (S-1-5-21-...) if resolved, otherwise null.</summary>
public string DomainSid { get; set; }

/// <summary>Legacy NetBIOS domain name if resolved from the Partitions container, otherwise null.</summary>
public string NetBiosName { get; set; }

/// <summary>DNS hostname of the PDC FSMO role owner if resolved, otherwise null.</summary>
public string PrimaryDomainController { get; set; }

/// <summary>DNS hostnames of known domain controllers for this domain.</summary>
public IReadOnlyList<string> DomainControllers { get; set; } = Array.Empty<string>();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Avoid caching a mutable public model by reference.

src/CommonLib/LdapUtils.cs stores DomainInfo instances in a static cache and returns the same reference to every caller. With public setters on every property here, any consumer can mutate a cached instance and affect later lookups process-wide. Please make the model immutable, or clone it at the cache boundary before this ships.

Based on learnings, public properties added in SharpHoundCommon may be consumed by dependent projects even when not used within this repository itself.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CommonLib/DomainInfo.cs` around lines 12 - 33, DomainInfo instances are
mutable and are being returned from a static cache in LdapUtils.cs, allowing
callers to mutate shared cached objects; make DomainInfo immutable (remove
public setters on Name, DistinguishedName, ForestName, DomainSid, NetBiosName,
PrimaryDomainController and DomainControllers) and provide a single constructor
(or init-only properties) that fully initializes all properties (keeping
DomainControllers as an IReadOnlyList<string> defaulting to
Array.Empty<string>()), then update LdapUtils.cs to only store and return
immutable DomainInfo objects (or to clone/create a new DomainInfo instance at
the cache boundary when reading/writing) so cached state cannot be mutated by
consumers.

Comment thread src/CommonLib/LdapUtils.cs
Comment thread src/CommonLib/LdapUtils.cs Outdated
Comment on lines +1456 to +1457
/// <see cref="DomainInfo"/> with <see cref="DomainInfo.FromFallback"/> = false. A failure
/// to acquire the connection or to read the default NC is fatal and returns <c>(false, null)</c>.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -e csproj -e props -e targets | xargs -r rg -n "GenerateDocumentationFile|TreatWarningsAsErrors|WarningsAsErrors"
rg -n "DomainInfo\\.FromFallback|<see cref=\"DomainInfo\\.FromFallback\""

Repository: SpecterOps/SharpHoundCommon

Length of output: 332


🏁 Script executed:

fd -e cs -i "*domaininfo*" | head -20

Repository: SpecterOps/SharpHoundCommon

Length of output: 362


🏁 Script executed:

cat -n src/CommonLib/DomainInfo.cs | head -100

Repository: SpecterOps/SharpHoundCommon

Length of output: 1916


🏁 Script executed:

rg -n "FromFallback" src/CommonLib/

Repository: SpecterOps/SharpHoundCommon

Length of output: 332


The XML docs reference a member that doesn't exist.

Both comments at lines 1456 and 1630 point to DomainInfo.FromFallback, but the DomainInfo class has no such member. These broken crefs will raise unresolved-reference warnings when XML docs are generated. Either add the missing member or remove the cref references.

Also applies to: 1629-1630

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CommonLib/LdapUtils.cs` around lines 1456 - 1457, The XML doc comments in
LdapUtils.cs reference a non-existent member DomainInfo.FromFallback which
causes unresolved cref warnings; fix by either adding a boolean member/property
named FromFallback to the DomainInfo class (and document it) or update the XML
docs to remove or replace the cref with an existing member (e.g., an actual
property like DomainInfo.IsFallback or simply remove the cref). Update all
occurrences that reference DomainInfo.FromFallback so the cref targets a real
symbol or is removed to eliminate the unresolved-reference warnings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant