Skip to content

identity_access_review data-gap PDF says admin detail was "switched off" when it was on, and omits the org name #6100

Description

@ToddHebebrand

Found in the 2026-09-17 pre-release sweep (G2, PR #6034 / #5818 W06), on a stack at 026c268d4.

Repro (verified)

  1. /reports → Templates → Identity & Access Review → Create report (defaults: adminDetail: true, dormantDays: 45).
  2. Generate now. POST /reports/:id/generate200 {"status":"completed"} — correct, an empty-data run completes.
  3. Download the PDF (staff path) or GET /api/v1/portal/reports/runs/:id/pdf (server path). Both render the designed identity arm.

The rendered PDF contains:

Administrator sign-in detail — Administrator sign-in detail was switched off for this report, so no individual sign-ins are listed. That is a setting, not a finding.

The report was created with adminDetail: true (select config->>'adminDetail' on the report row = true). The artifact makes a false statement about the reader's own configuration — on exactly the data-gap path this wave exists to keep honest ("unmeasured ≠ zero", "a gap is a gap").

Cause (verified)

apps/api/src/services/identityAccessReport.ts — both early gap returns use emptySummary(), which hardcodes adminSignins: null regardless of cfg.adminDetail:

  • L257 restricted-authority refusal (OD-8 = A)
  • L270 M365_TENANT_SYNC_ENABLED off

packages/shared/src/reportPdf/identityAccessPdf.ts:291 treats adminSignins === null as "switched off". The happy path at L576 (adminSignins: cfg.adminDetail ? adminSigninRows : null) is the only place null really means "off", so the two meanings are conflated.

The renderer already has the correct branch one else if below — adminRows.length === 0 && signins.total == null"No interactive sign-in data was available for this period… This is an absence of measurement, not an absence of activity."

On the restricted-scope path it is worse still: the real reason is a deliberate scope refusal, and the PDF blames a setting.

Second defect, same function

emptySummary also sets orgName: null (L161), and both gap returns happen before the organizations lookup at L285. So the data-gap PDF carries no customer name, while the sibling evidence types (threat_detection_review, endpoint_management_review, vulnerability_management) all print Default Organization in their equivalent gap PDFs. A PII-bearing evidence artifact that does not name the customer it is about is a distribution hazard.

Suggested fix

  • Pass adminDetail into emptySummary and distinguish "off" from "not measured" (e.g. adminSignins: cfg.adminDetail ? [] : null, which routes to the existing honest branch), or give the renderer a coverage-derived signal.
  • Move the org lookup above both early returns so orgName is populated on the gap paths.

Everything else in W06 verified fine: the run completes on empty data, the title block reads "Interactive sign-ins for 2026-08-18 to 2026-09-17", "VPN policy review" is absent, identity/signins/dormant are all null (never 0), the limits section prints, the type has no portal generate button (POST /portal/reports/generate with identity_access_review → 400), and OD-12 gating works in both directions.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions