Skip to content

Network - 27021 - Private Access applications are governed by least-privilege segmentation, strong authentication, and constrained administration - #1476

Merged
Thomas Detzner (tdetzner) merged 6 commits into
astaykov/preview-reportfrom
Sankey-27021
Aug 4, 2026
Merged

Network - 27021 - Private Access applications are governed by least-privilege segmentation, strong authentication, and constrained administration#1476
Thomas Detzner (tdetzner) merged 6 commits into
astaykov/preview-reportfrom
Sankey-27021

Conversation

@Manoj-Kesana

Copy link
Copy Markdown
Collaborator

No description provided.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Manoj Kesana (@Manoj-Kesana) Please, address my feedback.

The implementation introduces a shared cross-runspace data store and a Private Access Sankey overview that aggregates checks 25384, 25395, and 25396. The PowerShell parses successfully and the patch is structurally clean, but the aggregation currently loses child-check verdicts and availability state.

That loss of state can cause failed, skipped, timed-out, partially executed, or population-mismatched checks to appear green, yellow, or absent in the overview. In particular:

  • Failing scoped administrator assignments can be labelled App-scoped admin — Zero Trust.
  • Unprotected Quick Access applications can disappear from the funnel.
  • Equal-size but different application populations are not detected.
  • Missing child output is treated as manual review instead of skipped/unavailable.
  • Partial test execution can produce a misleading card claiming zero applications were evaluated.

These issues conflict with spec 27021, which states that child roll-up verdicts determine gate status and that skipped children must be represented as unavailable/gray rather than converted into pass, fail, or investigate.


Finding 1 — Blocking: failing administration assignments are rendered as Zero Trust

Relevant code

Publisher: src/powershell/tests/Test-Assessment.25384.ps1, lines 389–392

Add-ZtTestData -Name 'PrivateAccessAdministration' -Value ([PSCustomObject]@{
    TenantWide = $tenantWideAssignments.Count
    Scoped     = $scopedAssignments.Count
})

Consumer: src/powershell/private/tenantinfo/Add-ZtOverviewPrivateAccess.ps1, lines 57–70

$tenantWideAdmin = ($administration.TenantWide -as [int]) ?? 0
$scopedAdmin = ($administration.Scoped -as [int]) ?? 0

$nodes = @(
    # ...
    @{ source = 'Application Administrator assignments'; target = 'Tenant-wide admin - at-risk'; value = $tenantWideAdmin }
    @{ source = 'Application Administrator assignments'; target = 'App-scoped admin - Zero Trust'; value = $scopedAdmin }
)

Risk flag: src/powershell/private/tenantinfo/Add-ZtOverviewPrivateAccess.ps1, line 77

adminAtRisk = $tenantWideAdmin -gt 0

Problem

Check 25384 fails for more than tenant-wide scope. It also sets $passed = $false when an assignment principal is a group, service principal, or guest user. The published dashboard payload discards that verdict and reduces the result to only two counts: TenantWide and Scoped.

Every non-tenant-wide assignment is then sent to the green App-scoped admin - Zero Trust node. As a result, a scoped assignment held by a guest, group, or service principal can make check 25384 fail while the dashboard:

  • renders the assignment as green;
  • leaves adminAtRisk false; and
  • does not tint the terminal Zero Trust node.

This contradicts spec 27021, where the child roll-up verdict determines the gate status.

Required correction

Publish or retrieve the child roll-up status and preserve it in the overview. Separate scoped assignments that satisfy all 25384 pass criteria from scoped assignments that fail principal or applicability checks. Do not label all scoped assignments as Zero Trust.

Suggested PR review comment

Scoped includes every non-tenant-wide assignment, but check 25384 also fails scoped assignments held by groups, guests, or service principals. Those failing assignments are consequently rendered as App-scoped admin - Zero Trust, while adminAtRisk remains false. Publish the child verdict and/or separate compliant scoped assignments from problematic scoped assignments before assigning this green flow.


Finding 2 — Blocking: Quick Access and authentication-only applications disappear

Relevant code

