Skip to content

Profiles and custom rules

Profiles are declarative YAML files that shape what a scan looks at, which backends it prefers, and what redaction settings the schema accepts. They live under catalog/profiles/ and validate against a JSON schema (catalog/profiles/assessment-profile.schema.json) before any network call.

Built-in profiles

Profile Scope
core Default priority packs (identity + endpoint), no proxy.
identity Entra ID controls, privileged access, and identity protection.
full Every declarative area currently modeled by LicenseLens.
email Exchange Online / MDO themed pack.
collaboration SharePoint, OneDrive, and Teams sharing controls.
endpoint Intune compliance, MDE onboarding, and endpoint baselines.
data-protection Purview DLP, labels, retention, and related readiness checks.
secops Sentinel analytics and UEBA-oriented checks.
power-platform Power Platform tenant isolation and environment governance.
power-bi Power BI external sharing and capacity governance.
scuba Broad pack set aligned with SCuBA-oriented coverage.

Authoritative pack/check expansion: Profile reference.

Selecting a profile (--profile collision)

--profile means different things on scan vs doctor

On scan, demo, quickstart, and batch, --profile is a profile id (core, identity, full, …).

On doctor, --profile is probe depth only: basic (default, core Graph) or full (also MDE API + Sentinel). Profiles on doctor use the separate, repeatable flag --assessment-profile.

# Profile on scan
licenselens scan --profile identity -o reports
licenselens scan --live --auth client_secret --profile full -o reports

# Doctor: probe depth vs assessment requirements
licenselens doctor --live --profile full --auth client_secret
licenselens doctor --assessment-profile identity --assessment-profile full

Organization overlay or standalone rules without replacing the built-in id:

licenselens scan --config org-profile.yaml -o reports
licenselens scan --rules rules.yaml -o reports
licenselens scan --profile identity --config org-overlay.yaml --rules rules.yaml -o reports

What a profile declares

  • packs — the themed pack list for the top card and default scope.
  • check_ids — an explicit check list, when a profile is prescriptive.
  • backend_preferences — a preferred backend order (graph, arm, exchange_online, defender, secure_score, manual) plus allow_proxy and allow_manual flags.
  • exclusions — accepted-risk waivers: check_id, owner, reason, expires_on, and an optional kind: break_glass with principal_ids for named emergency-access accounts.
  • custom_rules — post-evaluation assertions over findings (see below).
  • severity_override — per-check severity overrides: a list of {check_id, severity} pairs that reclassify a check's severity before the report is written (see Severity overrides).
  • omissions — free-text omission notes (a list of strings) documenting what a profile deliberately does not assess.
  • annotations — owner/reason annotations: a list of {owner, reason} pairs recording who owns a profile decision and why.
  • redaction — schema fields redact_tenant_ids, redact_user_principals, redact_domains, and a replacement token (see Redaction).

Severity overrides

severity_override lets a profile reclassify a check's severity per tenant or per organization. Each entry names a check_id and the severity to apply (critical, high, medium, low, info). Duplicate check_id entries are rejected at validation time.

severity_override:
  - check_id: id-ca-legacy-auth-block
    severity: critical
  - check_id: id-guest-sharing
    severity: low

Omissions and annotations

omissions records what a profile deliberately does not assess (free-text notes), and annotations records who owns a profile decision and why:

omissions:
  - "Email pack not assessed (no Graph read API for MDO policy config)."
annotations:
  - owner: security-team
    reason: "Legacy auth blocking is the top priority for this tenant."

Report provenance

severity_override, omissions, and annotations all surface in the report's provenance footer payload (build_provenance):

  • severity_overrides — the profile's per-check severity overrides ([{"check_id", "severity"}]), empty when the scan carried none.
  • omissions — the profile's free-text omission notes, empty when none.
  • annotations — the profile's owner/reason annotations ([{"owner", "reason"}]), empty when none.

This makes the report self-describing: a reader can see which checks were reclassified, what was deliberately omitted, and who owns the decisions.

Custom rules

A custom rule asserts a condition over the finding set and is evaluated after the scan. Each rule declares a selector (a finding field path such as finding.status or finding.severity), an operator (gte, in, …), a value, and optionally a collection (count) and references.

custom_rules:
  - id: identity-high-gaps
    title: Identity high severity gaps
    selector: finding.severity
    operator: in
    value:
      - critical
      - high
    references:
      - https://learn.microsoft.com/security/zero-trust/deploy/identity

Supply standalone rules without a full profile via --rules:

licenselens scan --rules rules.yaml -o reports

Exclusions and break-glass accounts

Exclusions let you document an accepted risk instead of pretending a finding is "fixed". A break-glass exclusion records the named emergency-access principals that are meant to skip all-user MFA, so the check can account for them rather than flagging them as a gap.

Redaction

RedactionSettings on the profile schema accepts enabled, redact_tenant_ids, redact_user_principals, and redact_domains plus a replacement token (default [redacted]). The schema default is enabled: true with tenant-id and user-principal redaction on, and every built-in profile ships with those defaults (redact_domains is off except on the email profile).

The resolved profile's redaction block is applied at report-writing time to every output surface — HTML, JSON, Markdown, and the report ZIP's embedded DATA_JS/VIEWMODEL_JS — replacing the tenant id, any UPN-like string, and the tenant's own domains (harvested from the scan result) with the replacement token. Each class is gated by its flag under the master enabled switch, so a profile that keeps redact_domains: false still strips tenant ids and user principal names while leaving third-party URLs and host names intact.

redaction:
  enabled: true
  redact_tenant_ids: true
  redact_user_principals: true
  redact_domains: true   # opt-in on built-in profiles
  replacement: "[redacted]"

--no-redact opts out for a single run; --redact re-enables redaction when a profile turned it off. Redacted artifacts can still carry other sensitive material (evidence samples, non-tenant object ids, third-party host names), so treat JSON/ZIP artifacts as sensitive tenant data regardless.

See Permissions for what each collector needs, and Architecture for how profiles flow through the engine. See CLI reference for the full flag catalog.