Skip to main content

Scenario: The Orphaned HR Automation

The Setup — What Was Built and Why

Company: Contoso Corp, 2,000 employees Date: March 2025

Sarah Chen, a Senior IT Automation Engineer, is tasked with eliminating manual work in the employee onboarding process. She builds an Azure Function App called hr-onboarding-bot that:

  1. Watches a shared mailbox for "New Hire Approved" emails from the HR system
  2. Reads the new employee's details (name, department, role, start date)
  3. Creates a ServiceNow ticket to provision their accounts (laptop, email, badge, software)
  4. Assigns the ticket to the correct fulfillment group based on department
  5. Updates the HR system with the ticket number for tracking

The technical setup:

┌──────────────────────────────────────────────────────────────┐
│ Azure Subscription: Contoso-IT-Automation │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Azure Function App: hr-onboarding-bot │ │
│ │ Runtime: Node.js 18 │ │
│ │ Schedule: Runs every 15 minutes │ │
│ │ Created by: sarah.chen@contoso.com │ │
│ └──────────────────────┬──────────────────────────────┘ │
│ │ │
│ ┌──────────────────────┴──────────────────────────────┐ │
│ │ Entra ID Service Principal: sp-hr-onboarding │ │
│ │ App ID: a1b2c3d4-... │ │
│ │ Owner: sarah.chen@contoso.com │ │
│ │ Client Secret: expires Dec 2026 │ │
│ │ │ │
│ │ Permissions: │ │
│ │ - Microsoft Graph: Mail.Read (shared mailbox) │ │
│ │ ServiceNow Roles: │ │
│ │ - sn_incident_write (custom: create incidents) │ │
│ │ - sn_task_assign (custom: create/assign tasks) │ │
│ │ - sn_user_read (custom: look up user profiles) │ │
│ └───────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘

At this point: Everything is working as intended. Sarah owns the automation, the permissions match the job it does, and there's a clear business purpose.


Month 1-3: Working as Designed

The bot processes 40-60 new hire tickets per month. IT leadership is happy — onboarding time drops from 3 days to 4 hours. Sarah monitors the Function App dashboard, handles occasional errors, and rotates the client secret on schedule.

State of the world:

  • Owner: Sarah Chen (active employee, actively monitoring)
  • Permissions: Exactly what's needed (mail read, ticket create, user lookup)
  • Activity: Regular, matching business volume
  • Accountability: Clear — Sarah reviews logs weekly

Month 4: The First Drift — Scope Expands

The IT Service Desk asks Sarah to extend the bot. Now it should also:

  • Close stale onboarding tickets after 30 days
  • Reassign tickets when fulfillment groups change

Sarah updates the ServiceNow roles for the integration user:

  ServiceNow Roles:
- sn_incident_write (custom: create incidents)
- sn_task_assign (custom: create/assign tasks)
- sn_user_read (custom: look up user profiles)
+ - sn_incident_close (custom: close stale incidents)
+ - sn_task_reassign (custom: reassign tasks between groups)

This is normal and approved. The change is documented in a Jira ticket. Sarah's manager signs off.


Month 6: Ownership Decays — Sarah Leaves

July 2025: Sarah accepts a position at another company. Her last day is July 15.

What happens to Sarah's account:

  • July 15: Entra ID account disabled (standard offboarding)
  • August 15: Entra ID account deleted (30-day retention policy)

What happens to the bot: Nothing. The Azure Function App keeps running. The service principal sp-hr-onboarding keeps authenticating. The client secret doesn't care who created it.

┌─────────────────────────────────────────────────────────────┐
│ Service Principal: sp-hr-onboarding │
│ │
│ Owner: sarah.chen@contoso.com ← DELETED FROM ENTRA ID │
│ Status: ACTIVE (still authenticating every 15 minutes) │
│ Last Activity: 2 minutes ago │
│ Client Secret: Valid until Dec 2026 │
│ │
│ Nobody is monitoring this. │
│ Nobody knows this is running. │
│ Nobody can answer "who is responsible for this?" │
└─────────────────────────────────────────────────────────────┘

