Requirements & Architecture Plan

Access Governance Requirements

A hybrid identity, asset, and access-governance program covering cloud SaaS, on-premises infrastructure, and locally-managed shared accounts — scoped for 100–300 users.

Scope 100–300 users · cloud + on-prem Status Draft for review Related ← Back to the Asset & Access Map prototype

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.

Working assumption: no single vendor product covers both worlds well. The plan below is a hybrid stack — a SaaS-discovery layer for what's federated, a manually-maintained register + credential vault for what isn't, and a workflow layer that ties both to Jira and Slack so neither silently goes stale.

01 Environment inventory & risk register

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.

SystemCategoryAuth modelDiscovery methodRisk to track
Microsoft 365 / Entra IDCloudIs the IdPNative API (Graph)Copilot data processing scope; conditional access gaps
Google WorkspaceCloudSSO or is IdPNative API / SSO logsGemini data processing scope; admin console sprawl
GitHubCloudSSO (Enterprise)Native API + SSO logsCopilot code-suggestion training/retention policy; PAT sprawl
MimecastCloudSSO (SAML)Native APIVendor subprocessor list; data residency region
ProofpointCloudSSO (SAML)Native APIAI-driven threat scoring — training-data opt-out status
Proofpoint support portalCloudLocal loginManual tag onlyNo SSO — separate credential, easy to orphan on staff change
AkamaiCloudSSO (varies by product)Native API where availableAI-driven WAF/bot analytics — vendor data use terms
Active Directory (on-prem)On-premIs the on-prem IdPLDAP / native toolingStale privileged groups; local-only admin accounts
PortainerOn-premLocal accountsManual tag (+ API if configured)Shared admin logins to container hosts
Local firewallsOn-premLocal accountsManual tag onlyRarely-rotated shared admin credentials
SyspassOn-premVault, not SSO'dManual register (it is the vault)Who has vault access; rotation cadence on entries it holds
API keys (all platforms)Cross-cuttingManual register + native API where the platform exposes itOwner, scope, expiry — frequently un-owned after staff turnover
Service accounts (all platforms)Cross-cuttingManual register + native API where exposedSame 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.

02 Requirements

Functional

IDRequirementPriority
FR-1Auto-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-2Maintain 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-3Track 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-4Quarterly 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-5Scheduled password-rotation reminders for shared/local accounts (Syspass entries, firewall admin logins), on a cadence independent of the SSO-account recert cycle.Must
FR-6Per-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-7Auto-open a Jira ticket for every access grant, revoke, or failed recertification; post the corresponding Slack notification to the owning team/channel.Must
FR-8Offboarding: 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-9Per-user and org-wide relationship visualization plus a searchable/filterable audit table (as already prototyped).Should
FR-10Custom 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-11Webhook/API extensibility so a tool outside the initial integration list (Teams, ServiceNow, a custom config-management script) can be added later.Could
FR-12Every 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-13Every 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

Non-functional

Integration & review cadence

03 Recommended architecture

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.

SSO-federated Entra · Google · GitHub Mimecast · Proofpoint · Akamai Local-only / on-prem AD accounts · Portainer Firewalls · Syspass · vendor portals SaaS discovery SMP or Entra ID Governance On-prem register + credential vault auto (API) manual tag Unified access & asset register (this tool) Workflow engine Jira Slack opens ticket reminder quarterly recheck
Two discovery paths — automatic for SSO-federated systems, manual for local-only ones — converge on one register; a workflow engine turns that register's state into Jira tickets and Slack reminders, with recertification looping back on a quarterly cycle.

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.

04 Cost model — 100 to 300 users

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.

SaaS discovery & governance layer

OptionPricing modelEst. at 100 usersEst. at 300 usersNotes
Nudge Security$5/user/mo (150–2,500 accounts); $750/mo flat under 150~$9,000/yr~$18,000/yrOnly one of the three with published pricing; note the step at 150 accounts
ZluriQuote-basedEst. $6,000–$14,000/yrEst. $14,000–$28,000/yrNo public rate card — get a written quote before comparing
ToriiQuote-basedEst. $5,000–$12,000/yrEst. $12,000–$24,000/yrPositioned 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/yrLower end applies only if Entra P1/P2 is already covered by an existing M365 E3/E5 agreement

Shared/local credential vault (Syspass successor or complement)

OptionPricing modelEst. at 100 usersEst. at 300 usersNotes
Passbolt Community EditionFree, self-hosted, unlimited users$0 licensing$0 licensingSame 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/yrCloud or self-host
Bitwarden Enterprise$6/user/mo$7,200/yr$21,600/yrAdds SSO + policy controls + self-host
Delinea (PAM-tier)Quote-based, per privileged accountCustom quoteCustom quoteOnly justified if you need session recording / just-in-time elevation beyond shared-secret storage

Ticketing & chat

Jira and Slack are assumed already licensed. Incremental cost is integration/automation effort (webhooks, Jira automation rules, Slack app), not new per-seat licensing.

Indicative total — realistic hybrid stack

100 users / year
$13k – $22k
SMP/Entra Governance + Passbolt or Bitwarden Teams
300 users / year
$32k – $58k
Same stack, scaled seats
Not included
Build effort
Workflow engine, on-prem register, Jira/Slack automation

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.

05 Phased rollout

Phase 1
0–4 wks
Inventory & tagging. Populate the register for every system in Section 1 by hand — including on-prem/local — before any tool is purchased. This is the step that's easy to skip and the one everything else depends on.
Phase 2
1–2 mo
Automate the federated half. Connect an SMP or Entra ID Governance to the SSO-federated systems for automatic discovery and scheduled refresh, replacing the manual entries for that half of the register.
Phase 3
2–3 mo
Wire the workflow layer. Jira ticket creation on every grant/revoke/recert, Slack reminders on due dates, offboarding fan-out (automatic where possible, checklist ticket where not).
Phase 4
Ongoing
Live governance cycle. Quarterly recertification and password-rotation reminders running on schedule; orphaned-account register reviewed as a standing item, not a one-off cleanup.

06 Vendor evaluation checklist

Questions worth asking any SMP/IGA vendor before signing, specific to this environment's gaps:

  1. Can it hold a manually-tagged record for a system it has no API for (Portainer, local firewalls), alongside the auto-discovered ones — in the same data model, not a bolt-on spreadsheet?
  2. Does it expose a vendor-AI-data-processing risk field per connected app, or would that have to be tracked separately?
  3. Is Jira/Slack integration native, or does it require a middleware/webhook layer you'd build and maintain?
  4. Is pricing per human user, per total account (including service accounts/API keys), or per connected app — this changes the real cost materially once non-human identities are counted?
  5. Does quarterly recertification support a different cadence per system/risk tier, or is it one global schedule?
  6. What happens on offboarding for a system with no revoke API — does it generate a checklist, or silently do nothing?
  7. Get the 100-user and 300-user price in writing — the estimates in Section 4 are directional, not quotes.