Source population: src/powershell/private/tenantinfo/Add-ZtOverviewPrivateAccess.ps1, lines 35–39

$broadSegments = @($segmentation | Where-Object { $_.Status -eq 'Fail' }).Count
$segmentationReview = @($segmentation | Where-Object { $_.Status -eq 'ManualReview' }).Count
$leastPrivilegeApps = @($segmentation | Where-Object { $_.Status -eq 'Pass' })

All Private Access apps source flows are derived exclusively from these segmentation rows.

Authentication publisher: src/powershell/tests/Test-Assessment.25396.ps1, lines 329–340

Add-ZtTestData -Name 'PrivateAccessAuthentication' -Value @(
    foreach ($detail in $allAppDetails) {
        [PSCustomObject]@{
            AppId  = $detail.AppId
            Status = switch ($detail.Status) {
                'Protected'   { 'Pass' }
                'Unprotected' { 'Fail' }
                default       { 'ManualReview' }
            }
        }
    }
)

Displayed denominator: src/powershell/private/tenantinfo/Add-ZtOverviewPrivateAccess.ps1, line 74

description = "$($segmentation.Count) Private Access application(s) evaluated. $strongAuth reached the Zero Trust set by clearing both least-privilege segmentation and strong authentication."

Problem

The child checks do not use the same population:

  • Check 25395 publishes only PrivateAccessNonWebApplication applications.
  • Check 25396 publishes both PrivateAccessNonWebApplication and NetworkAccessQuickAccessApplication applications.

The overview nevertheless defines the source population entirely from 25395. Authentication-only applications therefore disappear from the funnel. This can hide an unprotected Quick Access application even though it causes 25396 to fail.

The description also reports $segmentation.Count, not the larger or unified application population required by spec 27021.

Required correction

Either align both child checks to the same application population or construct the parent population from the union of normalized, distinct App IDs. Authentication-only applications need an explicit unavailable/manual-review segmentation state rather than being omitted. The child verdict must remain failed even when a failing authentication row is outside the segmentation-clean subset.

Suggested PR review comment

The source population is built exclusively from 25395 rows, but 25396 also evaluates Quick Access applications. Authentication-only apps therefore disappear from the funnel, including potentially unprotected Quick Access apps. Spec 27021 requires the larger population when children disagree. Build the denominator from distinct App IDs across both datasets, or align the child populations explicitly.


Finding 3 — Important: population mismatch detection compares counts, not App ID sets

Relevant code

src/powershell/private/tenantinfo/Add-ZtOverviewPrivateAccess.ps1, line 79

populationMismatch = ($segmentation.Count -ne $authentication.Count)

Problem

Equal counts do not prove that the two children evaluated the same applications. For example:

  • Segmentation: {A, B}
  • Authentication: {A, C}

Both collections have a count of two, so populationMismatch is false. Nevertheless:

  • B has no authentication result and becomes manual review.
  • C has no segmentation result and disappears entirely.

Raw counts may also include duplicate App IDs, while spec 27021 defines the population in terms of distinct applications.

Required correction

Normalize App IDs, remove duplicates, and compare sets in both directions. Derive the source denominator from the intended distinct set or union rather than raw row counts.

Suggested PR review comment

Comparing only counts does not establish that the children evaluated the same applications. {A,B} and {A,C} both have count 2 but disagree on one app in each direction. Compare normalized distinct App ID sets, and derive the source denominator from their union.


Finding 4 — Important: missing child output is incorrectly classified as manual review

Relevant code

Data retrieval: src/powershell/private/tenantinfo/Add-ZtOverviewPrivateAccess.ps1, lines 25–27

$segmentation = @(Get-ZtTestData -Name 'PrivateAccessSegmentation')
$authentication = @(Get-ZtTestData -Name 'PrivateAccessAuthentication')
$administration = Get-ZtTestData -Name 'PrivateAccessAdministration'

Default classification: src/powershell/private/tenantinfo/Add-ZtOverviewPrivateAccess.ps1, lines 48–55

foreach ($app in $leastPrivilegeApps) {
    switch ($authByAppId[[string]$app.AppId]) {
        'Pass' { $strongAuth++ }
        'Fail' { $passwordOnly++ }
        # An app with no matching authentication row was not evaluated by 25396
        default { $authenticationReview++ }
    }
}

Problem

The shared payload contains no child execution status. A missing authentication row can mean:

  • 25396 was skipped because of licensing or service connectivity;
  • 25396 timed out;
  • 25396 threw before publishing;
  • selective execution through -Tests excluded 25396;
  • the children genuinely evaluated different populations; or
  • an individual data retrieval omitted the app.

All these cases currently increment authenticationReview. This converts unavailable or failed collection into an application-level Investigate result. Spec 27021 explicitly requires skipped children to render gray rather than being treated as pass, fail, or manual review.

The same loss of availability state affects segmentation and administration.

Required correction

Consume each child’s TestStatus/TestSkipped state or publish an explicit structured envelope containing execution status, verdict, and rows. Only classify an unmatched App ID as manual review when both child datasets completed successfully and the mismatch is genuine.

Suggested PR review comment

A missing authentication row is not necessarily ManualReview: 25396 may have been skipped, timed out, failed before publishing, or excluded through -Tests. The spec requires skipped children to render gray. Consume the child execution/result status and increment authenticationReview only when both datasets completed successfully and the App ID is genuinely unmatched.


Finding 5 — Important: partial child data creates a misleading overview

Relevant code

src/powershell/private/tenantinfo/Add-ZtOverviewPrivateAccess.ps1, lines 29–34

if ($segmentation.Count -eq 0 -and $authentication.Count -eq 0 -and $null -eq $administration) {
    Write-PSFMessage '🟦 Skipping: No Private Access check results available' -Tag Test -Level VeryVerbose
    Add-ZtTenantInfo -Name $tenantInfoName -Value $null
    return
}

Problem

The function skips only when all three payloads are absent. If only one child publishes data, the function still builds a summary as though the funnel were valid.

Examples:

  • If only 25396 ran, the description claims 0 Private Access application(s) evaluated even though authentication rows exist.
  • If 25396 is absent but 25395 ran, every segmentation-clean app becomes authentication manual review.
  • If 25384 is absent, its band silently collapses to zero instead of being marked unavailable.
  • If only 25384 ran, the card can show an administration band while implying a complete Private Access posture overview.

Required correction

Handle each child’s availability independently. A complete overview should distinguish completed, skipped, failed, unavailable, and partially executed gates. Do not generate a complete-looking funnel from partial row data without an explicit degraded-state indication.

Suggested PR review comment

This guard skips only when all three payloads are absent. If only 25396 ran, the overview is still created with zero segmentation flows and a description claiming 0 Private Access application(s) evaluated, despite authentication rows being available. Handle each child’s availability independently and represent unavailable gates explicitly instead of constructing a partially misleading funnel.


Finding 6 — Important: the parent never implements the required overall determination

Relevant code

src/powershell/private/tenantinfo/Add-ZtOverviewPrivateAccess.ps1, lines 73–80

$summary = @{
    description = "$($segmentation.Count) Private Access application(s) evaluated. $strongAuth reached the Zero Trust set by clearing both least-privilege segmentation and strong authentication."
    nodes       = $nodes
    adminAtRisk = $tenantWideAdmin -gt 0
    populationMismatch = ($segmentation.Count -ne $authentication.Count)
}

Problem

Spec 27021 defines an overall determination:

  • Pass when all three children pass.
  • Investigate when none fails and one or more investigates.
  • Fail when any child fails.

The summary contains no overall result and no per-gate child verdicts. It derives visual state only from selected row counts. This is the underlying reason a child can fail while its corresponding visual flow appears green or absent.

Required correction

Retrieve the roll-up result for checks 25384, 25395, and 25396 and include explicit gate and overall statuses in the summary. Row widths should quantify the gap; they must not replace the child verdicts.

Suggested PR review comment