The problem emerges: The automation continues to execute with the same authority it had when Sarah was present. But there is no human accountable for what it does, no one reviewing its logs, and no one who would notice if it misbehaved.


Month 8: Scope Drift — Roles Expand Without Re-approval

September 2025: A ServiceNow upgrade requires migrating custom roles to standard ones. The ServiceNow admin team replaces the bot's narrow custom roles with broader standard roles. Then a support ticket comes in — the bot is failing to create HR-related onboarding tickets — so another admin grants it hr_agent_workspace. Each change is a 5-minute fix in a ticket queue. Nobody asks "who owns this bot?"

  ServiceNow Roles:
- Microsoft Graph: Mail.Read (shared mailbox)
- - Role: sn_incident_write (custom: create/close incidents in IT queue only)
- - Role: sn_task_assign (custom: create and reassign tasks)
- - Role: sn_user_read (custom: read user profiles for assignment lookup)
+ - Role: itil (standard: full incident, problem, change management)
+ - Role: hr_agent_workspace (standard: HR case read/write, employee records)
+ - Role: personalize (standard: modify user preferences — includes sys_user write)
+ - Role: task_editor (standard: create/modify/close any task type)

Each role addition had a reason. Each was a 5-minute admin action in response to a ticket. None was malicious or even unusual. But nobody asked: "Should this automation have all of these together?" because nobody owns it.

What the bot can now do (but was never intended to):

  • Read and modify HR cases (employee grievances, terminations, salary disputes)
  • Write to user profiles across the org (not just look up names)
  • Manage change requests and problems (not just incidents)
  • Access all task types across all fulfillment groups

Month 10: The Standing Execution Path

November 2025: The bot is now an autonomous identity with:

PropertyOriginal (March)Current (November)
OwnerSarah Chen (active, monitoring)None (account deleted)
Permissions3 narrow custom roles4 standard roles spanning IT, HR, and user management
Blast radiusCreate tickets in IT queueHR cases, user profiles, change requests, all task types
OversightWeekly log reviewNone
Last human decisionMonth 4 (scope extension)6 months ago
Execution frequencyEvery 15 minutesEvery 15 minutes
Client secret expiryDec 2026Dec 2026 (13 more months of autonomous execution)

The execution path:

sp-hr-onboarding (Azure Service Principal)

├── OAuth Client Credentials ──→ ServiceNow Production
│ │
│ ├── Role: itil ──→ incident, problem, change_request tables
│ │ └── data classes: IT operations, security incidents, change records
│ │
│ ├── Role: hr_agent_workspace ──→ hr_case, sn_hr_core_case tables
│ │ └── data classes: employee grievances, terminations, salary data
│ │
│ ├── Role: personalize ──→ sys_user table (write)
│ │ └── data classes: employee profiles, contact info, org structure
│ │
│ └── Role: task_editor ──→ task, sc_task, change_task tables
│ └── data classes: fulfillment tasks, approvals, SLA records

└── Microsoft Graph: Mail.Read ──→ Shared mailbox
└── data classes: new hire PII (SSN, salary, personal details in HR emails)

Why This Is a Problem

It's not a vulnerability. No policy is violated.

  • The service principal is properly configured in Entra ID
  • The OAuth grant is valid in ServiceNow
  • The Azure Function App is running normally
  • All authentication succeeds with valid credentials
  • No security alerts fire
  • No compliance policies are broken

But the risk is real and growing:

  1. No accountability: If this bot starts creating fraudulent incidents, modifying user records, or exfiltrating data through its mail access — who gets the 2 AM phone call? Nobody. The owner doesn't exist.

  2. Excessive authority: The bot holds 4 standard roles spanning HR, IT operations, and user management. It can read HR complaints, modify user profiles, and manage change requests. It was built to create onboarding tickets.

  3. No expiration of intent: Sarah built this for a specific purpose. That purpose is still valid (onboarding still happens), but the scope has expanded far beyond intent. Nobody re-approved the expanded authority because nobody was asked.

  4. Invisible to existing tools:

    • IAM tools see a valid service principal with valid permissions ✓
    • CIEM tools see appropriate cloud role assignments ✓
    • CNAPP tools see no misconfigurations ✓
    • SIEM sees successful authentications ✓
    • Nobody sees: orphaned ownership + scope drift + standing execution path to sensitive data
  5. Attack surface: If the Azure Function App is compromised (dependency vulnerability, stolen credentials, supply chain attack), the attacker inherits broad ServiceNow access across HR, IT, and user management — with no monitoring and no owner to notice unusual behavior.


What SecurityV0 Detects

SecurityV0 connects to both Entra ID and ServiceNow, discovers the execution path, and triggers three deterministic findings:

Finding 1: Orphaned Ownership

TRIGGER: Owner (sarah.chen@contoso.com) status = DELETED in Entra ID
Service principal sp-hr-onboarding still ACTIVE
Last execution: 2 minutes ago

EXPLANATION:
Service principal "sp-hr-onboarding" executes every 15 minutes
using OAuth client credentials to ServiceNow Production.

Its registered owner (sarah.chen@contoso.com) was deleted from
Entra ID on August 15, 2025 — 3 months ago.

No other owner has been assigned.
No human is accountable for this execution authority.

REMEDIATION:
1. Assign a new owner (suggested: Sarah's former manager or IT Automation team lead)
2. New owner reviews current permissions vs. business need
3. New owner accepts accountability for ongoing execution

Finding 2: Scope Drift

TRIGGER: Role expansion detected without re-approval
3 custom roles → 4 standard roles (broader scope)
Changes occurred: September 2025
Last approval on record: April 2025 (Jira IT-4521, approved by Sarah's manager)

EXPLANATION:
Service principal "sp-hr-onboarding" had 3 custom narrow-scope
ServiceNow roles as of April 2025 (last approved change).

In September 2025, roles were replaced with standard roles during
a platform migration. Standard roles grant broader access than the
custom roles they replaced. Additional role (hr_agent_workspace)
added ad-hoc to fix a support ticket. No approval record for changes.

Current roles grant access to HR cases and user profiles —
data domains the original automation was never designed to access.

DRIFT TIMELINE:
Mar 2025: 3 custom roles (initial setup, owner: Sarah Chen)
Apr 2025: 3 roles + close/reassign capabilities (approved, Jira IT-4521)
Sep 2025: 4 standard roles (no approval, platform migration + ad-hoc fix)

REMEDIATION:
1. Revert to custom roles matching original intent (incident create, task create, user read)
2. Verify bot still functions with reduced permissions
3. If broader access is needed, create approval record with new owner

Finding 3: Blast Radius — Standing Execution Path to Sensitive Data

TRIGGER: Autonomous identity with no owner has execution path
to business-critical data domains

BLAST RADIUS:
┌──────────────────────────────────────────────────────────────────────┐
│ Reachable from sp-hr-onboarding: │
│ │
│ Path │ Domain │ Data Classes │
│ ─────────────────────────────┼────────────┼─────────────────────────│
│ itil → incident/problem │ IT Ops │ security alerts, │
│ │ │ change records │
│ hr_agent_workspace → hr_case │ HR │ PII, salary, │
│ │ │ complaints, terminations │
│ personalize → sys_user │ Identity │ org structure, │
│ │ │ contact info, roles │
│ task_editor → sc_task │ IT Ops │ fulfillment tasks, │
│ │ │ approvals │
│ Mail.Read → shared mailbox │ HR │ new hire PII │
│ │ │ (SSN, salary, address) │
└──────────────────────────────────────────────────────────────────────┘

REMEDIATION:
1. Immediate: Remove hr_agent_workspace and personalize roles; revert to custom narrow roles
2. Rotate the client secret (invalidates any stolen copies)
3. Assign owner who accepts accountability
4. Set up monitoring: alert if bot accesses tables outside its onboarding scope

Evidence Pack (Sealed Output)

{
"evidence_pack_id": "ep-2025-11-15-sp-hr-onboarding",
"generated_at": "2025-11-15T14:30:00Z",
"integrity_hash": "sha256:a4f8c2e1...",

"identity": {
"display_name": "sp-hr-onboarding",
"type": "service_principal",
"source_system": "entra_id",
"app_id": "a1b2c3d4-...",
"created_at": "2025-03-10T09:00:00Z",
"last_activity": "2025-11-15T14:15:00Z",
"execution_frequency": "every 15 minutes",
"owner": null,
"owner_history": [
{
"human": "sarah.chen@contoso.com",
"from": "2025-03-10",
"until": "2025-08-15",
"reason": "account_deleted"
}
]
},

"authority_snapshot": {
"entra_permissions": ["Mail.Read"],
"servicenow_roles": ["itil", "hr_agent_workspace", "personalize", "task_editor"],
"credential_type": "client_secret",
"credential_expires": "2026-12-01"
},

"drift_timeline": [
{"date": "2025-03-10", "event": "service_principal_created", "actor": "sarah.chen@contoso.com"},
{"date": "2025-03-10", "event": "roles_granted", "roles": ["sn_incident_write", "sn_task_assign", "sn_user_read"]},
{"date": "2025-04-22", "event": "roles_expanded", "added": ["incident_close", "task_reassign"], "approval": "JIRA-IT-4521"},
{"date": "2025-07-15", "event": "owner_disabled", "human": "sarah.chen@contoso.com"},
{"date": "2025-08-15", "event": "owner_deleted", "human": "sarah.chen@contoso.com"},
{"date": "2025-09-03", "event": "roles_replaced", "before": ["sn_incident_write", "sn_task_assign", "sn_user_read"], "after": ["itil", "task_editor", "personalize"], "approval": null},
{"date": "2025-09-10", "event": "role_added", "added": ["hr_agent_workspace"], "reason": "support ticket SN-8834", "approval": null}
],

"blast_radius": {
"execution_paths": [
{"role": "itil", "tables": ["incident", "problem", "change_request"], "domain": "IT Operations", "sensitivity": "internal"},
{"role": "hr_agent_workspace", "tables": ["hr_case", "sn_hr_core_case"], "domain": "HR", "sensitivity": "confidential"},
{"role": "personalize", "tables": ["sys_user"], "domain": "Identity", "sensitivity": "confidential"},
{"role": "task_editor", "tables": ["task", "sc_task", "change_task"], "domain": "IT Operations", "sensitivity": "internal"}
],
"data_domains": ["HR", "IT Operations", "Identity"],
"sensitivity_levels": ["confidential", "internal"]
},

"findings": ["orphaned_ownership", "scope_drift", "privilege_justification_gap"],

"remediation_actions": [
{"priority": 1, "action": "assign_owner", "target": "sp-hr-onboarding"},
{"priority": 2, "action": "revert_roles", "target": "servicenow_roles", "revert_to": "2025-04-22"},
{"priority": 3, "action": "rotate_credential", "target": "client_secret"},
{"priority": 4, "action": "establish_monitoring", "target": "servicenow_audit_log"}
]
}

The Key Insight

This bot was built correctly, approved correctly, and ran correctly for months. The risk didn't emerge from a misconfiguration, a vulnerability, or a policy violation.

The risk emerged from time passing:

  • An employee left → ownership decayed
  • A platform upgrade happened → scope drifted
  • Nobody asked "should this still be running with these permissions?" → intent was never re-validated

This is what SecurityV0 finds: execution that outlives intent, authority that outlives ownership, and scope that outlives approval — with no alerts, no violations, and no existing tool catching it.