ISPM Cross-Provider Comparison
The eight PasskeyBridge posture modules side by side: credentials, check counts, categories, scoring, and the gaps in each provider's coverage.
Overview
Identity Security Posture Management (ISPM) reads an identity provider's configuration and turns it into findings and a 0-100 score. PasskeyBridge ships eight posture modules: seven that scan a third-party provider and one that scans this platform itself. They all live in the dashboard Posture tab, one provider tab each, and they share a runner, two tables and a set of bands.
Each third-party module runs 25 checks, so scanning all seven is 175 checks. The internal module, shown as PasskeyBridge internal, runs 10 checks against PasskeyBridge's own production state: required secrets, row-level security coverage and policy coverage, the field-level encryption key, scheduled-job heartbeat freshness, audit pipeline activity and failure rate, billing quarantine lag, your tenant's signing key, and how many admins your tenant has. It needs no credentials.
Who can run them. Posture is an Enterprise tab: on the starter and pro plans the dashboard shows an upgrade panel in place of the tab. The plan gate is in the dashboard only. Each function enforces something different: the caller must present a valid dashboard session and be an admin member of the tenant. None of the eight functions is in the Cloudflare worker's PUBLIC_FUNCTIONS list, so none has an https://api.passkeybridge.io/v1 route; the dashboard calls them with supabase.functions.invoke.
Provider comparison matrix
Connecting each provider.
| Provider tab | provider value | Edge function | Fields you store | How the scan authenticates |
|---|---|---|---|---|
| PasskeyBridge internal | internal_self_attest | shield-internal-ispm | none | Reads this platform directly. |
| Okta | okta | shield-okta-ispm | Okta domain, API token | SSWS token on the Okta Admin API. |
| Microsoft Entra ID | entra_id | shield-entra-ispm | Directory ID, client ID, client secret | OAuth 2.0 client credentials for Microsoft Graph. |
| Google Workspace | google_workspace | shield-google-ispm | Workspace domain, admin email, service account email, private key | RS256 service-account JWT with domain-wide delegation. |
| AWS IAM Identity Center | aws_idc | shield-aws-idc-ispm | Region, access key ID, secret access key | SigV4 against the SSO Admin and Identity Store APIs. |
| Ping Identity | ping | shield-ping-ispm | Environment ID, region, client ID, client secret | OAuth 2.0 client credentials for the PingOne Management API. |
| OneLogin | onelogin | shield-onelogin-ispm | Subdomain, region, client ID, client secret | OAuth 2.0 client credentials over HTTP Basic. |
| CyberArk Identity | cyberark | shield-cyberark-ispm | CyberArk URL, client ID, client secret | OAuth 2.0 client credentials at the platform token endpoint. |
In every case only the secret is encrypted with AES-256-GCM. Domains, tenant ids, client ids, regions and the AWS access key ID are stored in clear text on shield_tenants, and column-level SELECT on the secret columns is revoked from the authenticated and anon roles.
Checks and scoring.
| Provider | Checks | Largest categories | Weights | Scoring |
|---|---|---|---|---|
| PasskeyBridge internal | 10 | data_protection 2, observability 2, credentials 2 | 2-5 | 100 minus the sum of the impacts. |
| Okta | 25 | mfa 5, lifecycle 5, authentication 3 | 2-8 | Penalty ratio. |
| Microsoft Entra ID | 25 | mfa 5, conditional_access 3, admin 3 | 2-8 | Penalty ratio. |
| Google Workspace | 25 | lifecycle 6, authentication 5, admin 4 | 2-8 | Penalty ratio. |
| AWS IAM Identity Center | 25 | permission_sets 9, authorization 5, identity_store 4 | 1-10 | Penalty ratio. |
| Ping Identity | 25 | authentication 6, applications 5, identity_store 4 | 1-10 | Penalty ratio. |
| OneLogin | 25 | identity_store 6, applications 6, authentication 4 | 1-10 | Penalty ratio. |
| CyberArk Identity | 25 | authentication 11, lifecycle 4, pam 4 | 2-10 | Pass and half-warning credit. |
"Penalty ratio" is 100 x (1 - total_penalty / max_penalty) over the checks that were not skipped, where total_penalty is the sum of the absolute score_impact values and max_penalty is the sum of weight x 2. CyberArk Identity instead divides the weight of passing checks plus half the weight of warning checks by the total weight of all 25, which means skipped checks pull its score down while they are neutral everywhere else. Scores from CyberArk are therefore comparable across time but harder to line up against the other providers.
Coverage gaps worth knowing.
- AWS IAM Identity Center has no MFA, IAM user, password policy or root account check, and it never calls AWS Organizations.
- Ping Identity has no roles API check. Its admin finding is a heuristic over application names and access control roles.
- Okta counts super admins by reading the membership of the built-in SUPER_ADMIN group, and skips the check if that group is not visible.
- Google Workspace has seven checks whose result is fixed because the Admin SDK does not expose the setting.
- OneLogin stores a Region choice that does not change which host the scan calls.
- CyberArk Identity decides about ten checks by keyword-matching policy names or serialised policy JSON.
The seven provider guides.
Scan mechanics shared by every provider
Every provider module wraps the same runner, so the request lifecycle is identical across all eight.
Actions. run_scan, retry_failed_checks, and one save action per provider such as save_okta_config. Every body carries tenant_id. An unrecognised action answers 400 Unknown action.
const { data, error } = await supabase.functions.invoke("shield-okta-ispm", {
body: { action: "run_scan", tenant_id: "<tenant uuid>" },
});Lifecycle. The runner validates the session JWT with the anon client, parses the body, confirms the caller is an admin member of tenant_id, inserts a scan row with status running, calls the provider's scan handler, then updates that row to completed with the score and counts, inserts one finding per check, writes an audit row, and finally dispatches a posture alert if the tenant configured one and the failed count reached its threshold. If the handler throws, the scan row is updated to failed with the message and the audit row records the failure.
Response.
{
"scan_id": "7c1f0b2e-...",
"parent_scan_id": null,
"posture_score": 68,
"checks_total": 25,
"checks_passed": 14,
"checks_failed": 5,
"checks_warning": 4,
"checks_skipped": 2,
"duration_ms": 8421,
"partial_failures": []
}Retrying. retry_failed_checks takes retry_of_scan_id, re-runs only the checks that failed or warned, and copies the passed and skipped findings onto the new scan. The new scan row records parent_scan_id and retry_of_check_ids, and the tab labels it Retry, N checks. A requested check that does not come back is listed in partial_failures and the audit row is marked warning. A skipped check therefore changes only on a full Run scan.
Bands. The tab labels the score the same way for every provider.
| Score | Label in the tab |
|---|---|
| 80-100 | Strong |
| 60-79 | Moderate |
| 40-59 | Needs work |
| 0-39 | Critical |
No scheduler. No cron job calls any ISPM function. Scans happen when someone presses Run scan or Retry failed checks in the Posture tab.