07 Inventory methodology & schema

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.

  1. Pull the real account list, not the assumed one. For every system in Section 1's register, export the actual account list from its admin console — every login it has, not "who we think has access."
  2. Join every human account against the HR roster. Any active account with no matching active employee is an orphan candidate on day one — this single join finds more orphans than any tool will, and it's a spreadsheet operation, not a purchase.
  3. Classify every remaining account using the schema below, including the non-human ones (service accounts, API keys) — these never join against HR, so they need their own orphan test (Section 9).
  4. Only then turn on scheduled read-only pulls — their job is to diff against this now-trustworthy baseline and surface changes, not to perform discovery from a cold start.

Inventory schema

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.

FieldExampleRequiredNotes
Account IDac-siem-042YesStable internal key, independent of the vendor's own ID
System / platformSIEMYesMatches the register in Section 1
Account holderJ. ReyesYesA named person, never a team alias — for service accounts, the human accountable for it
Account typeHuman-SSOYesHuman-SSO / Human-local / Shared-local / Service account / API key
SSO-capableYYesDoes the platform support SAML/OIDC federation at all
SSO-migration statusDoneYesNot started / In progress / Done / N/A — local-only by necessity
Business justificationSOC L2 dutiesYesOne line — why this account exists
CriticalityHighYesDrives review/rotation cadence in Section 10
Last used2026-09-08Where availableStrongest single signal for orphan/service-identity detection
Last rotated2026-07-01Credentials onlyShared/local accounts and API keys
Review cadenceQuarterlyYesSet from Section 10's tier table
Last reviewed2026-06-30YesSet by the recertification workflow
Linked ticketITACC-4201YesThe Jira ticket that authorized creation — no ticket, no account (Section 9)
Active HR matchYHuman accountsN/A for service accounts/API keys — see Section 9 for their own test
Flag statusNeeds reviewYesReviewed / Needs review / Orphan candidate

08 SSO / local-account consolidation

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.

PlatformSAML/OIDC supportMigration action
Mimecast, Proofpoint, Akamai, GitHub (Enterprise)SupportedMigrate, then disable the local login entirely — don't leave a fallback account live
Google WorkspaceSupportedFederate to Entra or run as its own IdP — either way, no standalone local logins should remain
Proofpoint support portalVendor-controlled, usually notConfirm with the vendor; if unsupported, treat as a permanent local-only exception (name the individual, don't share the login)
Active Directory local admin accountsN/A — AD is the on-prem IdPConvert shared local admin use to named individual + PIM-style just-in-time elevation where AD tooling allows it
PortainerDepends on edition/configFederate via LDAP/OIDC if the deployed edition supports it; otherwise named individual local accounts
Local firewallsRarely — device-dependentUse named individual local accounts if the device supports more than one; shared credential only if it genuinely doesn't
SyspassN/A — it is the vaultStays local by design; access to Syspass itself should be named individual accounts, never shared
The genuine remainder rule: once every SSO-capable platform is migrated, what's left should be small. For what's left, default to a named individual account wherever the platform technically supports more than one login — reserve a true shared/local credential (with the vault + rotation-reminder pattern from Section 2) only for cases like a firewall with a single local admin slot, reachable solely behind VPN. That's a narrow, defensible exception, not the default state.

09 Orphan & service-identity prevention

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.

Creation-time control

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.

Detecting existing orphans, by account type

Account typeOrphan signalData needed
Human — SSO or localNo matching active HR recordHR roster join (Section 7, step 2)
Service accountNo active-employee owner, or unused 90+ daysNamed owner's HR status + last-used timestamp
API keyPast its stated expiry, or unused 90+ days, or owner inactiveExpiry field + last-used timestamp + owner's HR status
Shared/local credentialPast its rotation-due date with no logged use sinceLast-rotated + last-used from the vault/device

Read-only pull cadence, by source type

Source typePull methodSuggested frequency
SSO-federated cloud (Entra, Google, GitHub, Mimecast, Proofpoint, Akamai)Native read-only APIDaily
Local-only (AD local accounts, firewalls, Portainer)Scheduled export/script, no live APIWeekly, or aligned to change-control windows
Syspass / credential vaultVault's own audit log exportWeekly — 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.

10 Review & rotation cadence

Compliance framing: this organization treats the New Zealand Information Security Manual (NZISM) as a mandatory baseline, not optional guidance — periodic account review and privileged-access control are MUST requirements under it. The cadence below is this program's policy interpretation of that requirement, built from NZISM's risk-based review principle (Identity and Access Management; Section 16.4, Privileged Access Management) plus common cross-framework practice. Confirm the exact current control wording with your compliance/security lead before citing a specific clause number in an audit response — the live NZISM site and PDF couldn't be parsed in this session to quote verbatim text.
Account tierReview cadenceCredential rotationBetween-review control
Privileged / local / VPN-gated only (firewalls, Portainer, AD local admin)QuarterlyQuarterly — same cycle as the reviewContinuous use-logging; VPN access itself MFA-enforced
Shared credentials in SyspassQuarterlyQuarterly, or sooner on any role changeVault access log reviewed at the same cadence
Service accounts / API keysQuarterlyPer key policy (90 days is a reasonable default)Last-used monitoring (Section 9)
Standard SSO-governed human accountsAnnual (or on role change)N/A — SSO-managed, MFA-enforcedConditional 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.

11 Vendor fit comparison

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.

RequirementSaaS Management Platform
(Nudge / Zluri / Torii — pattern)
Microsoft Entra ID Governance
FR-1 Auto-discover SSO-federated systemsStrong — core functionStrong for Microsoft/Google/GitHub-EMU; partial elsewhere without a SCIM connector
FR-2 On-prem / local-only registerNot supported — cloud SaaS only, by categoryNot reachable — no SSO/SCIM path
FR-3 API keys / service accountsStrong — named as a core discovery targetAvailable via separate Workload Identities Premium add-on
FR-4 Quarterly recertificationStrong — behavioral nudge-based access reviewsStrong — native Access Reviews
FR-5 Shared/local password rotationOut of scope — not a credential vaultOut of scope — not a credential vault
FR-6 Vendor AI-risk flaggingGood — markets specifically around AI/OAuth riskLimited — not its focus area
FR-7 Jira + Slack integrationSlack confirmed native; Jira unconfirmed — verify directlyBuildable via Logic Apps custom extensions, not native
FR-8 Offboarding fan-outStrong for the SaaS half it reachesStrong via Lifecycle Workflows, for connected apps only
NFR Data residency / self-hosted optionTypically SaaS-only, no self-hostRuns 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.

12 Integration design principles

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.

Least privilege for the tool's own credentials

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.

IntegrationScope requiredExplicitly not granted
Entra / Google / GitHub / Mimecast / Proofpoint / Akamai readsDirectory / audit-log read-onlyNo write, no admin role, no mailbox content access beyond what discovery genuinely needs
JiraCreate + transition on one specific project/issue typeNot a Jira admin token — can't touch unrelated projects or delete issues
SlackBot token scoped to specific channels + DM-sendNot a workspace-admin token; can't read unrelated channels
Credential vault (Syspass/Passbolt)Metadata only (entry exists, last-rotated) if exposed via APINever the secret value itself — the tool references, never retrieves

Jira ticket lifecycle (open and close)

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:

StateEntered whenCloses when
RequestedAccess change or new account submitted
ApprovedOwner/manager sign-off recorded
Actioned — automaticChange made on an API-reachable system (SSO-federated)Auto-closes when the register's next read-only pull confirms the change is live
Actioned — manualChange 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.

API key / service-account Slack alerts

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.

TriggerRecipientChannel
Expiry approaching (30 / 7 / 1 days out)Named ownerDirect message
Unused 90+ daysNamed owner + security channelDM + channel post, flagged as a deprovision candidate
Rotation dueNamed ownerDirect message
Rotation overdueNamed owner + security channelEscalating channel post — same pattern as an overdue human recert

13 Open-source tooling map

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 boxOSS candidateWhat it actually changesCaveat
SaaS discovery & governanceEvolveum midPointThe most capable open-source IGA — provisioning, access certification, and joiner/mover/leaver lifecycle without per-seat licensingNo continuous shadow-IT/SaaS discovery equivalent to Nudge — strong at governing known systems, not finding unknown ones
On-prem register + local/shared accessTeleport (Community Edition) or JumpServerStructurally 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 handOnly 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 vaultPassbolt or VaultwardenFor the genuine remainder Teleport/JumpServer can't front — true single-slot shared secretsSame as Section 4 — self-hosted, free, no per-seat cost
Unified asset & access registerGLPICombines hardware/license/asset tracking with a native ticketing module — closest OSS analog to the hardware+license+access model already prototypedIts access-governance depth is lighter than a dedicated IGA — likely paired with midPoint rather than replacing it
Workflow engine (Jira/Slack glue)n8nNative 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 handWorkflow 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.

"Free" isn't the same as "no cost." Every row above trades license fees for infrastructure and operational overhead — someone patches midPoint/GLPI/Teleport/n8n, backs them up, and owns them when something breaks at 2am. Factor that operational cost into any build-vs-buy comparison against Section 4's vendor pricing, not just the missing subscription line.

14 Demo-to-requirements traceability

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 asTry itStatus
FR-1Auto-discover SSO-federated systemsData Sources tab mocks Entra/Google/Workday/Jira/MDM connectors with a live-looking sync clock, 5-min manual cooldown, 30-min auto-refreshData Sources tab → watch "Last synced" tick down and refreshPartial — simulated, no real API calls
FR-2Manual 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 itData Sources tab → "+ Add data source"Partial — records the type; doesn't yet carry a full manual per-account tag workflow
FR-3API keys/service accounts as first-class identity recordsUnmatched & 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 cadenceData Sources tab, bottom table; or Audit Table → Type filter → "Service account"Partial — no explicit expiry field on register-level entries yet
FR-4Quarterly recertification with due dateEvery 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" actionClick any access node/row → drawer → "Review & rotation"Demonstrated
FR-5Rotation reminders for shared/local credentialsShared-local and service-account grants carry a separate lastRotated date on a 90-day cadence, with its own due/overdue pill and "Mark rotated" actionDrawer on a Service account or Shared (local) item (e.g. Priya's CI/CD Deploy, Diego's Office Firewall admin)Demonstrated
FR-6Per-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 cardsPartial — illustrative note, not the structured Y/N/Unknown+region schema FR-6 specifies
FR-7Auto-open Jira ticket + Slack notification on every changeEvery 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 postRequest 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-8Offboarding 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 reclaimID card → "Offboard user" (any user)Partial — breakdown is real and data-driven; the action itself is intentionally non-executing, as scoped
FR-9Per-user + org-wide visualization, searchable/filterable tableForce-directed relationship graph per user; audit table with search plus Category/Status/Type/Department/Manager/Platform/Role filters and a Group-by selectorRelationship Map tab; Audit Table tabDemonstrated
FR-10Custom tag/metadata fields per assetThe account-type tag (SSO / Shared-local / Service account) is exactly this kind of operator-set metadata field, shown as a badge everywhere the asset appearsAny access row or graph node — see the type badgePartial — one fixed tag dimension, not open-ended custom fields
FR-11Webhook/API extensibilityNot applicable to a static front-end mock
FR-12Least-privilege scoping of the tool's own credentialsEvery 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-13Full ticket lifecycle incl. required manual completion notesRequested → 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 enablesRequest a change on an SSO item vs. a Shared (local)/Service item and compare the close flowDemonstrated
NFRRole-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 checkHeader → role switch → try any action as AuditorDemonstrated

"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.

15 Security requirements — OWASP, NIST, NZISM

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.

Mandatory, not advisory. This program treats the OWASP Top 10 as a MUST-follow gate, on the same footing as the NZISM cadence requirement in Section 10 — not a best-practice suggestion to revisit later. A build that can't demonstrate every row in the table below is not cleared for go-live against real data, full stop. The NZISM principles later in this section carry the same MUST-follow status, with the same standing caveat: exact control citations still need your compliance lead's sign-off before they're quoted to an auditor — "mandatory" is about the bar this program holds itself to, not a claim that this document has independently verified NZISM's literal text.

OWASP Top 10 (2021) mapping

CategoryConcrete control for this tool
A01 Broken Access ControlRBAC (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 FailuresConnector 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 InjectionThe 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 DesignThe 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 MisconfigurationNo 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 ComponentsConnector 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 FailuresHuman 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 FailuresTicket-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 FailuresEvery 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 ForgeryAny 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.

NIST CSF 2.0 alignment

FunctionConcrete control(s) for this tool
GovernA 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.
IdentifyAn 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.
ProtectLeast 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.
DetectThe 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.
RespondA 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.
RecoverBecause 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.

NZISM-aligned access-control principles

Same verification caveat as Section 10: the specific NZISM control IDs and control text below have not been checked against a live copy of the NZISM in this environment — no working fetch of the current site succeeded. The principles named here (least privilege, separation of duties, need-to-know, session/credential management) are widely-recognized concepts NZISM is generally understood to address, framed here in NZISM-aligned MUST-follow language per this program's compliance stance — but treat this as a best-practice framing, not a verified citation, until your compliance lead confirms it against the current published NZISM.

Data, access & management — consolidated requirements

Purpose and scope — if this were built out for real

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.

16 Connecting real data sources

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).

One contract, every source type

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:

By source type

Source typeAuth / access methodRead patternSecurity control
SaaS REST API (Entra, Google, Workday, Jira)OAuth2 client-credentials / managed identityPaged REST polling on a schedule; webhook where the vendor offers oneToken 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 roleIncremental SELECT against a read replica, filtered by a watermark columnSELECT-only grant on named views, never base tables — detailed below
LDAP / Active DirectoryService bind account, read-onlyScoped LDAP search against specific OUsBind 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 importScheduled file ingest, or triggered on uploadTransferred over SFTP/TLS, checksum-verified on receipt, encrypted at rest once ingested
Ticketing / chat (Jira, Slack) — the one write pathOAuth app token / bot tokenCreate + transition one issue type; post to named channelsScoped 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

Deep dive: connecting a Postgres / SQL data source

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.

Schema mapping — example

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_idAccount holder (joined against HR roster)
app_roles.role_nameBusiness justification / Account type
app_roles.granted_atLast reviewed (seed value only — real value comes from the recert workflow)
app_roles.jira_ticket_refLinked ticket — absent here is itself the Section 9 creation-time-control finding
app_roles.updated_atThe watermark column driving the next incremental pull

Threat model: the data collection points, reviewed

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 pointAttack scenarioWorst-case impactMitigation 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 connectorFull read of the source directory/audit log from an unexpected network location — a stolen token doesn't care what fetched itToken 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 connectorThe access_register_reader credential is exfiltrated (host compromise, credential-manager misconfiguration) and used directly against the database from an attacker-controlled hostBulk 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 zeroSELECT-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 accountBind credential compromised; used to enumerate the full directory, including OUs and attributes never intended for this tool's scopeDirectory 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 leakSearch 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 ingestA 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 locationPoisoned 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" rowChecksum 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 systemsThe agent host itself is compromised — it is, by definition, the one place multiple local-only source credentials are reachable from a single machineThe 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 oneAgent 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 credentialNot just a data leak — potential unauthorized door control, which is a physical-safety incident, not an information-security oneSection 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 connectorThe app-layer credential that creates Jira issues or posts to Slack is compromised and used to create convincing fake tickets or alert messagesSocial-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 attemptToken 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
Where this leaves the design. Five of the seven collection points have a mitigation that holds even if the credential itself leaks — least-privilege scoping, network isolation, or a vendor-side permission boundary that the tool's own code can't override. Two do not fully close: a validly-formed poisoned flat-file import, and a compromised write-path credential producing convincing fake output. Both are left open above rather than papered over, because the honest fix for each trades against an efficiency goal this document also cares about (no manual gate on every sync; no added friction on ticket/alert delivery) — that trade is a call for whoever owns this program to make deliberately, not one to make silently by omission.

17 Build plan: prototype to production

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.

  1. Secrets management, before writing a single connector. Stand up Vault/AWS Secrets Manager/Azure Key Vault first — no connector credential is ever hardcoded, even in a dev environment, because "just for now" credentials are exactly how orphaned access starts (Section 9).
  2. Connector framework. One interface — fetch since a watermark, return normalized records, emit a diff — implemented per Section 16's contract. Start with the two highest-value sources (the HR system of record and the primary IdP) before adding the long tail of on-prem/local systems.
  3. Unified data store. Section 7's schema as real tables, not the demo's in-memory arrays — every write attributable to a specific sync run or ticket action (Section 15 A08), never a hand edit with no trail.
  4. Sync orchestrator. A scheduler (n8n, per Section 13, or a cron-based job runner) running each connector on its own cadence from Section 9's pull-cadence table, surfacing each run's diff for review rather than auto-applying every change silently.
  5. Backend API + server-side RBAC. The layer the current prototype doesn't have at all: real user authentication, and every permission check enforced in the API (Section 15 A01) — not hidden in the UI the way the demo's Admin/Auditor toggle currently is.
  6. Ticketing & alerting integration. Wire real Jira ticket creation/transition and real Slack alerts to real events — a diff detected, a recertification due, a rotation overdue — using the least-privilege scopes already specified in Section 12.
  7. Frontend. The existing prototype's graph, audit table, and drawer are largely reusable as-is — swap the hardcoded USERS/SOURCES arrays for real API calls and keep the interaction design; this is the smallest step in the list.
  8. Observability. Logging for every register read and write (Section 15 A09) shipped before go-live, not retrofitted after an incident asks for it.
  9. Testing. Extend the jsdom smoke-test pattern (Section 14) into real integration tests against a test database and mocked connector APIs, plus a contract test per connector so a source-system schema change fails a test run instead of silently breaking a production sync.
  10. Rollout. Follow Section 5's calendar, but its Phase 1 ("Inventory & tagging") now means something concrete: point Connector #1 at a read replica of the HR system and validate its diff against the manual baseline before anything downstream trusts it.
The honest build-vs-buy note: steps 1–4 above — connector framework, secrets management, unified store, orchestrator — are the majority of the real engineering effort, and they're exactly what a SaaS Management Platform or Entra ID Governance sells pre-built (Section 4, Section 11). Building this yourself only pays off if the on-prem/local-account half of the environment (FR-2, the gap no vendor in Section 11 closes) is enough of the actual problem to justify carrying steps 1–4 yourself instead of paying for them.

18 Local vs. remote — a multi-perspective challenge

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.

CISO— owns breach impact, audit findings, cyber-insurance posture
Challenge
You're proposing to build a single index of every access grant in the org and host it inside the same network perimeter that ransomware operators already target first. Doesn't that make the register a bigger prize for an on-prem breach than it would be sitting behind a cloud provider's separately-hardened edge?
Response — Yes, and this is the strongest challenge in this section. A locally-hosted register is a high-value target inside the exact blast radius ransomware actors already reach first (domain controllers, file servers, hypervisors). Housing it there without materially better isolation than the rest of the on-prem estate is a real net-negative versus a cloud-hosted register behind its own edge. See the synthesis below — this challenge is what changes the recommendation.
Challenge
Does self-hosting actually satisfy a real compliance/residency requirement, or is "NZISM says local" being used as an unexamined justification for a decision already made on other grounds?
Response — NZISM's data-residency concerns are about jurisdiction and government-cloud assurance status, not literally "must be a physical box in this building." A cloud region/tenant the organization controls (Section 2's NFR already says exactly this) can satisfy the same residency requirement a local server does. If "local" is the answer, the specific requirement forcing it needs to be named — not assumed.
Challenge — unresolved
If this box is breached, does the incident-response plan assume the register is evidence (read from) or compromised (can no longer be trusted) — and has that been decided before go-live, or only after the first incident forces the question?
Response — No default answer given here; this is a real IR-planning gap that has to be closed by policy before production data goes anywhere near this system, local or remote.
Security Architect— owns network segmentation, patch/vuln management, the actual attack-surface diagram
Challenge
What network segment does this actually live in? If it's on general server VLAN reachable from standard workstation subnets, one phished laptop is a path to the whole access register — that's a worse position than most of the SaaS sources it's reading from.
Response — Correct, and non-negotiable either way: this system needs its own isolated segment, reachable only via a jump host / bastion, regardless of whether it's hosted locally or in the cloud. "We built it ourselves" buys no exemption from segmentation.
Challenge
Are we conflating "the sources are local" with "the register must be local"? The firewalls/Portainer/AD local accounts need network line-of-sight from something — does that something have to be the register itself, or could it be a thin local agent that pushes data out over one outbound-only encrypted channel?
Response — This is the load-bearing distinction the whole section turns on: a small local agent/relay (already implied by Section 16's connector pattern) can sit on-prem purely to reach local-only sources, while the register/application itself runs somewhere with real operational maturity behind it. Conflating the two is exactly the design mistake worth catching here.
Challenge
Who owns emergency patching of the OS/DB/app stack at 2am on a Saturday when a CVE drops, if it's self-hosted? A cloud provider patches its own infrastructure layer continuously; on-prem, that's now this team's on-call burden on top of everything else.
Response — Section 13's "free isn't free" callout already conceded this for open-source tooling generally; it applies with more force here, because this specific box is a standing target. If local hosting is chosen anyway, this on-call burden needs a named owner before go-live, not an assumption that "IT will handle it."
ITSM / IT Operations— owns uptime, change management, day-to-day operability
Challenge
Is there an actual runbook, or does this become "yet another server nobody has time to maintain" — which is precisely the kind of unowned system this tool exists to prevent everywhere else in the org?
Response — Fair, and self-indicting if the answer is "we'll figure it out later." The build plan (Section 17) needs a named operational owner and a runbook as a go-live gate, exactly like Section 9's creation-time control requires a ticket before any account exists — the register itself shouldn't be exempt from the discipline it enforces on everything else.
Challenge
Does the org's change-management process have the maturity to run CAB/change windows for a system this sensitive, on top of everything else already on the change calendar?
Response — If not, that's a capacity problem self-hosting makes worse, not better — a managed/cloud-hosted register shifts the base-infrastructure change burden off this team's plate, leaving only application-level changes (which exist regardless of hosting location) on the local change calendar.
Challenge — unresolved
Does this local box feed the org's existing SIEM/log pipeline, or does its audit trail (Section 15, A09) sit in isolation — unmonitored, and therefore quietly undermining the exact compliance value the tool exists to provide?
Response — No answer assumed here; this has to be confirmed against the org's actual monitoring stack before go-live, not left as a "someone will notice" gap.
IT Support / Desktop— actually operates the offboarding/recert workflows day to day
Challenge
If this is local-network-only with no remote-friendly access, does that block support staff working from a branch office, from home, or covering an after-hours offboarding, from doing routine work?
Response — Legitimate operational cost of "local" that's easy to overlook from a security-only view. Any hosting decision has to include a real remote-access story for support staff — a VPN-gated local box still needs one; a cloud-hosted register with proper auth arguably needs less new infrastructure to get there.
Challenge
If the local server goes down — power outage, failed patch, hardware fault — offboarding and access-review workflows stall. What's the fallback, and who gets paged?
Response — Same answer as the CISO's IR-planning gap above: this needs a defined fallback (manual checklist reverting to Section 1's pre-tool process) and a named on-call path, decided before this becomes the only place offboarding happens.
Challenge
Does hosting this locally quietly turn desktop support into the de-facto server-ops team for a security-owned system — a skills and responsibility mismatch nobody explicitly signed up for?
Response — A real risk of "just put it on a spare server" thinking. Ownership (per the ITSM challenge above) needs to sit with whoever already operates production infrastructure — security/IT ops, not desktop support by default — regardless of hosting location.
Finance— owns the actual cost comparison, not just the license-fee line
Challenge
Is "local" actually cheaper once hardware capex/refresh cycle, power, cooling, on-call time, and DR capacity are counted — or does it just move cost off the software line and onto lines Finance doesn't associate with this project?
Response — Almost certainly the latter unless proven otherwise — this is Section 13's "free isn't free" callout applied to infrastructure instead of open-source licensing. A real TCO comparison (hardware + power + staff time + insurance, against Section 4's SaaS estimates) should be a prerequisite to choosing "local," not an afterthought.
Challenge
Where does liability sit if this fails or is breached? A cloud vendor contract carries SLA and liability terms; a self-hosted failure's cost sits entirely with this org — has that risk been quantified or insured against?
Response — Not by default, and worth stating plainly: self-hosting is also a decision to self-insure this specific risk. That should be an explicit line in whatever board/budget approval this program needs, not an implicit assumption.
Challenge — unresolved
Is security/IT staff time spent operating a local server the best use of a team whose actual differentiated value is elsewhere?
Response — A fair opportunity-cost question with no universal answer — it depends on staffing levels this document doesn't have visibility into. Flagged here so it's asked deliberately rather than defaulted past.
Legal & Compliance— and others: the org's actual regulatory obligations
Challenge
Given this system holds PII-adjacent identity data for the whole org, will a smaller internal security team realistically meet regulatory breach-notification windows with the same time-to-detect/contain a hyperscaler's dedicated security operations achieves?
Response — Reasonable doubt, not a settled "no" — depends entirely on the maturity of this org's own detection/response capability (Section 15, Detect/Respond). This is an argument for whichever hosting choice pairs with the org's actual incident-response maturity, not an automatic point for either side.
Challenge
If audited, can this org demonstrate equivalent controls to a certified cloud provider's inherited SOC 2/ISO 27001 posture on its own, for a self-hosted system — or does self-hosting create new audit burden the cloud alternative would have covered by inheritance?
Response — New burden, not covered by inheritance — self-hosting means proving every control in Section 15 independently, whereas a certified cloud provider lets some of that evidence be inherited from the provider's own attestations. That audit-effort delta should be weighed alongside cost, not treated as a wash.
Synthesis — this challenge changes the recommendation. Section 3's "on-prem register" box conflated two different things: the sources that are genuinely local-only (firewalls, Portainer, AD local accounts — no argument there, they have no other way to be reached), and the register/application itself, which does not need to sit on that same footing. The stronger design, surfaced by the Security Architect's challenge above: a thin, purpose-built local connector/agent reaches the local-only sources and pushes normalized diffs out over one outbound-only encrypted channel, while the register, backend API, and RBAC layer run in a cloud environment the organization controls (satisfying the NFR/NZISM residency concern the CISO's second challenge raised) with a managed provider's patching, DR, and inherited compliance posture behind it. Section 3's diagram and Section 17's build plan should be read with this revision — "on-prem" describes the thin connector layer only, not the register it feeds.

19 Physical / building access control

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.

Common systems and how they're actually reached

SystemTypical architectureIntegration methodNotes
Gallagher Command CentreSQL Server backend + Application ServerREST API (preferred) or direct read-only DB per Section 16Common in NZ/AU deployments — worth checking first given this program's NZISM context
Lenel OnGuardSQL Server / Oracle backendOpenAccess API (preferred) or DB readAPI licensing is often a separate SKU — confirm it's actually enabled, not just theoretically available
HID / Genetec Security CenterSQL Server backendREST/SDK (preferred) or DB readSDK access typically requires a separate developer/partner agreement
Honeywell Pro-WatchSQL Server backendDirect read-only DB — API surface is limitedMore likely to need the Section 16 SQL deep-dive pattern than a native API
Software House C-CURE 9000SQL Server backend + Web Services APIWeb Services API (preferred)
Cloud-native systems (Kisi, Openpath/Avigilon Alta, Brivo)Vendor-hosted, no on-prem DBREST API, often with native webhooksEasiest integration of the row — same OAuth2 pattern as the SaaS connectors in Section 16

The one boundary that doesn't apply anywhere else in this program

Read-only here means something stricter than usual. Every badge system in the table above exposes door-control endpoints — remote unlock, lockdown override — alongside its reporting/cardholder endpoints, because that's the system's actual job. Every other connector in Section 16 risks leaking data if over-scoped; an over-scoped credential on this connector can unlock a door. The integration role/API scope must be explicitly restricted to cardholder and access-event read endpoints only, with door-control capability excluded at the permission-grant level — not just left unused by application code. Verify this against the vendor's actual permission model before connecting anything, since "read-only" is not always a single checkbox in these systems the way it is in a SQL GRANT.

Extending the data model

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"):

FieldExampleNotes
Door / zone groupServer room, Level 3 office, After-hoursMaps to the badge system's own access-group model, not door-by-door
Card referencecard-ref-8841An internal reference ID only — never store the raw card/badge number in the register itself
Access scheduleBusiness hours only / 24×7An after-hours grant on a low-criticality role is itself an orphan-review signal
Last badge-in event2026-08-30 07:41Strongest signal for both orphan detection and the FR-4 recert workflow — same role last-used plays for API keys in Section 9
Escort-required flagY / NFor 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

Where it plugs into what's already here

20 Component architecture & deployment

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.

Correcting and completing the component list

Component (as sketched)What it actually isCorrection / addition
Front endThe browser UI — the graph/table/drawer already prototyped, served as static assetsCorrect 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 separatingThe 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 changesCorrect on all three counts, and this is the right instinctTwo 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 maintainBoth instincts are right, and they're not actually in tensionEach 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 confirmRight instinct, one distinction worth drawingSyspass (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.

Proposed deployment topology

Browser Nginx TLS · headers · rate limit Application / API layer stateless · RBAC enforced here only thing that talks to the DB Jira Slack Secrets manager short-lived creds, fetched at runtime Primary DB current state + audit history Read replica Cloud connectors Entra · Google · Workday · Jira On-prem agent firewalls · Portainer · AD · badges HTTPS fetch creds reads / writes (audited) replicates push diff, stamped with sync-run ID agent fetches its own local-source creds too
Neither connector writes to the database or the frontend directly — every diff lands through the same application-layer endpoint, gets stamped with a sync-run ID, and only the app layer holds the database connection. RBAC is enforced there, not in Nginx or the browser.

Read-only today, write-path deferred

The instinct to start read-only is correct — hold that line deliberately. Everything above supports a system that observes and reports: connectors read, the app layer aggregates and displays, Jira/Slack carry the only outbound writes the tool makes (and those are requests for a human to act on, not the tool acting itself). A future "the tool makes the change directly" capability — auto-revoking access, not just flagging it — is a materially different risk profile: a bug or compromise in a read path leaks data; a bug or compromise in a write path changes who can get into what. If that capability is ever built, it needs its own threat model and its own go/no-go gate, not an incremental extension of the read-only architecture above.

Data volume & retention sizing

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 classVolume at 300 usersGrowth driverRetention
Current-state register (hardware + license + access + physical, Section 7/19)~6,000–10,000 rowsNew hires, new systems, new grantsLive indefinitely — it is the register; low volume means no cost pressure to prune it
Material change history (audit trail)~40,000–60,000 events/yrRecert confirmations, rotations, grant/revoke ticketsKept 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 sourcesScheduled pulls (Section 9's cadence table)Weeks to a couple of months — operational data, not compliance evidence
Connector staging extractsNear-zero steady stateEach cycle's raw pull, used only to compute a diffNot 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 useTool usage, not identity data — scales with how often people open it, not with how many identities it tracksRolling 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.

21 Independent audit — config, backups & standards gap report

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."

Scope and limitation of this audit, stated plainly. This program does not exist as a running system — it is a design document (Sections 1–20) plus a static HTML prototype with no backend, no persistence, and no deployed infrastructure. Nothing below could be evidenced by log review, control testing, or interview, because there is no operating environment yet to test. Every "Evidenced" mark means the design document specifies this control in enough detail to build and later test it; it does not mean the control has been implemented, deployed, or verified working. Treat this section as a pre-build gap analysis, the artifact an auditor would actually want before a first ISO 27001 Stage 1 or SOC 2 Type I readiness assessment — not as a substitute for one. As stated in Section 10 and Section 15, exact NZISM control numbers and ISO 27001 Annex A control identifiers could not be verified against a live, current copy of either standard in this environment; control names and intent below are accurate to the well-established structure of both frameworks, but a compliance lead must confirm exact clause numbers before this document is cited in a real audit.

Configuration management — new, previously absent from this document

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 areaStatusEvidence / gap
Infrastructure-as-code / declarative baselineNot evidencedNo 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 changesNot evidencedNo 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 standardPartially evidencedSection 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 detectionNot evidencedNothing 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 couplingEvidencedSection 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.

Backup & disaster recovery — new, previously absent from this document

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 areaStatusEvidence / gap
Backup existence and frequencyNot evidencedNo 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 controlNot evidencedNot 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 copyNot evidencedNo 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 testingNot evidencedNo 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 evidencedNo 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 backupNot evidencedAn 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 accessNot evidencedNot 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.
Why this matters more than it might first appear. Section 2's own NFR requires audit history be kept 12+ months as compliance evidence, and Section 15 treats data/access history as core to the tool's purpose. None of that retention promise means anything if there is no tested, working backup and restore path underneath it — a promise to retain data for 12 months is only as strong as the weakest recovery mechanism protecting it from an outage or attack in month 3.

ISO/IEC 27001:2022 Annex A — representative control gap table

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 areaStatusEvidence / gap
Organizational — policies for information securityPartially evidencedThis 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 policyEvidencedSections 4, 8, 11, 15 (A01) collectively specify least-privilege, RBAC, recertification, and offboarding at a policy level in real detail.
Organizational — supplier/third-party relationshipsNot evidencedNo 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 managementNot evidencedNo 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 continuityPartially evidencedSection 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 processNot evidencedOut 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 securityEvidenced (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, cryptographyEvidencedSections 15 (A02, A07), 16, and 20 specify TLS, least-privilege credentials, and a secrets-manager-based approach in real detail.
Technological — configuration managementNot evidencedCovered above — the newly identified gap in this review.
Technological — information backupNot evidencedCovered above — the newly identified gap in this review, and the largest one found.
Technological — logging and monitoringPartially evidencedSection 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 managementNot evidencedNo patching cadence, dependency-scanning, or vulnerability-disclosure process is specified for the app layer, its dependencies, or the connector code.
Technological — secure development lifecyclePartially evidencedSection 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 — relevance to financially-relevant access controls

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 objectiveStatusEvidence / gap
Segregation of duties over financially-relevant access grantsEvidencedSection 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 reviewPartially evidencedSection 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)EvidencedSection 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 evidencedSame 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 testEvidencedSection 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.
When SOC 1 applies here, specifically. Not every deployment of this tool needs a SOC 1 opinion — it applies when the register governs access to in-scope financial systems (the ERP, the general ledger, payroll, billing). Where this tool only governs IT/security-relevant access (VPN, endpoint tools, SaaS collaboration apps), SOC 2 is the applicable frame and SOC 1 doesn't apply at all. A program owner should scope which connectors (Section 16) feed financially-relevant systems before deciding whether a SOC 1 opinion is even in scope for this tool.

SOC 2 Trust Services Criteria — gap table

Trust Services CriteriaStatusEvidence / gap
Security (the Common Criteria, CC1–CC9)Partially evidencedStrongest 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.
AvailabilityNot evidencedDirectly 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.
ConfidentialityEvidencedSection 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 IntegrityPartially evidencedSection 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).
PrivacyPartially evidencedSection 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 alignment — themes, not control numbers

Same caveat as Section 10 and Section 15, restated here because this is the section most likely to be quoted out of context. NZISM's exact chapter and control numbering could not be verified against a live, current copy of the standard in this environment. The rows below name NZISM's well-known thematic areas (Governance, Access Control, Cryptography, Information Security Monitoring, Physical Security, Software Security) and assess this document against the intent of each — a compliance lead must map these to the actual current control IDs before any of this is cited as a completed NZISM alignment.
NZISM themeStatusEvidence / gap
Governance and risk managementPartially evidencedThis document itself is a governance artifact; no formal risk register or risk-acceptance sign-off process is shown alongside it.
Access controlEvidencedThe strongest-covered theme in this entire document — Sections 4, 8, 11, 15, 16 all speak directly to least-privilege and separation of duties.
CryptographyPartially evidencedTLS 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 monitoringPartially evidencedSame gap as ISO 27001's logging/monitoring row above — logging exists as a concept, active monitoring and alerting does not yet.
Physical securityEvidenced (as an observed data source)Section 19, with the same accurate scope boundary noted under ISO 27001 above.
Software security / secure developmentPartially evidencedSame gap as ISO 27001's SDLC row — prototype-level testing exists; a production secure-development-lifecycle gate does not yet.

Consolidated finding — what is actually missing, ranked

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:

  1. Backup, restore, and disaster recovery — no design exists yet. The single largest gap across all three frameworks. Needed: backup schedule and encryption, an offsite/immutable copy, a restore-testing cadence, and stated RPO/RTO targets, for both the register database and the secrets manager it depends on.
  2. Configuration management and change control — no design exists yet. Needed: an infrastructure-as-code baseline, a named hardening standard, drift detection, and a change-approval process distinct from Section 12's access-request ticketing.
  3. Incident response and breach notification — no design exists yet. Needed: a documented escalation path and notification process specifically for this tool, given it aggregates identity/access data from many systems and is itself an attractive breach target.
  4. Vulnerability management — no design exists yet. Needed: patching cadence, dependency/SAST scanning, and a disclosure process for the app layer and connector code.
  5. Active security monitoring — logging exists, alerting does not. Needed: correlation/alerting on the access-log data Section 15 already requires be collected, so anomalous use of the tool itself is detected rather than only retrospectively reviewable.
  6. Encryption key management lifecycle — distinct from credential rotation, currently unaddressed. Needed: a stated approach to encryption-at-rest key generation, storage, and rotation, separate from the connector-credential rotation runbook Section 16 already covers well.
  7. Third-party/vendor risk assessment — no design exists yet. Needed: a lightweight due-diligence step (security questionnaire or SOC 2 report review) before onboarding a new SaaS connector, distinct from the technical connector contract Section 16 already specifies.
  8. DSAR / right-to-erasure handling — principle-level only. Needed: a concrete process for handling an individual's request to see or remove their own data from the register, beyond the general retention/deletion statement already in Section 15.

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.

22 Full-application threat model (STRIDE)

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.

Trust boundaries in scope

STRIDE, applied per boundary

STRIDE categoryBoundaryThreat scenarioControl / evidence needed
SpoofingBrowser ↔ NginxAn attacker presents a forged or stolen session token to appear as a legitimate admin userSSO-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
SpoofingApp/API layer ↔ ConnectorsA 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
TamperingNginx ↔ App/API layerA 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
TamperingApp/API layer ↔ DatabaseA compromised app-layer process issues an unauthorized UPDATE against the audit-history table, altering the compliance record after the factAudit-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"
RepudiationAdmin ↔ Auditor role boundaryAn admin approves a risky access grant and later denies having done so, with no way to prove otherwiseEvery 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
RepudiationApp/API layer ↔ Jira/SlackA 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 sideOutbound 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 disclosureBrowser ↔ NginxSensitive register data (who has access to what) is cached by an intermediate proxy or browser history after a user views itCache-Control: no-store on every authenticated response; this is a concrete, checkable Nginx/app-layer header requirement — see Section 24
Information disclosureApp/API layer ↔ DatabaseA verbose error message on a failed query leaks schema details, connection strings, or stack traces to an authenticated-but-unauthorized userGeneric 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 serviceBrowser ↔ NginxA flood of requests (or a single abusive account) exhausts app-layer capacity, denying access to legitimate admins/auditors during an active offboarding eventRate 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 serviceApp/API layer ↔ ConnectorsA misbehaving or compromised connector floods the app layer's ingestion endpoint with excessive diffs, starving normal sync trafficPer-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 privilegeAdmin ↔ Auditor role boundaryAn 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 boundaryServer-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 privilegeApp/API layer ↔ Secrets managerThe 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 oncePer-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
Net new findings from this pass. Three items above were not previously called out anywhere in Sections 1–21 and are worth flagging explicitly: (1) the Nginx-to-app-layer internal hop needs its own encryption/integrity guarantee, not just the internet-facing TLS termination; (2) the audit-history table needs an enforced append-only database grant, not just a naming convention; (3) connector authenticity (proving which connector sent a diff, not just that a valid credential was used) has no design yet and is a real gap alongside the ones already identified in Section 16.

23 OWASP re-verification & SAMM maturity

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."

Bad-practices scan

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 practiceStatus in this designDetail
Hardcoded secrets in source or config filesAvoided by designSection 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 designSection 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 APIGap — not yet specifiedNo 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 applicableMost 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 productionGap — not yet specifiedNot 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 endpointsPartially evidencedSection 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 unchangedAvoided by designSection 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 payloadsPartially evidencedSection 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 specifiedSame 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 specifiedNot addressed anywhere; closed concretely in Section 24 alongside the CORS and header requirements.
Trusting client-supplied role/permission claims without server-side re-verificationAvoided by designSection 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 passNot 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).

OWASP SAMM maturity assessment

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 functionPracticeCurrent maturityBasis for rating
GovernancePolicy & compliance2 — documentedThis document itself functions as a detailed security requirements policy; not yet measured against real operation (would need audit evidence to reach level 3).
GovernanceStrategy & metrics1 — ad hocNo 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.
DesignThreat assessment2 — documentedSections 16, 18, and 22 are substantive, repeatable threat-modeling exercises, not one-off ad hoc reviews — a real strength of this program.
DesignSecurity requirements3 — measuredEvery 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.
DesignSecure architecture2 — documentedSection 20's component separation and Section 18's local-vs-remote synthesis are real architectural decisions with stated rationale, not defaults adopted without justification.
ImplementationSecure build1 — ad hocNo dependency-scanning, SAST, or reproducible-build requirement specified — same gap as the bad-practices scan above and Section 21's SDLC finding.
ImplementationSecure deployment1 — ad hocSection 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.
ImplementationDefect management0 — not performedNo vulnerability-triage or defect-tracking process specified specifically for security findings, as distinct from the general Jira ticketing this tool uses for access requests.
VerificationArchitecture assessment2 — documentedThis 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.
VerificationRequirements-driven testing2 — documentedThe 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.
OperationsIncident management0 — not performedSame gap as Section 21 — no incident response process exists yet at any maturity level.
OperationsEnvironment management1 — ad hocBackup/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.
Reading the SAMM table. The pattern is consistent with every other framework applied in this document: Design and Verification — the categories closest to "did we think about this correctly" — score well, because that's the nature of a requirements document. Implementation and Operations — the categories that require an actual running system with actual incident history — necessarily score low, because none of that exists yet. That gap is expected at this stage, not a hidden weakness; treating a 0/1 maturity score here as a failure would misread what a pre-build document can and can't demonstrate.

24 Hardening reference — hashing, TLS, CORS, Nginx, redundancy, backups

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.

Credential hashing

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.

ParameterRequirement
AlgorithmArgon2id (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 parametersMinimum 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
SaltUnique per credential, generated by the hashing library itself (never a fixed or reused application-wide salt)
PepperOptional 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 schemeAPI 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

TLS configuration

ParameterRequirement
Minimum protocol versionTLS 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 suitesAEAD 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 managementAutomated 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
HSTSStrict-Transport-Security: max-age=31536000; includeSubDomains; preload — forces HTTPS on every future visit, closing the downgrade-attack window
OCSP staplingEnabled 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

CORS policy

ParameterRequirement
Allowed originsAn 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 requestsIf 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/headersScoped 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 cachingAccess-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

Nginx reference configuration

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).

Redundancy

ComponentRedundancy approach
App/API layerMultiple 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 databaseSynchronous 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 managerUse 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 tierAt 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

Backups — concrete configuration, closing the Section 21 gap

ParameterRequirement
ScheduleNightly 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
EncryptionAES-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 copyCross-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 testingA 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 backupCovered 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

25 Documentation & SOPs — starter set

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 / documentPurposeTriggerEvidence it produces
Incident Response SOPDefines detection, escalation, containment, and notification steps for a security event involving this tool or the data it aggregatesAn anomaly alert (Section 22), a reported breach, or a failed control identified in a future audit passAn incident log entry per event: detection time, actions taken, resolution time, root cause — closes the Section 21 incident-management gap
Backup & Restore SOPOperationalizes the Section 24 backup configuration — who triggers a restore, how the quarterly drill is run, how results are recordedQuarterly (scheduled drill), or an actual recovery needDrill 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 SOPOperationalizes 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 compromiseA rotation log: credential class, rotation date, confirming test result, prior credential revocation timestamp
Connector Onboarding SOPOperationalizes 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 onlineA new system is proposed as a data sourceA per-connector onboarding record: vendor security review outcome, credential scope granted, schema mapping (Section 16), go-live date
Access Recertification SOPOperationalizes the Section 10 review cadence — who reviews what, how a "still needed" vs. "revoke" decision gets made and recordedPer-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 SOPOperationalizes Section 12's offboarding breakdown (auto-revoke vs. manual-checklist items) into a step someone actually follows and signs off onAn employee's departure is recorded in HR/WorkdayA completed offboarding checklist per departure, with a completion timestamp against the Section 2 SLA
Configuration Change Management SOPCloses the Section 21/23 config-management gap — how an infrastructure or app-config change is proposed, reviewed, applied, and reconciled against the declared baselineAny 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 RunbookThe 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 descriptionA declared disaster/major outageA recovery timeline against the Section 24 RTO target, logged post-incident
Vendor / Third-Party Risk Assessment SOPCloses the Section 21 vendor-risk gap directly — a lightweight, repeatable check (security questionnaire or SOC 2 report review) before any new SaaS connector is onboardedSame trigger as Connector Onboarding SOP — the two are companion documents, this one feeding that one's "vendor security review outcome" fieldA vendor risk assessment record per data source, refreshed on a periodic cadence (e.g. annually) even for existing connectors
How to use this list. These eight documents are the direct, named answer to every "not evidenced — no process exists yet" finding raised in Section 21 and Section 23. None of them need to be invented from scratch: each row states the specific trigger and evidence output a real SOP for that purpose would need, which is enough to draft the first version of each and start operating it. Maturity on Section 23's SAMM table moves from "not performed" to at least "ad hoc" the day each SOP is written, and to "documented and repeatable" once it's been run through its trigger at least once with the stated evidence actually produced.