Spec 27021 says the child roll-up verdict determines each gate and defines an overall Pass/Investigate/Fail result. This summary contains only row-derived counts and two warning flags, so a child can fail while its gate appears green or absent. Preserve the three child verdicts and compute the required overall status independently of Sankey widths.


Finding 7 — Important coverage gap: no PowerShell tests were added

Relevant code

New execution path: src/powershell/private/tenantinfo/Invoke-ZtTenantInfo.ps1, lines 29–31

if ($Pillar -in ('All', 'Network')) {
    Add-ZtOverviewPrivateAccess
}

Existing test file: code-tests/commands/Invoke-ZtTenantInfo.Tests.ps1

Problem

The PR introduces:

  • a new cross-runspace TestData dictionary;
  • Add-ZtTestData and Get-ZtTestData;
  • a new multi-gate aggregation function;
  • a new Network/All branch in Invoke-ZtTenantInfo; and
  • three child publishers.

No corresponding PowerShell tests were added. The existing Invoke-ZtTenantInfo test invokes only the Devices branch and does not mock or assert Add-ZtOverviewPrivateAccess.

Required tests

At minimum, add tests for:

  1. Normal pass/fail/manual-review partitioning.
  2. App ID joins with matching populations.
  3. Different populations with different counts.
  4. Different populations with equal counts.
  5. Duplicate and case-variant App IDs.
  6. Quick Access rows present only in authentication.
  7. Scoped problematic administrator assignments.
  8. Child pass/fail/investigate propagation.
  9. Skipped, timed-out, errored, and missing children.
  10. Selective execution where only one child runs.
  11. Clearing and retrieving the new shared state.
  12. Invoke-ZtTenantInfo behavior for Network, All, and unrelated pillars.

Suggested PR review comment

Please add coverage for the new Network and All branches. The existing test only invokes the Devices branch, and there are no tests for the new aggregation function, App ID join, missing-child behavior, child verdict propagation, or shared TestData state.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Manoj Kesana (@Manoj-Kesana) Please, address my feedback (in 3 additional comments).

Suggestions

  • Add a direct test for Get-ZtTestResultStatus() against the shared dynamic dictionary. Current aggregation tests mock the helper, so its real dictionary behavior is not covered.
  • Add component coverage verifying that failed, investigate, and unavailable gate verdicts are visible independently of link widths.
  • Regenerate both report templates after correcting the source component.

Previous-report disposition

Previous finding Commit result
Failing scoped administrators shown as Zero Trust Resolved
Authentication-only apps disappear Partially resolved — retained in the population, but their authentication failure can remain visually hidden
Population comparison uses counts Partially resolved — sets are compared, but mismatch does not affect overall status
Missing child classified as manual review Resolved for segmentation/authentication joins
Partial data creates complete-looking overview Partially resolved — degraded state added, but administration has no gray band
Parent lacks child and overall verdicts Partially resolved — fields are produced but not rendered
No PowerShell tests Resolved, with the gaps identified above

Comment thread src/powershell/private/tenantinfo/Add-ZtOverviewPrivateAccess.ps1
Comment thread src/powershell/private/tenantinfo/Add-ZtOverviewPrivateAccess.ps1
Comment thread src/powershell/private/tenantinfo/Add-ZtOverviewPrivateAccess.ps1
@Manoj-Kesana

Copy link
Copy Markdown
Collaborator Author

Copilot resolve the merge conflicts in this pull request

@Manoj-Kesana Manoj Kesana (Manoj-Kesana) removed the ready for review PR is ready for review and merging label Aug 3, 2026
@Manoj-Kesana
Manoj Kesana (Manoj-Kesana) marked this pull request as draft August 3, 2026 17:56
@Manoj-Kesana
Manoj Kesana (Manoj-Kesana) marked this pull request as ready for review August 4, 2026 05:28
@Manoj-Kesana Manoj Kesana (Manoj-Kesana) added the ready for review PR is ready for review and merging label Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@tdetzner
Thomas Detzner (tdetzner) merged commit 43d04c5 into astaykov/preview-report Aug 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for review PR is ready for review and merging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants