A hybrid identity, asset, and access-governance program covering cloud SaaS, on-premises infrastructure, and locally-managed shared accounts — scoped for 100–300 users.
This program exists because access lives in two very different worlds at once. Entra ID, Google Workspace, GitHub, Mimecast, Akamai, and Proofpoint are cloud-managed and mostly SSO-federated — they can be discovered and governed automatically. Active Directory, on-prem firewalls, Portainer, Syspass, and vendor support portals are local-only — nobody's API tells you who has access; a human has to know, tag it, and check back. A workable program has to treat both as first-class, not bolt the second one on as an afterthought.
Every system in scope, tagged by how it's reached today and what risk attribute needs tracking against it. This table is the seed for the "Data Sources" and "Unmatched accounts" views already prototyped — it's the thing that actually needs populating first, before any tool is bought.
| System | Category | Auth model | Discovery method | Risk to track |
|---|---|---|---|---|
| Microsoft 365 / Entra ID | Cloud | Is the IdP | Native API (Graph) | Copilot data processing scope; conditional access gaps |
| Google Workspace | Cloud | SSO or is IdP | Native API / SSO logs | Gemini data processing scope; admin console sprawl |
| GitHub | Cloud | SSO (Enterprise) | Native API + SSO logs | Copilot code-suggestion training/retention policy; PAT sprawl |
| Mimecast | Cloud | SSO (SAML) | Native API | Vendor subprocessor list; data residency region |
| Proofpoint | Cloud | SSO (SAML) | Native API | AI-driven threat scoring — training-data opt-out status |
| Proofpoint support portal | Cloud | Local login | Manual tag only | No SSO — separate credential, easy to orphan on staff change |
| Akamai | Cloud | SSO (varies by product) | Native API where available | AI-driven WAF/bot analytics — vendor data use terms |
| Active Directory (on-prem) | On-prem | Is the on-prem IdP | LDAP / native tooling | Stale privileged groups; local-only admin accounts |
| Portainer | On-prem | Local accounts | Manual tag (+ API if configured) | Shared admin logins to container hosts |
| Local firewalls | On-prem | Local accounts | Manual tag only | Rarely-rotated shared admin credentials |
| Syspass | On-prem | Vault, not SSO'd | Manual register (it is the vault) | Who has vault access; rotation cadence on entries it holds |
| API keys (all platforms) | Cross-cutting | — | Manual register + native API where the platform exposes it | Owner, scope, expiry — frequently un-owned after staff turnover |
| Service accounts (all platforms) | Cross-cutting | — | Manual register + native API where exposed | Same as API keys; often exempted from recert by mistake |
"Manual tag" means: no vendor API will hand you this list — a person enters and periodically reconfirms it. That's a process requirement, not a tooling gap a product purchase fixes on its own.
| ID | Requirement | Priority |
|---|---|---|
| FR-1 | Auto-discover and inventory accounts on every SSO-federated system (Entra, Google, GitHub, Mimecast, Proofpoint, Akamai where supported) via native API, on a scheduled refresh. | Must |
| FR-2 | Maintain a manually-tagged register for non-federated systems (AD local accounts, firewalls, Portainer, Syspass, vendor support portals) — same data model as FR-1, populated by a person instead of an API. | Must |
| FR-3 | Track every API key and service account as a first-class identity record: owner, purpose, scope, expiry/last-rotated date — separate from human user records. | Must |
| FR-4 | Quarterly access recertification: auto-generate a review task per system owner with a due date and escalation path, with no dependency on a human remembering to start it. | Must |
| FR-5 | Scheduled password-rotation reminders for shared/local accounts (Syspass entries, firewall admin logins), on a cadence independent of the SSO-account recert cycle. | Must |
| FR-6 | Per-vendor risk flags: cloud-hosted (Y/N), uses customer data in an AI feature (Y/N), training-data opt-out available (Y/N/Unknown), data residency region. | Must |
| FR-7 | Auto-open a Jira ticket for every access grant, revoke, or failed recertification; post the corresponding Slack notification to the owning team/channel. | Must |
| FR-8 | Offboarding: one action fans out revoke requests — automatic on SSO-federated systems, a generated manual checklist ticket for local/on-prem systems that have no revoke API. | Must |
| FR-9 | Per-user and org-wide relationship visualization plus a searchable/filterable audit table (as already prototyped). | Should |
| FR-10 | Custom tag/metadata fields per asset, so on-prem-only tools can carry SSO-status, business owner, and criticality without a native integration existing. | Must |
| FR-11 | Webhook/API extensibility so a tool outside the initial integration list (Teams, ServiceNow, a custom config-management script) can be added later. | Could |
| FR-12 | Every integration credential the tool itself holds is least-privilege and single-purpose — read-only for discovery pulls, narrowly scoped for the one Jira project/Slack channel it writes to (detailed in Section 12). | Must |
| FR-13 | Every access-change ticket carries a full lifecycle, not just open/closed — it auto-closes on API-confirmed completion, or requires a human to record completion notes before closing when the action was performed manually (Section 12). | Must |
Two different discovery mechanisms feed one register, because they have to: federated systems can be polled, local-only systems can only be told. Everything downstream — recertification, rotation reminders, offboarding, ticketing — runs off that one register so a reviewer never has to know which half of the org a given account came from.
The box worth being honest about is the "Workflow engine" — it is not a product you buy, it's the piece that has to be built or configured (Jira/Slack automation rules, or a small custom service) to actually connect the register's state to a ticket and a notification. Everything to its left has a commercial product that can fill it in; this piece is glue.
Grounded in currently published pricing where available; vendor-quoted tools are marked as estimates. None of this includes the internal engineering time for the workflow engine and on-prem register.
| Option | Pricing model | Est. at 100 users | Est. at 300 users | Notes |
|---|---|---|---|---|
| Nudge Security | $5/user/mo (150–2,500 accounts); $750/mo flat under 150 | ~$9,000/yr | ~$18,000/yr | Only one of the three with published pricing; note the step at 150 accounts |
| Zluri | Quote-based | Est. $6,000–$14,000/yr | Est. $14,000–$28,000/yr | No public rate card — get a written quote before comparing |
| Torii | Quote-based | Est. $5,000–$12,000/yr | Est. $12,000–$24,000/yr | Positioned as the lower-cost option of the three in market comparisons |
| Microsoft Entra ID Governance | $7/user/mo add-on (needs P1/P2), or $12/user/mo Entra Suite bundle | $8,400–$14,400/yr | $25,200–$43,200/yr | Lower end applies only if Entra P1/P2 is already covered by an existing M365 E3/E5 agreement |
| Option | Pricing model | Est. at 100 users | Est. at 300 users | Notes |
|---|---|---|---|---|
| Passbolt Community Edition | Free, self-hosted, unlimited users | $0 licensing | $0 licensing | Same self-hosted model as Syspass — lowest-friction upgrade path; paid Pro tier adds vendor support if wanted |
| Bitwarden Teams | $4/user/mo | $4,800/yr | $14,400/yr | Cloud or self-host |
| Bitwarden Enterprise | $6/user/mo | $7,200/yr | $21,600/yr | Adds SSO + policy controls + self-host |
| Delinea (PAM-tier) | Quote-based, per privileged account | Custom quote | Custom quote | Only justified if you need session recording / just-in-time elevation beyond shared-secret storage |
Jira and Slack are assumed already licensed. Incremental cost is integration/automation effort (webhooks, Jira automation rules, Slack app), not new per-seat licensing.
Ranges are directional, built from currently published rate cards and market-comparison commentary — confirm every "estimate" line with a written vendor quote before budgeting against it.
Questions worth asking any SMP/IGA vendor before signing, specific to this environment's gaps:
Automation keeps an inventory accurate; it does not build one. The first pass has to be a thorough manual pull, done once, before any scheduled sync starts maintaining it.
One row per account (not per person, not per system) — a user with five platform logins is five rows. Ready to use as a spreadsheet or as the on-prem register's data model.
| Field | Example | Required | Notes |
|---|---|---|---|
| Account ID | ac-siem-042 | Yes | Stable internal key, independent of the vendor's own ID |
| System / platform | SIEM | Yes | Matches the register in Section 1 |
| Account holder | J. Reyes | Yes | A named person, never a team alias — for service accounts, the human accountable for it |
| Account type | Human-SSO | Yes | Human-SSO / Human-local / Shared-local / Service account / API key |
| SSO-capable | Y | Yes | Does the platform support SAML/OIDC federation at all |
| SSO-migration status | Done | Yes | Not started / In progress / Done / N/A — local-only by necessity |
| Business justification | SOC L2 duties | Yes | One line — why this account exists |
| Criticality | High | Yes | Drives review/rotation cadence in Section 10 |
| Last used | 2026-09-08 | Where available | Strongest single signal for orphan/service-identity detection |
| Last rotated | 2026-07-01 | Credentials only | Shared/local accounts and API keys |
| Review cadence | Quarterly | Yes | Set from Section 10's tier table |
| Last reviewed | 2026-06-30 | Yes | Set by the recertification workflow |
| Linked ticket | ITACC-4201 | Yes | The Jira ticket that authorized creation — no ticket, no account (Section 9) |
| Active HR match | Y | Human accounts | N/A for service accounts/API keys — see Section 9 for their own test |
| Flag status | Needs review | Yes | Reviewed / Needs review / Orphan candidate |
The single highest-leverage structural move available: every account moved onto SSO stops needing manual tagging and inherits whatever already governs Entra/AD — conditional access, MFA, automatic recertification. Do this per platform, not per account, and close the local account once its SSO replacement is confirmed working. Leaving both live "just in case" is exactly how orphans get created.
| Platform | SAML/OIDC support | Migration action |
|---|---|---|
| Mimecast, Proofpoint, Akamai, GitHub (Enterprise) | Supported | Migrate, then disable the local login entirely — don't leave a fallback account live |
| Google Workspace | Supported | Federate to Entra or run as its own IdP — either way, no standalone local logins should remain |
| Proofpoint support portal | Vendor-controlled, usually not | Confirm with the vendor; if unsupported, treat as a permanent local-only exception (name the individual, don't share the login) |
| Active Directory local admin accounts | N/A — AD is the on-prem IdP | Convert shared local admin use to named individual + PIM-style just-in-time elevation where AD tooling allows it |
| Portainer | Depends on edition/config | Federate via LDAP/OIDC if the deployed edition supports it; otherwise named individual local accounts |
| Local firewalls | Rarely — device-dependent | Use named individual local accounts if the device supports more than one; shared credential only if it genuinely doesn't |
| Syspass | N/A — it is the vault | Stays local by design; access to Syspass itself should be named individual accounts, never shared |
Detection (Section 1's HR join, Section 7's scheduled diff) only ever finds an orphan after it exists. Prevention means closing the creation path so an unowned account can't quietly appear in the first place.
No service account, API key, or new local account gets created outside one path: a Jira request template that forces an owner, a business justification, and a review date before the account is created. Anything discovered later that has no matching ticket is, by definition, an audit finding — this is what "Linked ticket" in the schema enforces.
| Account type | Orphan signal | Data needed |
|---|---|---|
| Human — SSO or local | No matching active HR record | HR roster join (Section 7, step 2) |
| Service account | No active-employee owner, or unused 90+ days | Named owner's HR status + last-used timestamp |
| API key | Past its stated expiry, or unused 90+ days, or owner inactive | Expiry field + last-used timestamp + owner's HR status |
| Shared/local credential | Past its rotation-due date with no logged use since | Last-rotated + last-used from the vault/device |
| Source type | Pull method | Suggested frequency |
|---|---|---|
| SSO-federated cloud (Entra, Google, GitHub, Mimecast, Proofpoint, Akamai) | Native read-only API | Daily |
| Local-only (AD local accounts, firewalls, Portainer) | Scheduled export/script, no live API | Weekly, or aligned to change-control windows |
| Syspass / credential vault | Vault's own audit log export | Weekly — feeds the rotation-due check, not a discovery feed |
Every pull's job is to surface a diff for a human to act on — new account, removed account, changed role — never to silently reconcile itself.
| Account tier | Review cadence | Credential rotation | Between-review control |
|---|---|---|---|
| Privileged / local / VPN-gated only (firewalls, Portainer, AD local admin) | Quarterly | Quarterly — same cycle as the review | Continuous use-logging; VPN access itself MFA-enforced |
| Shared credentials in Syspass | Quarterly | Quarterly, or sooner on any role change | Vault access log reviewed at the same cadence |
| Service accounts / API keys | Quarterly | Per key policy (90 days is a reasonable default) | Last-used monitoring (Section 9) |
| Standard SSO-governed human accounts | Annual (or on role change) | N/A — SSO-managed, MFA-enforced | Conditional access / sign-in risk monitoring, continuous |
Quarterly is a reasonable, defensible cadence specifically because VPN-gating and local-only exposure lower the account's attack surface relative to anything internet-facing — but the review answers "does this still need to exist," not "was it misused since the last check." That second question needs continuous monitoring regardless of review cadence; don't let the quarterly cycle stand in for it.
Run this same table against any candidate's live demo before shortlisting — no vendor is named as a recommendation here deliberately, since the right column split depends on which slice of your environment (Section 1) each product actually reaches.
| Requirement | SaaS Management Platform (Nudge / Zluri / Torii — pattern) | Microsoft Entra ID Governance |
|---|---|---|
| FR-1 Auto-discover SSO-federated systems | Strong — core function | Strong for Microsoft/Google/GitHub-EMU; partial elsewhere without a SCIM connector |
| FR-2 On-prem / local-only register | Not supported — cloud SaaS only, by category | Not reachable — no SSO/SCIM path |
| FR-3 API keys / service accounts | Strong — named as a core discovery target | Available via separate Workload Identities Premium add-on |
| FR-4 Quarterly recertification | Strong — behavioral nudge-based access reviews | Strong — native Access Reviews |
| FR-5 Shared/local password rotation | Out of scope — not a credential vault | Out of scope — not a credential vault |
| FR-6 Vendor AI-risk flagging | Good — markets specifically around AI/OAuth risk | Limited — not its focus area |
| FR-7 Jira + Slack integration | Slack confirmed native; Jira unconfirmed — verify directly | Buildable via Logic Apps custom extensions, not native |
| FR-8 Offboarding fan-out | Strong for the SaaS half it reaches | Strong via Lifecycle Workflows, for connected apps only |
| NFR Data residency / self-hosted option | Typically SaaS-only, no self-host | Runs within the org's existing Microsoft tenant |
Both columns leave the same two gaps: FR-2 (on-prem register) and FR-5 (credential rotation). No vendor in this category closes either — that part of the architecture (Section 3) stays a build regardless of which vendor wins the SaaS-discovery slice.
The integrations themselves need the same governance discipline as the accounts they're tracking — a read pull that's over-scoped, or a ticket that silently never closes, undermines the whole program.
The workflow engine and register are themselves a new set of service accounts/API keys — subject to every requirement in Sections 7–9, starting with minimal scope.
| Integration | Scope required | Explicitly not granted |
|---|---|---|
| Entra / Google / GitHub / Mimecast / Proofpoint / Akamai reads | Directory / audit-log read-only | No write, no admin role, no mailbox content access beyond what discovery genuinely needs |
| Jira | Create + transition on one specific project/issue type | Not a Jira admin token — can't touch unrelated projects or delete issues |
| Slack | Bot token scoped to specific channels + DM-send | Not a workspace-admin token; can't read unrelated channels |
| Credential vault (Syspass/Passbolt) | Metadata only (entry exists, last-rotated) if exposed via API | Never the secret value itself — the tool references, never retrieves |
A ticket that only ever gets created and never verifiably closed is worse than no ticket — it looks like governance while providing none. Every ticket this program opens follows one of two paths to closure:
| State | Entered when | Closes when |
|---|---|---|
| Requested | Access change or new account submitted | — |
| Approved | Owner/manager sign-off recorded | — |
| Actioned — automatic | Change made on an API-reachable system (SSO-federated) | Auto-closes when the register's next read-only pull confirms the change is live |
| Actioned — manual | Change made by hand on a local/on-prem system (firewall, Portainer, AD local) | Stays open until a human adds completion notes (what changed, on which system, how it was verified) — then closes |
The manual-completion-notes field is a required custom field on this ticket type, not a comment left to discipline — a ticket can't transition to Closed without it. This is what makes "manual settings" auditable instead of just trusted.
These run on their own trigger set, separate from the human recertification reminders in Section 10 — a non-human identity has no one checking a personal inbox for it.
| Trigger | Recipient | Channel |
|---|---|---|
| Expiry approaching (30 / 7 / 1 days out) | Named owner | Direct message |
| Unused 90+ days | Named owner + security channel | DM + channel post, flagged as a deprovision candidate |
| Rotation due | Named owner | Direct message |
| Rotation overdue | Named owner + security channel | Escalating channel post — same pattern as an overdue human recert |
Every box in the Section 3 architecture diagram has a genuine, actively-maintained open-source candidate — this isn't a cost-cutting fallback, one of these (Teleport/JumpServer) is structurally better than the vault-and-rotate pattern for local access, not just cheaper.
| Architecture box | OSS candidate | What it actually changes | Caveat |
|---|---|---|---|
| SaaS discovery & governance | Evolveum midPoint | The most capable open-source IGA — provisioning, access certification, and joiner/mover/leaver lifecycle without per-seat licensing | No continuous shadow-IT/SaaS discovery equivalent to Nudge — strong at governing known systems, not finding unknown ones |
| On-prem register + local/shared access | Teleport (Community Edition) or JumpServer | Structurally removes the shared-credential problem — fronts SSH/RDP/Kubernetes/DB access so each admin authenticates as themselves via cert/SSO, with every session recorded. This satisfies the "named individual, not shared" rule (Section 8) without touching every device by hand | Only helps for systems it can front (SSH/RDP/DB/K8s) — a firewall's own web GUI or Portainer's native login may still need direct local accounts if not proxied |
| Credential vault | Passbolt or Vaultwarden | For the genuine remainder Teleport/JumpServer can't front — true single-slot shared secrets | Same as Section 4 — self-hosted, free, no per-seat cost |
| Unified asset & access register | GLPI | Combines hardware/license/asset tracking with a native ticketing module — closest OSS analog to the hardware+license+access model already prototyped | Its access-governance depth is lighter than a dedicated IGA — likely paired with midPoint rather than replacing it |
| Workflow engine (Jira/Slack glue) | n8n | Native Jira and Slack nodes, self-hosted, free — this is a concrete answer to the "glue you build" callout in Section 3, not a custom service someone maintains by hand | Workflow logic (recert scheduling, escalation rules) still has to be authored — n8n is the engine, not the policy |
Commercial tools still lead on prebuilt connectors, AI-driven access insights, and continuous SaaS/shadow-IT discovery specifically — the gap named in Section 11 doesn't close with open source either. What changes here is everything downstream of discovery: governance, on-prem access brokering, and the workflow glue all have credible, free, self-hosted answers.
The interactive prototype is a UI/interaction proof, not the production system Sections 1–13 spec out — it mocks every data source client-side and persists nothing server-side. This table is an honest accounting of which requirements it actually demonstrates, versus which are out of a front-end mock's reach by design.
| Req. | Requirement (short) | Demonstrated as | Try it | Status |
|---|---|---|---|---|
| FR-1 | Auto-discover SSO-federated systems | Data Sources tab mocks Entra/Google/Workday/Jira/MDM connectors with a live-looking sync clock, 5-min manual cooldown, 30-min auto-refresh | Data Sources tab → watch "Last synced" tick down and refresh | Partial — simulated, no real API calls |
| FR-2 | Manual register for non-federated systems | "+ Add data source" form requires the operator to declare SSO-federated vs. local-only per system — the tool never infers it | Data Sources tab → "+ Add data source" | Partial — records the type; doesn't yet carry a full manual per-account tag workflow |
| FR-3 | API keys/service accounts as first-class identity records | Unmatched & orphaned register lists service accounts and API keys with description, last activity, and flag status; in-map service-account access items carry their own rotation cadence | Data Sources tab, bottom table; or Audit Table → Type filter → "Service account" | Partial — no explicit expiry field on register-level entries yet |
| FR-4 | Quarterly recertification with due date | Every access grant carries a lastReviewed date and an atype-based cadence (annual for SSO, quarterly for shared-local/service); the drawer shows a live overdue/due-soon/OK pill and a "Recertify now" action | Click any access node/row → drawer → "Review & rotation" | Demonstrated |
| FR-5 | Rotation reminders for shared/local credentials | Shared-local and service-account grants carry a separate lastRotated date on a 90-day cadence, with its own due/overdue pill and "Mark rotated" action | Drawer on a Service account or Shared (local) item (e.g. Priya's CI/CD Deploy, Diego's Office Firewall admin) | Demonstrated |
| FR-6 | Per-vendor risk flags (AI use, data residency, etc.) | Entra and Google Workspace connector cards surface a free-text "AI / cloud-app risk" flag (unreviewed OAuth grants, AI add-ons with domain-wide Drive access) | Data Sources tab → Entra ID / Google Workspace cards | Partial — illustrative note, not the structured Y/N/Unknown+region schema FR-6 specifies |
| FR-7 | Auto-open Jira ticket + Slack notification on every change | Every request/approve/close action stamps a ticket number into history; orphaned service accounts/API keys have a "Send test alert" button that simulates the Slack post | Request a role change and approve it; or Data Sources → orphan table → "Send test alert" | Partial — tickets are real per-item state; Slack is a manual demo trigger, not an automatic one |
| FR-8 | Offboarding fan-out (auto SSO / manual checklist local) | "Offboard user" computes and displays a real breakdown of that user's access split into auto-revoke (SSO) vs. needs-a-manual-checklist (shared-local/service), plus hardware to reclaim | ID card → "Offboard user" (any user) | Partial — breakdown is real and data-driven; the action itself is intentionally non-executing, as scoped |
| FR-9 | Per-user + org-wide visualization, searchable/filterable table | Force-directed relationship graph per user; audit table with search plus Category/Status/Type/Department/Manager/Platform/Role filters and a Group-by selector | Relationship Map tab; Audit Table tab | Demonstrated |
| FR-10 | Custom tag/metadata fields per asset | The account-type tag (SSO / Shared-local / Service account) is exactly this kind of operator-set metadata field, shown as a badge everywhere the asset appears | Any access row or graph node — see the type badge | Partial — one fixed tag dimension, not open-ended custom fields |
| FR-11 | Webhook/API extensibility | — | — | Not applicable to a static front-end mock |
| FR-12 | Least-privilege scoping of the tool's own credentials | Every connector card states its actual integration scope (e.g. "Directory read-only", "Create + transition, 1 project") instead of a generic "Connected" | Data Sources tab → any connector card → "Scope (least priv.)" | Demonstrated |
| FR-13 | Full ticket lifecycle incl. required manual completion notes | Requested → Actioned (auto/manual) → Closed stage track; SSO items auto-close on "simulated approval," shared-local/service items require a completion-notes field before the close button enables | Request a change on an SSO item vs. a Shared (local)/Service item and compare the close flow | Demonstrated |
| NFR | Role-based access to the tool itself | "Viewing as: IT Admin / Read-only Auditor" switch gates every mutating action (request, approve, recertify, rotate, offboard, add source) behind a real permission check | Header → role switch → try any action as Auditor | Demonstrated |
"Demonstrated" means the interaction and its data model work end-to-end in the browser session — not that it's wired to a real Entra/Jira/Slack API. Nothing in the prototype persists past a page reload; that's the one gap every row above shares, and it's the entire reason Sections 1–13 exist.
Every control below is scoped to this tool's actual attack surface: it pulls read-only identity/asset data from Entra ID, Google Workspace, and Workday; writes narrowly to one Jira project; sends alerts to Slack; carries its own RBAC layer (Section 2's NFR); and — precisely because it becomes a single index of every access grant in the org — is itself a high-value target. Generic checklist items not tied to one of those five surfaces are deliberately left out.
| Category | Concrete control for this tool |
|---|---|
| A01 Broken Access Control | RBAC (Section 2's NFR, prototyped as the Admin/Auditor switch) must be enforced server-side on every query, not just hidden in the UI — a department-scoped viewer must be blocked at the API layer from pulling another department's register, not merely kept from seeing the nav link. Row-level scoping by department/manager chain is the default; org-wide visibility is a distinct, narrowly-granted role. |
| A02 Cryptographic Failures | Connector tokens (Entra/Google/Workday/Jira/Slack) and the identity register itself are encrypted at rest, not just in transit — the register is the richest single target in the system, not only its credentials. |
| A03 Injection | The searchable audit table (FR-9) is the primary injection surface — every search/filter/group-by parameter must be parameterized server-side, never string-concatenated, since inputs plausibly include user-supplied names, ticket IDs, and free-text asset tags. |
| A04 Insecure Design | The Jira write path (FR-7, FR-12) is architecturally incapable of anything beyond create/transition on one designated project — enforced by the connector credential's own scope, not by application-code convention that a future change could bypass. |
| A05 Security Misconfiguration | No default admin account/password ships with the tool's own RBAC; every connector (Entra/Google/Workday/Jira/Slack) is provisioned at the minimum scope in Section 12's table — e.g. Workday read-only worker data, never HR write access. |
| A06 Vulnerable & Outdated Components | Connector SDKs (Entra/Google/Workday/Jira/Slack clients) go through the same dependency-scanning/patch cadence as any other service with org-wide data access — a compromised library here reaches every employee's access record. |
| A07 Identification & Authentication Failures | Human logins to the tool (IT/security/compliance users) require MFA and short session lifetimes, since a hijacked session exposes the full access register. Connector service credentials use OAuth client-credentials/managed identities, not long-lived static secrets, wherever the provider supports it. |
| A08 Software & Data Integrity Failures | Ticket-provenance data (FR-13's full lifecycle) is only trustworthy if it can't be edited by hand without a trace — every register change is attributable to a sync run or a specific ticket action, never a silent direct edit. |
| A09 Security Logging & Monitoring Failures | Every read of the register and every Jira/Slack write the tool performs is logged with actor, timestamp, and target — this log is close to the tool's core compliance deliverable, so its own integrity matters as much as the primary data. |
| A10 Server-Side Request Forgery | Any admin-entered connector endpoint (a self-hosted Jira base URL, a Workday tenant URL) is validated/allow-listed server-side before the tool makes an outbound call, so a config field can't be used to reach an unintended internal endpoint. |
| Function | Concrete control(s) for this tool |
|---|---|
| Govern | A named owner reviews who has admin rights to the register tool itself — easy to skip precisely because the tool's job is reviewing everyone else's access. |
| Identify | An explicit inventory of what the tool connects to and at what scope (mirroring Section 1's environment register) — this is the tool's own attack-surface map, reviewed on the same cadence as the org's asset inventory. |
| Protect | Least privilege on connector credentials (read-only everywhere except the one narrow Jira write path) and on the tool's internal RBAC — the same controls as A01/A05 above. |
| Detect | The Slack-alert mechanism already planned for API keys/service accounts (Section 12) extends to anomalous access to the tool itself — an admin querying the full org register off-hours, or a spike in export activity. |
| Respond | A documented process for a suspected-compromised connector credential: who revokes it, how fast, and how the tool degrades — read-only/stale-data mode, never fail-open into broader access. |
| Recover | Because this tool is a record of access, not the authorization system granting it, recovery means restoring register/audit history from backup — an outage here should never itself create an access gap, since the tool sits outside the authorization path. |
Relevant NIST SP 800-53 control families (not specific control numbers, which aren't verified here): AC (Access Control) for RBAC and connector scoping; IA (Identification & Authentication) for tool login/MFA and service-account auth; AU (Audit & Accountability) for the A09 logging above; CM (Configuration Management) for connector config and dependency management; SI (System & Information Integrity) for the A08 data-integrity concerns. Pull exact control numbers from the current SP 800-53 Rev. 5 catalog before citing one formally.
This tool would exist to give IT, security, and compliance teams a single source of truth for who has access to what across hardware, licenses, and platforms — and, critically, to tie every grant back to the Jira ticket that authorized it, closing the common gap where access is granted informally and never traced or revoked. It's built for the people who have to answer "why does this person still have access" during an audit or offboarding review — IT admins doing day-to-day provisioning, security/compliance leads running periodic recertification, and external auditors needing evidence — not for end users managing their own accounts. It is explicitly not an IAM/SSO replacement (it reads from Entra/Google/Workday; it authenticates no one), not a PAM/credential vault (it tracks that a credential exists and needs rotation; it never brokers or stores the credential itself), and not a substitute for the SaaS Management Platforms or Entra ID Governance discussed in Section 11 — it's a narrow governance and traceability layer that sits on top of those systems, valuable precisely because it doesn't try to re-implement what they already do.
Could this run against a real organization's data today? Not as-is — the prototype's USERS/SOURCES are in-memory JavaScript arrays with no backend, no auth, and nothing persisted. What is reusable is the interaction design and data model (Sections 7, 9, 14) — the graph, the audit table, the drawer, the ticket lifecycle all assume the same shape of record regardless of where it came from. Making it real means building one thing per source system: a connector that reads that source and normalizes it into that shape, plus the backend, storage, and auth layer the demo has none of (Section 17).
Every connector — whether it talks to a REST API, a SQL database, or LDAP — implements the same four rules, so the register upstream of it never needs to know or care which kind of source it's looking at:
| Source type | Auth / access method | Read pattern | Security control |
|---|---|---|---|
| SaaS REST API (Entra, Google, Workday, Jira) | OAuth2 client-credentials / managed identity | Paged REST polling on a schedule; webhook where the vendor offers one | Token scoped to the read-only directory/audit-log role the vendor's SDK exposes — never an admin/global token |
| SQL database (Postgres, SQL Server, MySQL, Oracle) | Dedicated least-privilege DB role | Incremental SELECT against a read replica, filtered by a watermark column | SELECT-only grant on named views, never base tables — detailed below |
| LDAP / Active Directory | Service bind account, read-only | Scoped LDAP search against specific OUs | Bind account has no write ACL; network-restricted to reach only the domain controller, not the whole segment |
| Flat-file / CSV export (vendor portals with no API) | SFTP pull, or a manual upload the connector diffs against the last import | Scheduled file ingest, or triggered on upload | Transferred over SFTP/TLS, checksum-verified on receipt, encrypted at rest once ingested |
| Ticketing / chat (Jira, Slack) — the one write path | OAuth app token / bot token | Create + transition one issue type; post to named channels | Scoped to exactly one project / a fixed channel list (Section 12) — this is the sole exception to "read-only, always" above, and it's narrowed instead |
This is the source type most likely to be a real internal system (an HR database, a homegrown app-role table) rather than a vendor API — so it gets the most direct scrutiny, since nothing about it is off-the-shelf.
| CREATE ROLE access_register_reader LOGIN PASSWORD '…' NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT; GRANT SELECT ON v_employee_directory, v_app_role_assignments TO access_register_reader; ALTER ROLE access_register_reader SET default_transaction_read_only = on; |
A connector's real job, reduced to one table: turning whatever the source calls things into Section 7's inventory schema.
| Source column (example) | → Register field |
|---|---|
| employees.employee_id | Account holder (joined against HR roster) |
| app_roles.role_name | Business justification / Account type |
| app_roles.granted_at | Last reviewed (seed value only — real value comes from the recert workflow) |
| app_roles.jira_ticket_ref | Linked ticket — absent here is itself the Section 9 creation-time-control finding |
| app_roles.updated_at | The watermark column driving the next incremental pull |
The connectors above are where this program actually touches the outside world — every other component (the frontend, the app layer's own database) is fully within this tool's own perimeter. That makes each collection point worth reviewing individually, as an attacker would: not "is a control listed for this," but "what happens if this specific point is compromised, and does the listed control actually hold." The same unresolved-challenge convention from Section 18 applies here — where a gap is real and not fully closed by anything already designed, it's marked as such rather than talked around.
| Collection point | Attack scenario | Worst-case impact | Mitigation already in the design |
|---|---|---|---|
| SaaS REST API connector (Entra, Google, Workday, Jira) | OAuth client-credentials token is leaked from connector config, a log line, or a compromised connector host, and reused directly against the vendor API from outside the connector | Full read of the source directory/audit log from an unexpected network location — a stolen token doesn't care what fetched it | Token scoped to the read-only role only (nothing to escalate to); short-lived where the vendor supports it; secrets manager issuance (Section 20) rather than static config, so a leaked token has a bounded lifetime |
| SQL / Postgres connector | The access_register_reader credential is exfiltrated (host compromise, credential-manager misconfiguration) and used directly against the database from an attacker-controlled host | Bulk read of everything the granted views expose — bounded by the views' own column scoping, but still a full historical dump if the attacker can iterate the watermark from zero | SELECT-only grant on named views (no base-table access, no write capability to escalate with); network isolation (VPC/PrivateLink/bastion) means the stolen credential alone isn't enough — the attacker also needs a foothold inside that network segment |
| LDAP / AD bind account | Bind credential compromised; used to enumerate the full directory, including OUs and attributes never intended for this tool's scope | Directory reconnaissance well beyond what the register needs — group membership, service accounts, disabled-but-present accounts — useful intelligence for a broader AD attack, not just an access-register leak | Search scope restricted to specific OUs at the LDAP query level, not just "the app only asks for what it needs" — narrowing enforced by the directory side, which holds even if the connector's own logic is bypassed |
| Flat-file / SFTP ingest | A malicious or corrupted file is substituted before the connector diffs it — either at the vendor portal, in transit, or by anyone with write access to the SFTP drop location | Poisoned data enters the register looking like a legitimate sync — someone appears to have access they don't, or a real orphaned account is hidden by a doctored "still active" row | Checksum verification on receipt catches transit corruption but not a validly-formed, maliciously-authored file — this is a real gap, not a solved one; closing it needs either a signed export from the vendor (rare for this file type) or a manual review step before a flat-file diff auto-applies, which conflicts with the "no manual gate on every sync" efficiency goal |
| On-prem agent (Section 18) — firewalls, Portainer, AD, badge systems | The agent host itself is compromised — it is, by definition, the one place multiple local-only source credentials are reachable from a single machine | The highest-value single target in the whole architecture: one compromised host yields read access to every local-only source it was configured against, not just one | Agent is outbound-only (no inbound listener to attack from the network side); each local credential still scoped read-only per source, so compromise doesn't grant write access anywhere — but this doesn't reduce the read blast radius, which is the point above. Unresolved — treat the agent host itself as a Tier-0 asset (patching, EDR, no shared use for anything else), not as an ordinary server, and say so explicitly rather than relying on the credential scoping alone |
| Physical access control connector (Section 19) | Badge-system API credential is compromised and used beyond its intended read scope — this vendor's API is unusual in that reporting and door-control endpoints often share one credential | Not just a data leak — potential unauthorized door control, which is a physical-safety incident, not an information-security one | Section 19 already requires the read boundary be enforced at the vendor's own permission-grant level (a role that structurally cannot call door-control endpoints), not by this tool simply choosing not to call them — the only collection point in this document where the mitigation has to live outside this tool's own code entirely |
| Ticketing / chat write-path (Jira, Slack) — the one non-read-only connector | The app-layer credential that creates Jira issues or posts to Slack is compromised and used to create convincing fake tickets or alert messages | Social-engineering amplification — a fake "access review overdue, approve here" ticket or Slack message, sent from the tool's own legitimate integration identity, is more convincing than a generic phishing attempt | Token scoped to exactly one project / a fixed channel list (already specified above) bounds where a compromised token can post, but does not stop it from posting convincing-but-fake content within that scope — Unresolved — worth a house style convention (e.g. every tool-generated ticket/message includes a non-forgeable reference, like a link back to the register showing the same event) so a recipient has something to cross-check against, rather than trusting the source alone |
Section 5 already lays out the rollout on a calendar; this is the technical build order underneath it — the components that have to exist, roughly in the sequence they have to exist in, before Phase 1 of that rollout can point at anything real.
Section 3's architecture put an "on-prem register" box next to the credential vault, largely because several of its sources are on-prem. That's not the same claim as "the register application itself must run on-prem" — and it deserves to be argued with, not assumed. Below, six stakeholders who'd actually be affected by that decision each get to challenge it on their own terms. Where a challenge has a real answer, it's given; where it doesn't, that's stated plainly rather than talked around.
A badge that still opens a door after someone's left is the exact same governance failure as a SaaS account that's never revoked — it needs an owner, a ticket, a review cadence, and automatic revocation on offboarding, same as everything in Section 7's schema. Most of this program's design (connectors, orphan detection, offboarding fan-out, review cadence) already generalizes to physical access without new concepts — what it needs is its own source-type entry and one hard boundary the other integrations don't have to worry about.
| System | Typical architecture | Integration method | Notes |
|---|---|---|---|
| Gallagher Command Centre | SQL Server backend + Application Server | REST API (preferred) or direct read-only DB per Section 16 | Common in NZ/AU deployments — worth checking first given this program's NZISM context |
| Lenel OnGuard | SQL Server / Oracle backend | OpenAccess API (preferred) or DB read | API licensing is often a separate SKU — confirm it's actually enabled, not just theoretically available |
| HID / Genetec Security Center | SQL Server backend | REST/SDK (preferred) or DB read | SDK access typically requires a separate developer/partner agreement |
| Honeywell Pro-Watch | SQL Server backend | Direct read-only DB — API surface is limited | More likely to need the Section 16 SQL deep-dive pattern than a native API |
| Software House C-CURE 9000 | SQL Server backend + Web Services API | Web Services API (preferred) | — |
| Cloud-native systems (Kisi, Openpath/Avigilon Alta, Brivo) | Vendor-hosted, no on-prem DB | REST API, often with native webhooks | Easiest integration of the row — same OAuth2 pattern as the SaaS connectors in Section 16 |
Physical access becomes a fourth asset category alongside hardware, licenses, and platform access — same schema (Section 7), same account-type tagging (Section 14's SSO/shared-local/service pattern extends naturally: a personal badge is "SSO-equivalent," a shared contractor/visitor badge is "shared-local"):
| Field | Example | Notes |
|---|---|---|
| Door / zone group | Server room, Level 3 office, After-hours | Maps to the badge system's own access-group model, not door-by-door |
| Card reference | card-ref-8841 | An internal reference ID only — never store the raw card/badge number in the register itself |
| Access schedule | Business hours only / 24×7 | An after-hours grant on a low-criticality role is itself an orphan-review signal |
| Last badge-in event | 2026-08-30 07:41 | Strongest signal for both orphan detection and the FR-4 recert workflow — same role last-used plays for API keys in Section 9 |
| Escort-required flag | Y / N | For zones (data centers, comms rooms) where even an authorized badge-holder shouldn't enter alone — a control this register can surface but never enforce itself |
A working sketch of the components, corrected and filled in against everything Sections 1–19 already established, plus a concrete deployment topology and realistic data-volume numbers.
| Component (as sketched) | What it actually is | Correction / addition |
|---|---|---|
| Front end | The browser UI — the graph/table/drawer already prototyped, served as static assets | Correct that it needs TLS and hardening, but RBAC is not enforced here — the UI hides controls per role (as the demo's Admin/Auditor toggle does), while the actual permission check happens server-side (Section 15, A01). Nginx (or equivalent) sits in front for TLS termination, HSTS, security headers (CSP, X-Frame-Options), gzip, and rate limiting — not application logic. |
| "Application or html that renders the data" | Two different things bundled into one line — worth separating | The frontend renders HTML from data it's given. The application/API layer is a distinct backend service: it enforces RBAC server-side, runs the ticket-lifecycle logic (Section 12), receives connector diffs, and is the only thing that talks to the database. Keep it stateless so it can run behind a load balancer as more instances, not one bigger box. |
| Database — access, storing data, history of changes | Correct on all three counts, and this is the right instinct | Two tables per entity, not one: a current-state table (what the register shows) and an append-only history/audit table (every change, who/what/when, per Section 15 A08/A09) — never overwrite history rows. The database is reached only by the application layer; connectors never write to it directly (see below), which is what makes every write attributable to a specific sync run or ticket. |
| Data collection — separate or integrated, unique scripts per integration (hard to manage) vs. easier to maintain | Both instincts are right, and they're not actually in tension | Each connector is necessarily a little bespoke (different auth, different schema) — but all of them implement the one shared contract from Section 16 (fetch-since-watermark, emit-diff, normalize-to-schema). That's what keeps ten bespoke connectors manageable instead of ten unrelated scripts: the bespoke part is isolated to a small adapter per source, and everything downstream of that adapter is identical code. |
| Secrets in a vault (Syspass), rotated manually with tests to confirm | Right instinct, one distinction worth drawing | Syspass (Section 4/8) is well-suited to human-managed shared/local credentials. Machine-to-machine connector credentials are better served by a purpose-built secrets manager (Vault, AWS Secrets Manager, Azure Key Vault) that supports short-lived tokens and API-based retrieval — the connector fetches a credential at runtime, never stores one in its own config. "Rotate manually with a confirming test" is exactly right as a runbook step regardless of which vault: issue the new credential, run a synthetic read-only test call against it, then revoke the old one — never revoke-then-issue, which risks an outage window. |
Worth sizing concretely rather than leaving as a vague scaling worry — the numbers below are for the 100–300 user range this document scopes to (Section 2), including the 2–3× multiplier for non-human identities that section already calls out.
| Data class | Volume at 300 users | Growth driver | Retention |
|---|---|---|---|
| Current-state register (hardware + license + access + physical, Section 7/19) | ~6,000–10,000 rows | New hires, new systems, new grants | Live indefinitely — it is the register; low volume means no cost pressure to prune it |
| Material change history (audit trail) | ~40,000–60,000 events/yr | Recert confirmations, rotations, grant/revoke tickets | Kept live at least 12–24 months (Section 2's NFR), then archived — not deleted, since this is the compliance evidence |
| Connector sync-run metadata (did a pull happen, how long did it take) | ~5,000 rows/yr across all sources | Scheduled pulls (Section 9's cadence table) | Weeks to a couple of months — operational data, not compliance evidence |
| Connector staging extracts | Near-zero steady state | Each cycle's raw pull, used only to compute a diff | Not retained by design — overwritten every cycle; only the resulting diff is written to history |
| Tool access/audit logging (who viewed or exported what, Section 15 A09) | The real volume driver — potentially millions of rows/yr at heavy daily use | Tool usage, not identity data — scales with how often people open it, not with how many identities it tracks | Rolling hot window (12–24 months, searchable), then cold-archived — never kept forever by default given the volume |
The corrective worth stating plainly: the identity/access register itself stays small — tens of megabytes even after years of growth, at this user scale — so it is not a big-data problem and doesn't need special storage tooling. The line item that actually grows is the tool's own access/audit logging, and that's a log-management decision (retention window, hot/cold tiering, structured log storage separate from the relational register tables) rather than a database-sizing problem for the register. "More users" mostly means more viewers generating more log lines, not a bigger register — plan retention and read-replica capacity around that distinction, not around identity-record count.
Reviewed as an auditor would: against NZISM, ISO/IEC 27001:2022 Annex A, and SOC 2 Trust Services Criteria. Every line below is marked by what is actually evidenced in this document and the prototype, not by what a well-run program of this kind would eventually have — this is a design-stage audit of a specification, not an operational audit of a running system, and the finding for almost every unmet control is "not yet built," not "built and failing."
Every prior section describes what the system should do; none specify how its own configuration is controlled, versioned, or kept from drifting once built. That gap is real and is called out here rather than assumed away.
| Control area | Status | Evidence / gap |
|---|---|---|
| Infrastructure-as-code / declarative baseline | Not evidenced | No document in this program specifies that the Nginx config, app-layer environment, or database schema be defined in version-controlled code (Terraform, Ansible, or equivalent) rather than configured by hand. Section 20's deployment diagram names the components; it does not say how their configuration is created or reproduced. |
| Change control for configuration changes | Not evidenced | No approval workflow, peer review, or change-advisory step is specified for altering production configuration (firewall rules on the app host, DB grants, connector schedules). Section 12's ticket lifecycle governs access changes; nothing equivalent governs infrastructure changes. |
| Secure configuration baseline / hardening standard | Partially evidenced | Section 20 specifies TLS termination, security headers, and a stateless app layer at a principle level. No named baseline (a CIS Benchmark for the OS/Nginx/Postgres, or an internal equivalent) is referenced, so "hardened" has no checkable definition yet. |
| Configuration drift detection | Not evidenced | Nothing in Sections 17 or 20 specifies periodic reconciliation between the declared baseline (once one exists) and what's actually running. Without this, a manual out-of-band change (exactly the kind Section 15 A05/A01 worry about for the app's own RBAC) could persist undetected. |
| Secrets rotation vs. config rotation coupling | Evidenced | Section 20 specifies that connector credentials are fetched from a secrets manager at runtime rather than stored in config, and Section 16's Postgres deep-dive gives an explicit issue-then-test-then-revoke rotation runbook. This is the one configuration-adjacent control this document already treats seriously. |
This is the largest single gap this audit finds. No section in this document, including Section 20's database design and Section 20's retention table, specifies how the register's own data is backed up, where those backups live, or how a restore would be tested. A retention policy (how long history is kept) is not a backup capability (whether that history survives a database failure, a ransomware event, or a bad migration) — this document currently has the first without the second.
| Control area | Status | Evidence / gap |
|---|---|---|
| Backup existence and frequency | Not evidenced | No backup schedule is specified for the primary database (current-state tables, audit-history tables) anywhere in this document. Section 20's read replica exists for query offload, not for disaster recovery — a replica that mirrors a corrupted or maliciously altered primary is not a backup. |
| Backup encryption and access control | Not evidenced | Not specified whether backups are encrypted at rest, who can read a restored backup, or whether backup access is logged. Backups are a well-known ransomware target precisely because they're often less monitored than production — this document has not yet addressed that. |
| Offsite / immutable backup copy | Not evidenced | No requirement that at least one backup copy be stored somewhere an attacker who compromises the primary environment (including the app-layer host) cannot also delete — the classic "3-2-1" or immutable-storage pattern is absent. |
| Restore testing | Not evidenced | No cadence is specified for actually restoring a backup to confirm it works. An untested backup is a documented assumption, not a control — this is one of the most commonly cited SOC 2 and ISO 27001 findings in real audits, and this document currently has nothing to show an auditor here. |
| Recovery point / recovery time objectives (RPO/RTO) | Not evidenced | No target is stated for how much data loss is acceptable in a failure (RPO) or how quickly the register must be restored to service (RTO). Without these, "backups exist" has no measurable bar to meet. |
| Secrets manager's own backup | Not evidenced | An overlooked dependency: if the secrets manager (Section 20) that issues connector credentials is itself lost with no recovery path, every connector stops working simultaneously even if the register database is intact. Not addressed anywhere in this document. |
| Backup restore access is segregated from routine admin access | Not evidenced | Not specified whether the person who can trigger a restore is a different role from routine IT-admin users of the tool (Section 11's RBAC) — restore capability is itself a high-privilege action that Section 11's admin/auditor model doesn't currently account for. |
Annex A has 93 controls across four themes (Organizational, People, Physical, Technological). The table below is not exhaustive — it selects the controls most directly relevant to this tool's actual design and attack surface, in the same spirit as Section 15's OWASP mapping, rather than reproducing the full standard.
| Annex A theme / control area | Status | Evidence / gap |
|---|---|---|
| Organizational — policies for information security | Partially evidenced | This document functions as a de facto security requirements policy for the tool itself, but there is no organizational information-security policy document it's shown to derive from or align with. |
| Organizational — access control policy | Evidenced | Sections 4, 8, 11, 15 (A01) collectively specify least-privilege, RBAC, recertification, and offboarding at a policy level in real detail. |
| Organizational — supplier/third-party relationships | Not evidenced | No process is specified for assessing a new SaaS/connector vendor's own security posture (SOC 2 report review, security questionnaire) before onboarding a data source — Section 16 covers the technical connector contract but not vendor due diligence. |
| Organizational — information security incident management | Not evidenced | No incident response plan, escalation path, or breach-notification process is specified anywhere in this document — a material gap given the tool aggregates identity and access data across many systems and would itself be a high-value breach target. |
| Organizational — ICT readiness for business continuity | Partially evidenced | Section 18's local-vs-remote synthesis and Section 20's deployment topology imply availability considerations, but no formal business continuity or disaster recovery plan exists — this is the same gap identified under Backup & DR above, viewed from the continuity-planning angle rather than the technical-mechanism angle. |
| People — screening, security awareness, disciplinary process | Not evidenced | Out of scope for a technical requirements document, but an auditor would still ask, and this document doesn't note that these are organizational (not tool) controls to be evidenced elsewhere. |
| Physical — physical entry, monitoring, equipment security | Evidenced (as a data source, not as this tool's own facility) | Section 19 documents how the tool observes physical access control systems; it correctly does not claim to provide physical security itself, which is the accurate boundary. |
| Technological — access rights, authentication, cryptography | Evidenced | Sections 15 (A02, A07), 16, and 20 specify TLS, least-privilege credentials, and a secrets-manager-based approach in real detail. |
| Technological — configuration management | Not evidenced | Covered above — the newly identified gap in this review. |
| Technological — information backup | Not evidenced | Covered above — the newly identified gap in this review, and the largest one found. |
| Technological — logging and monitoring | Partially evidenced | Section 15 (A09) specifies that access/audit logging must exist; no log-retention/SIEM-correlation/alerting-on-anomaly capability is specified beyond that — logging is described as a data store, not yet as a monitored control. |
| Technological — vulnerability management | Not evidenced | No patching cadence, dependency-scanning, or vulnerability-disclosure process is specified for the app layer, its dependencies, or the connector code. |
| Technological — secure development lifecycle | Partially evidenced | Section 15's OWASP mapping and the existing jsdom regression suite (this session's own test work) show security-relevant testing exists at the prototype level; no code-review, SAST/dependency-scanning, or pre-release security-testing gate is specified for the production build. |
SOC 1 (SSAE 18) reports on internal controls over financial reporting (ICFR) — it is the relevant frame whenever this register governs access to systems that touch financial data (an ERP, a billing platform, a payroll system) rather than general IT access. Framed differently from SOC 2: SOC 2 asks "is the system secure," SOC 1 asks "can a financial auditor rely on this system's access controls when forming an opinion on the financial statements."
| ICFR-relevant control objective | Status | Evidence / gap |
|---|---|---|
| Segregation of duties over financially-relevant access grants | Evidenced | Section 11's admin/auditor RBAC split, combined with Section 12's ticket-approval workflow, gives exactly the separation-of-duties structure a SOC 1 auditor looks for: no single person requests and approves their own access change. |
| Completeness of the access population under review | Partially evidenced | Section 9's cadence table and Section 16's connector contract together aim at completeness, but no control confirms a financially-relevant system was not silently missed from the source-of-truth list — a SOC 1 auditor would sample specifically for this. |
| Timeliness of access removal (a classic SOC 1 test-of-detail) | Evidenced | Section 2's offboarding SLA and Section 12's auto-revoke/manual-checklist breakdown are precisely the kind of dated, auditable evidence a SOC 1 walkthrough samples against. |
| Change management over the register's own logic (a SOC 1 ICFR concern when the register itself feeds a financial control) | Not evidenced | Same configuration-management gap identified in Section 21/23 — if this tool's own ticket-closing or recertification logic changed without controlled review, that would itself be an ICFR-relevant deficiency, and no change-control process exists yet to prevent or detect it. |
| Evidence retention for the audit period under test | Evidenced | Section 2's 12+ month rolling retention NFR matches typical SOC 1 audit-period evidence requirements — contingent on the Backup & DR gap (Section 21) actually being closed, since retained-but-unrecoverable evidence doesn't help an auditor either. |
| Trust Services Criteria | Status | Evidence / gap |
|---|---|---|
| Security (the Common Criteria, CC1–CC9) | Partially evidenced | Strongest area of this document — RBAC (CC6), least-privilege connectors (CC6), and the Section 16 threat model (CC7 — system operations/incident detection intent) are all substantively covered. Weakest sub-areas: CC7.4/CC7.5 (incident response and recovery) and CC8.1 (change management) — both trace back to the same config-management and incident-response gaps identified above. |
| Availability | Not evidenced | Directly dependent on the Backup & DR gap above — SOC 2's Availability criterion specifically expects documented backup, recovery, and capacity-planning controls, none of which exist yet in this document. |
| Confidentiality | Evidenced | Section 15's data classification references, Section 16's view-based column redaction (excluding salary/national-ID columns from what a connector even sees), and least-privilege scoping throughout are all concrete and auditable once built. |
| Processing Integrity | Partially evidenced | Section 16's watermark/diff/normalize contract gives real integrity guarantees for data ingestion; no equivalent statement exists for the app layer's own processing (e.g., what happens if a sync partially fails mid-write — is it transactional or can the register be left in a half-updated state). |
| Privacy | Partially evidenced | Section 15 addresses data minimization and retention/deletion at a principle level; no data subject access request (DSAR) or right-to-erasure handling process is specified for personal data the register holds about individual employees. |
| NZISM theme | Status | Evidence / gap |
|---|---|---|
| Governance and risk management | Partially evidenced | This document itself is a governance artifact; no formal risk register or risk-acceptance sign-off process is shown alongside it. |
| Access control | Evidenced | The strongest-covered theme in this entire document — Sections 4, 8, 11, 15, 16 all speak directly to least-privilege and separation of duties. |
| Cryptography | Partially evidenced | TLS is specified throughout (Section 16, 20); no statement exists on encryption-at-rest algorithm choice, key management lifecycle, or key rotation — a materially different (and currently unaddressed) topic from the credential-rotation runbook already covered. |
| Information security monitoring | Partially evidenced | Same gap as ISO 27001's logging/monitoring row above — logging exists as a concept, active monitoring and alerting does not yet. |
| Physical security | Evidenced (as an observed data source) | Section 19, with the same accurate scope boundary noted under ISO 27001 above. |
| Software security / secure development | Partially evidenced | Same gap as ISO 27001's SDLC row — prototype-level testing exists; a production secure-development-lifecycle gate does not yet. |
Collapsing the tables above into one ordered list, an auditor's actual punch list for this program before it could pass a real readiness assessment:
Set against that list, the genuinely strong areas of this document are worth naming too, since an audit that only lists gaps is as misleading as one that only lists strengths: access control, least-privilege data collection, and data confidentiality controls are substantively designed in real, checkable detail (Sections 4, 8, 11, 15, 16, 19), well ahead of where most programs are at this stage of maturity. The gaps above are concentrated almost entirely in operational resilience (backup/DR, config management, incident response) rather than in the access-governance domain this program was actually built to address — which is the expected shape of a gap analysis for a tool that was designed access-first.
Section 16 threat-modeled the data collection points specifically. This extends that same rigor across every trust boundary in the application — the browser, the app/API layer, the database, the admin/auditor role split, and the ticket/alert write-path — using STRIDE (Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege) so no category of risk is skipped by only thinking about the parts already discussed.
| STRIDE category | Boundary | Threat scenario | Control / evidence needed |
|---|---|---|---|
| Spoofing | Browser ↔ Nginx | An attacker presents a forged or stolen session token to appear as a legitimate admin user | SSO-federated session (Section 4) with short-lived tokens and re-authentication on privilege-sensitive actions; session binding to IP/device fingerprint is a should-have, not yet specified — Should add to the build plan |
| Spoofing | App/API layer ↔ Connectors | A rogue process impersonates a legitimate connector and submits a fabricated diff (e.g. "this user's access was revoked" when it wasn't) | Mutual TLS or a signed-request scheme (HMAC over the payload with a per-connector secret) so the app layer can verify which connector actually sent a diff, not just that some caller had a valid token — not yet specified; this is a real gap, not a resolved one |
| Tampering | Nginx ↔ App/API layer | A request is modified in flight between the reverse proxy and the app layer if that hop isn't itself encrypted (common oversight when both run "inside" the same trust zone) | TLS (or at minimum a private, isolated network segment plus integrity-checked framing) required on this internal hop too — Section 20's diagram shows it as a plain arrow; treat it as needing the same protection as the internet-facing hop, not less |
| Tampering | App/API layer ↔ Database | A compromised app-layer process issues an unauthorized UPDATE against the audit-history table, altering the compliance record after the fact | Audit-history table enforced append-only at the database grant level (no UPDATE/DELETE privilege exists for the app's own service account on that table, only INSERT) — this needs to be added explicitly to Section 20's database design, not assumed from "it's an audit table" |
| Repudiation | Admin ↔ Auditor role boundary | An admin approves a risky access grant and later denies having done so, with no way to prove otherwise | Every mutating action logged with actor identity, timestamp, and the specific before/after state (Section 15 A09) — evidenced at a policy level; needs a build-time requirement that this logging cannot itself be disabled or bypassed by the admin role it's watching |
| Repudiation | App/API layer ↔ Jira/Slack | A ticket or alert is sent, and later no record exists inside this tool of what was sent or why, making the write-path unauditable from this tool's own side | Outbound write logged in this tool's own audit trail (not just relying on Jira/Slack's own history) — not yet explicitly specified; add as a requirement alongside Section 16's write-path scoping |
| Information disclosure | Browser ↔ Nginx | Sensitive register data (who has access to what) is cached by an intermediate proxy or browser history after a user views it | Cache-Control: no-store on every authenticated response; this is a concrete, checkable Nginx/app-layer header requirement — see Section 24 |
| Information disclosure | App/API layer ↔ Database | A verbose error message on a failed query leaks schema details, connection strings, or stack traces to an authenticated-but-unauthorized user | Generic error responses to the client; full detail logged server-side only — a standard OWASP A05 control (Section 15), restated here as it applies to this specific boundary |
| Denial of service | Browser ↔ Nginx | A flood of requests (or a single abusive account) exhausts app-layer capacity, denying access to legitimate admins/auditors during an active offboarding event | Rate limiting at Nginx (Section 24 gives a concrete config), plus a documented incident path if rate limiting itself needs tuning under real load — ties to the Section 21 incident-response gap |
| Denial of service | App/API layer ↔ Connectors | A misbehaving or compromised connector floods the app layer's ingestion endpoint with excessive diffs, starving normal sync traffic | Per-connector rate/size limits on the ingestion endpoint, plus alerting when a connector's diff volume deviates sharply from its historical norm (ties to Section 16's "skip and alert" principle for stale reads, extended to abnormally large reads) |
| Elevation of privilege | Admin ↔ Auditor role boundary | An auditor-role user bypasses the UI's disabled controls and calls a mutating API endpoint directly, since UI-level disabling (as in the current prototype) is not a security boundary | Server-side authorization check on every mutating endpoint, independent of what the UI renders — this is exactly the correction Section 20 already makes to the demo's current UI-only enforcement; restated here as the specific STRIDE risk it closes |
| Elevation of privilege | App/API layer ↔ Secrets manager | The app layer's own identity is granted broader secrets-manager access than it needs (e.g. read access to every connector's secret rather than only the ones it's actively syncing), so a single app-layer compromise yields every credential at once | Per-connector secret scoping at the vault-policy level, not a single blanket credential the app layer holds for everything — a should-have refinement to Section 20's secrets-manager design, not yet specified there |
Two passes: first, a direct bad-practices scan of this design against common real-world OWASP findings (independent of the Top 10 category mapping already done in Section 15, since a category mapping can pass on paper while a specific bad practice still slips through); second, an OWASP SAMM (Software Assurance Maturity Model) maturity rating, because Section 15's OWASP Top 10 table answers "are the ten categories addressed" while SAMM answers the different question "how mature is the process that keeps them addressed over time."
Common, specific anti-patterns that a Top 10 category mapping can miss because they're implementation details, not categories. Each is checked against this design directly.
| Bad practice | Status in this design | Detail |
|---|---|---|
| Hardcoded secrets in source or config files | Avoided by design | Section 20 requires every connector credential be fetched from the secrets manager at runtime — nothing in this design stores a credential in a file that would be committed to version control. |
| Disabled or permissive TLS certificate verification (verify=false patterns) | Avoided by design | Section 16 explicitly requires sslmode=verify-full for the Postgres connector; Section 24 extends the same "verify, don't just encrypt" principle to every other TLS-using hop. |
| Wildcard CORS (Access-Control-Allow-Origin: *) on an authenticated API | Gap — not yet specified | No CORS policy exists anywhere in Sections 1–22. This is a specific, checkable bad practice that a general "RBAC is enforced" statement does not cover — closed concretely in Section 24. |
| Weak or unsalted password hashing (MD5/SHA1 for credential storage) | Partially applicable | Most identities are SSO-federated (Section 4), so this tool doesn't store most passwords at all. Where it does — local/shared-service account credentials the tool itself might need to hold — no hashing algorithm is specified yet; closed concretely in Section 24. |
| Verbose stack traces or debug mode left enabled in production | Gap — not yet specified | Not addressed as an explicit build/deploy requirement anywhere in this document; a generic "don't leak internals" principle exists (Section 15 A05) but no requirement that debug/development mode be a build-time-disabled flag, not a runtime toggle someone could leave on. |
| Missing rate limiting on authentication or high-cost endpoints | Partially evidenced | Section 22's DoS analysis calls for rate limiting; Section 24 now gives it a concrete Nginx configuration. Not yet evidenced: rate limiting specifically on login/authentication attempts as distinct from general traffic. |
| Default or vendor-supplied credentials left unchanged | Avoided by design | Section 16 requires a dedicated, purpose-created role for every data source connection — never reusing an existing account, which structurally rules out "left the default admin password" as a failure mode here. |
| Insecure deserialization of connector payloads | Partially evidenced | Section 16's schema-normalization step implies structured, validated parsing, but no explicit statement rules out accepting arbitrary serialized objects (e.g. a Python pickle or Java native serialization payload) from a connector — should be an explicit "structured data formats only (JSON/Protobuf with schema validation), never native object deserialization" requirement. |
| Outdated or unpatched dependencies (frontend libraries, backend frameworks) | Gap — not yet specified | Same gap already identified in Section 21's vulnerability-management finding — restated here as the OWASP A06 (Vulnerable and Outdated Components) angle specifically. |
| Overly broad session/cookie scope (missing Secure/HttpOnly/SameSite flags) | Gap — not yet specified | Not addressed anywhere; closed concretely in Section 24 alongside the CORS and header requirements. |
| Trusting client-supplied role/permission claims without server-side re-verification | Avoided by design | Section 20 and Section 22 both explicitly require RBAC be enforced server-side, independent of anything the UI sends or hides — the specific correction already made to the current prototype's UI-only gating. |
| Unescaped operator input rendered as HTML (stored XSS) | Found and fixed this pass | Not hypothetical — an actual, demonstrated finding in the reference prototype. asset-map-demo.html concatenated the "Add data source" form fields (system name, type, scope) and the request-change role field directly into innerHTML templates with no escaping; entering <img src=x onerror=...> as a system name or requested role would have executed. Fixed by adding an esc() HTML-escaping helper applied at every render site touching operator-entered text (data-source cards, drawer, audit table, change history), verified by two new jsdom regression tests that submit the payload and assert it renders as inert escaped text with no script execution and no parsed <img> element. The production app-layer/API must apply the equivalent server-side output-encoding discipline — this fix protects the demo, not a future backend, which needs its own equivalent control (OWASP A03). |
SAMM rates process maturity on a 0–3 scale per practice (0 = not performed, 1 = ad hoc, 2 = documented and repeatable, 3 = measured and continuously improved). Rated here against what this document currently establishes as process, not against an operating program — consistent with Section 21's stated audit scope.
| SAMM business function | Practice | Current maturity | Basis for rating |
|---|---|---|---|
| Governance | Policy & compliance | 2 — documented | This document itself functions as a detailed security requirements policy; not yet measured against real operation (would need audit evidence to reach level 3). |
| Governance | Strategy & metrics | 1 — ad hoc | No KPIs or metrics program specified for measuring the security posture over time (e.g. mean time to revoke, recert completion rate) beyond the operational cadences in Section 9. |
| Design | Threat assessment | 2 — documented | Sections 16, 18, and 22 are substantive, repeatable threat-modeling exercises, not one-off ad hoc reviews — a real strength of this program. |
| Design | Security requirements | 3 — measured | Every functional requirement in Section 3 is traceable to a security control in Section 15 via Section 14's mapping table — an unusually rigorous level for a design-stage document. |
| Design | Secure architecture | 2 — documented | Section 20's component separation and Section 18's local-vs-remote synthesis are real architectural decisions with stated rationale, not defaults adopted without justification. |
| Implementation | Secure build | 1 — ad hoc | No dependency-scanning, SAST, or reproducible-build requirement specified — same gap as the bad-practices scan above and Section 21's SDLC finding. |
| Implementation | Secure deployment | 1 — ad hoc | Section 20 names the deployment topology but not a deployment process (staged rollout, automated config validation, rollback plan) — config management gap from Section 21 again surfacing here. |
| Implementation | Defect management | 0 — not performed | No vulnerability-triage or defect-tracking process specified specifically for security findings, as distinct from the general Jira ticketing this tool uses for access requests. |
| Verification | Architecture assessment | 2 — documented | This section and Sections 21/22 are themselves the architecture-assessment artifact — the practice exists, though it's been performed once, not on a cadence yet. |
| Verification | Requirements-driven testing | 2 — documented | The jsdom regression suite (built earlier this session) tests functional and RBAC-boundary behavior directly against stated requirements — real, if currently scoped to the prototype rather than the production build. |
| Operations | Incident management | 0 — not performed | Same gap as Section 21 — no incident response process exists yet at any maturity level. |
| Operations | Environment management | 1 — ad hoc | Backup/DR and config management (Section 21, Section 24) are specified in this pass but not yet operated — maturity can't exceed "ad hoc" for a control that hasn't run once yet. |
Concrete configuration, closing the gaps the bad-practices scan (Section 23) and the audit (Section 21) just found. Where Sections 15/16/20 stated a principle ("use TLS," "hash credentials"), this section gives the actual parameters a build team would implement against.
Most identities in this design are SSO-federated (Section 4) — this tool never sees, let alone stores, the password. Hashing applies specifically to the local/shared-service accounts Section 4 already acknowledges exist (shared local logins, service accounts) where this tool or its directory is the credential's actual store.
| Parameter | Requirement |
|---|---|
| Algorithm | Argon2id (preferred) or bcrypt if the platform lacks Argon2 support — never MD5, SHA-1, or unsalted SHA-256/512, which are fast-hash algorithms unsuited to password storage regardless of output length |
| Argon2id parameters | Minimum memory cost 19 MiB, iterations 2, parallelism 1 (OWASP's current baseline recommendation) — tuned upward if server headroom allows, since higher cost directly raises brute-force expense |
| Salt | Unique per credential, generated by the hashing library itself (never a fixed or reused application-wide salt) |
| Pepper | Optional additional secret held in the secrets manager (Section 20), separate from the salt stored alongside the hash — raises the bar if the credential store itself is exfiltrated but the pepper is not |
| What is never hashed with this scheme | API tokens, connector credentials, and session tokens are not passwords — they're generated secrets, stored via the secrets manager (Section 20) and compared using constant-time equality, not password hashing |
| Parameter | Requirement |
|---|---|
| Minimum protocol version | TLS 1.2 floor, TLS 1.3 preferred wherever the client supports it — TLS 1.0/1.1 disabled entirely, closing the "encrypts but doesn't verify enough" gap named in Section 23 |
| Cipher suites | AEAD suites only (TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256 for 1.3; ECDHE with AES-GCM for 1.2) — no CBC-mode or RC4 suites |
| Certificate management | Automated issuance/renewal (ACME/Let's Encrypt for a public-facing deployment, or an internal CA for the private hop to the app layer) — no manually-renewed certificates, which is a common cause of unplanned expiry outages |
| HSTS | Strict-Transport-Security: max-age=31536000; includeSubDomains; preload — forces HTTPS on every future visit, closing the downgrade-attack window |
| OCSP stapling | Enabled at Nginx, so certificate revocation status is checked without every client making a separate round-trip to the CA |
| Internal hop (Nginx → app layer) | TLS or mTLS required here too, per the Section 22 finding — not left in plaintext just because it stays inside a private network segment |
| Parameter | Requirement |
|---|---|
| Allowed origins | An explicit allowlist of the exact frontend origin(s) — never Access-Control-Allow-Origin: * on any endpoint that reads authenticated data, closing the Section 23 finding directly |
| Credentialed requests | If cookies/session credentials are used, Access-Control-Allow-Credentials: true paired with a specific origin (never *, which browsers reject for credentialed requests anyway, but is still worth stating as a rule rather than relying on browser enforcement alone) |
| Allowed methods/headers | Scoped to what the app actually uses (GET, POST, the specific custom headers the frontend sends) — not a blanket * reflection of the request's own headers |
| Preflight caching | Access-Control-Max-Age set to a sensible window (e.g. 600s) to reduce preflight overhead without caching so long that an origin removed from the allowlist stays effectively trusted in cached preflights |
A representative hardened server block — the concrete artifact Section 20's "Nginx: TLS termination, security headers, rate limiting" line was pointing at.
| server { listen 443 ssl http2; server_name register.example.internal; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE+AESGCM:TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256; ssl_prefer_server_ciphers off; ssl_stapling on; ssl_stapling_verify on; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header Content-Security-Policy "default-src 'self'" always; add_header Cache-Control "no-store" always; limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; limit_req zone=api_limit burst=20 nodelay; location /api/ { proxy_pass https://app_layer_upstream; proxy_ssl_verify on; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; } } |
Notes on the choices above: Cache-Control: no-store is set globally, closing the Section 22 information-disclosure finding about cached authenticated data; proxy_ssl_verify on closes the Section 22 finding about the internal Nginx-to-app-layer hop; the rate-limit zone gives the concrete mechanism behind Section 22's DoS mitigation, tuned per expected legitimate traffic (10 requests/sec/IP is a starting point, not a fixed answer — real tuning needs production traffic data this document doesn't have yet).
| Component | Redundancy approach |
|---|---|
| App/API layer | Multiple stateless instances behind a load balancer, across at least two availability zones — statelessness (Section 20) is precisely what makes this cheap; no session affinity required |
| Primary database | Synchronous or near-synchronous standby with automatic failover (not just the read replica from Section 20, which is for query offload — a failover standby is a distinct requirement) |
| Secrets manager | Use the cloud provider's own multi-AZ managed offering (AWS Secrets Manager, Azure Key Vault) rather than a single self-hosted instance — self-hosting this component reintroduces the single-point-of-failure risk it exists to prevent elsewhere |
| On-prem agent (Section 18) | A single active agent per site is acceptable given its narrow, outbound-only role — but the Section 16 "on replica lag or connection failure, skip and alert" principle must extend to "agent unreachable, alert," so an agent outage is detected promptly rather than silently producing stale data |
| Nginx / reverse proxy tier | At least two instances behind a load balancer or DNS failover — a single reverse proxy instance is as much a single point of failure as a single database would be |
| Parameter | Requirement |
|---|---|
| Schedule | Nightly full snapshot plus continuous WAL/transaction-log archiving (for Postgres: pg_basebackup + streaming WAL to object storage) — the continuous stream is what makes a tight RPO possible, not the nightly snapshot alone |
| Encryption | AES-256 at rest, keys managed by the same secrets/KMS layer as everything else in Section 20 — a backup encrypted with a key stored next to the backup itself is not meaningfully encrypted |
| Offsite / immutable copy | Cross-region replication of the backup store, with object-lock/WORM (write-once-read-many) retention on at least the most recent 30 days — specifically so a compromised app-layer or DB-admin credential cannot also delete the backups that would otherwise enable recovery |
| Restore testing | A full restore-to-a-scratch-environment drill at least quarterly, with the result (success/failure, time taken) logged as its own audit record — an untested backup remains a documented assumption, per Section 21, until this runs at least once |
| RPO target | ≤15 minutes, achieved via continuous WAL streaming rather than relying on the nightly snapshot interval |
| RTO target | ≤4 hours to a working register, assuming the automatic-failover standby (above) has not already absorbed the failure — the 4-hour figure applies to a full disaster-recovery restore from the offsite copy, not routine failover |
| Secrets manager backup | Covered by the managed service's own multi-region durability guarantee when using a cloud-native offering (above) — the gap Section 21 found applies specifically to a self-hosted secrets manager, which is one more reason to prefer the managed option |
Section 21 marked several controls "not evidenced" specifically because no documented process exists yet, not because the underlying idea was missing. This section turns each of those into a named, ownable document with a stated purpose, trigger, and evidence output — the starter scaffolding a program owner would fill in and operate, not the SOPs themselves in full.
| SOP / document | Purpose | Trigger | Evidence it produces |
|---|---|---|---|
| Incident Response SOP | Defines detection, escalation, containment, and notification steps for a security event involving this tool or the data it aggregates | An anomaly alert (Section 22), a reported breach, or a failed control identified in a future audit pass | An incident log entry per event: detection time, actions taken, resolution time, root cause — closes the Section 21 incident-management gap |
| Backup & Restore SOP | Operationalizes the Section 24 backup configuration — who triggers a restore, how the quarterly drill is run, how results are recorded | Quarterly (scheduled drill), or an actual recovery need | Drill results log (success/failure, time taken) — the specific evidence Section 21 and Section 24 both call for and that doesn't exist as a document yet |
| Credential Rotation SOP | Operationalizes the issue-then-test-then-revoke runbook already specified in Section 16, extended to every credential class (connector, admin, secrets-manager root) | Scheduled rotation cadence (FR-5) or a suspected compromise | A rotation log: credential class, rotation date, confirming test result, prior credential revocation timestamp |
| Connector Onboarding SOP | Operationalizes Section 16's connector contract plus the Section 21 vendor-risk-assessment gap — the due-diligence and technical steps to bring a new data source online | A new system is proposed as a data source | A per-connector onboarding record: vendor security review outcome, credential scope granted, schema mapping (Section 16), go-live date |
| Access Recertification SOP | Operationalizes the Section 10 review cadence — who reviews what, how a "still needed" vs. "revoke" decision gets made and recorded | Per-item review due date (Section 10's cadence table) | A recertification decision record per access item, per cycle — this is the primary evidence the tool's own core purpose (Section 2) produces |
| Offboarding SOP | Operationalizes Section 12's offboarding breakdown (auto-revoke vs. manual-checklist items) into a step someone actually follows and signs off on | An employee's departure is recorded in HR/Workday | A completed offboarding checklist per departure, with a completion timestamp against the Section 2 SLA |
| Configuration Change Management SOP | Closes the Section 21/23 config-management gap — how an infrastructure or app-config change is proposed, reviewed, applied, and reconciled against the declared baseline | Any change to production configuration (Nginx, DB grants, connector schedules, IaC definitions) | A change record: what changed, who approved it, drift-check result after applying |
| Disaster Recovery Runbook | The step-by-step technical procedure invoked when the Backup & Restore SOP escalates to a full-site or full-database recovery — distinct from that SOP in being a runbook (ordered technical steps) rather than a process description | A declared disaster/major outage | A recovery timeline against the Section 24 RTO target, logged post-incident |
| Vendor / Third-Party Risk Assessment SOP | Closes the Section 21 vendor-risk gap directly — a lightweight, repeatable check (security questionnaire or SOC 2 report review) before any new SaaS connector is onboarded | Same trigger as Connector Onboarding SOP — the two are companion documents, this one feeding that one's "vendor security review outcome" field | A vendor risk assessment record per data source, refreshed on a periodic cadence (e.g. annually) even for existing connectors |