Skip to main content

Veza vs SecurityV0: ServiceNow Integration Analysis

Date: 2026-02-02 Purpose: Deep comparison of Veza's ServiceNow integration approach and how SecurityV0 can implement similar (or differentiated) capabilities.


Executive Summary

AspectVezaSecurityV0
Core Question"Who has access to what?""Who is executing autonomously after ownership decays?"
FocusAccess visibility & entitlementsAutonomous execution & ownership decay
ServiceNow RoleSystem of record for accessSource of execution chains
Data DirectionBidirectional (read + write)Read-only (design constraint)
Real-time SyncYes (via writeback)Possible (via events)
Action ModelRemediation (revoke access)Evidence (prove the gap)

Key Insight: Veza and SecurityV0 solve different problems. Veza answers "who CAN access what" (entitlements). SecurityV0 answers "who IS executing and should they still be allowed to" (autonomous execution with decayed ownership).


Veza Capabilities Breakdown

1. Who Has Access to What

What Veza Does:

User → Roles → ACLs → Tables (CRUD permissions)

ServiceNow Data Veza Ingests:

  • sys_user - User accounts
  • sys_user_role - Role assignments
  • sys_user_has_role - User-role mappings
  • sys_security_acl - Access Control Lists
  • sys_db_object - Table definitions

Veza's Graph:

10 ServiceNow Users → 51 Roles → 1744 ACLs → 618 Tables

What This Misses (SecurityV0 Opportunity):

  • Veza shows who CAN access tables
  • Veza does NOT show which automations ARE executing
  • Veza does NOT trace: Automation → OAuth → Azure SP → Resources

2. Insert Data into ServiceNow

What Veza Does:

  • Writes entitlement records to ServiceNow tables
  • Creates historical audit trail in ServiceNow
  • Populates custom tables for compliance

SecurityV0 Constraint:

"Read-only — Platform never modifies source systems"

Implication: SecurityV0 cannot write to ServiceNow. This is a feature, not a limitation — it means SecurityV0 can be deployed without change management approval in sensitive environments.

3. Automated Ticket Creation

What Veza Does:

Veza Finding → ServiceNow Ticket → App Owner → Remediation → Close Ticket → Writeback to Veza

SecurityV0 Equivalent:

SecurityV0 Finding → Evidence Pack → Manual Review → (External ticketing if needed)

Phase 2 Opportunity: SecurityV0 could generate evidence packs that integrate with ServiceNow ITSM for remediation tracking, while maintaining read-only posture on the identity/automation side.

4. ServiceNow Writeback to Veza (Near Real-Time)

This is the most interesting capability for SecurityV0.


Deep Dive: Near Real-Time Updates

How Veza Achieves "Near Real-Time"

Based on the PDF and ServiceNow architecture, Veza likely uses one of these patterns:

Pattern 1: ServiceNow Business Rule → Outbound REST

┌─────────────────────────────────────────────────────────────┐
│ ServiceNow │
│ │
│ [Ticket Closed] → Business Rule → REST Message → Veza API │
│ (async, after) │
└─────────────────────────────────────────────────────────────┘

How It Works:

  1. Veza installs a Business Rule on change_request or custom table
  2. When ticket state changes to "Closed", rule fires
  3. Rule calls outbound REST Message to Veza's webhook endpoint
  4. Veza updates its internal state

ServiceNow Implementation:

// Business Rule: Notify Veza on Ticket Close
// Table: change_request
// When: after, update
// Condition: current.state == 'closed' && previous.state != 'closed'

(function executeRule(current, previous) {
var rm = new sn_ws.RESTMessageV2('Veza Writeback', 'ticket_closed');
rm.setStringParameterNoEscape('ticket_number', current.number);
rm.setStringParameterNoEscape('ticket_sys_id', current.sys_id);
rm.setStringParameterNoEscape('closed_by', current.closed_by.user_name);
rm.setStringParameterNoEscape('closed_at', current.closed_at);

var response = rm.executeAsync(); // Non-blocking
})(current, previous);

Pattern 2: ServiceNow Event → Script Action → Webhook

┌─────────────────────────────────────────────────────────────┐
│ ServiceNow │
│ │
│ [Record Change] → Event Queue → Script Action → Veza API │
│ (decoupled, async) │
└─────────────────────────────────────────────────────────────┘

Advantages:

  • Decoupled from transaction (doesn't slow down user)
  • Retries on failure
  • Auditable via event log

Pattern 3: Scheduled Job Polling (Near Real-Time = Every N Minutes)

┌─────────────────────────────────────────────────────────────┐
│ ServiceNow │ Veza │
│ │ │
│ [Scheduled Job] │ │
│ Every 5 minutes: │ │
│ - Query changed records │ │
│ - POST batch to Veza ─────────┼──────→ [Ingest Changes] │
│ │ │
└─────────────────────────────────────────────────────────────┘

ServiceNow Query Pattern:

// Find records changed in last 5 minutes
var gr = new GlideRecord('change_request');
gr.addQuery('sys_updated_on', '>=', gs.minutesAgo(5));
gr.query();

var changes = [];
while (gr.next()) {
changes.push({
sys_id: gr.sys_id.toString(),
number: gr.number.toString(),
state: gr.state.toString(),
sys_updated_on: gr.sys_updated_on.toString()
});
}

// POST to external system
var rm = new sn_ws.RESTMessageV2('Veza Sync', 'batch_update');
rm.setRequestBody(JSON.stringify({changes: changes}));
rm.execute();

Pattern 4: Flow Designer → REST Step (Low-Code)

┌─────────────────────────────────────────────────────────────┐
│ ServiceNow Flow Designer │
│ │
│ Trigger: Record Updated (change_request) │
│ Condition: state = closed │
│ Action: REST Step → POST to Veza webhook │
└─────────────────────────────────────────────────────────────┘

This is likely Veza's preferred pattern — it's low-code, configurable by customers, and doesn't require scripting knowledge.


SecurityV0 Implementation Options

Option A: Polling-Based Sync (Simplest)

Approach: SecurityV0 polls ServiceNow on a schedule.

┌─────────────────────────────────────────────────────────────┐
│ SecurityV0 │ ServiceNow │
│ │ │
│ [Scheduled Connector] │ │
│ Every 15 minutes: │ │
│ - GET /api/now/table/sys_script? │
│ sysparm_query=sys_updated_on>=<last_sync> │
│ - Diff against previous state │ │
│ - Generate change events │ │
└─────────────────────────────────────────────────────────────┘

Pros:

  • Read-only (respects SecurityV0 constraint)
  • Simple to implement
  • No ServiceNow customization required

Cons:

  • Not real-time (15-minute lag typical)
  • Polling overhead
  • May miss rapid changes (created + deleted within window)

Implementation:

def incremental_sync(self, last_sync_time: str) -> list[dict]:
"""Fetch records changed since last sync."""

tables_to_sync = [
"sys_script", # Business Rules
"sys_script_include", # Script Includes
"sysauto_script", # Scheduled Jobs
"sys_hub_flow", # Flow Designer
"oauth_entity", # OAuth apps
"sys_user", # Users (for ownership decay)
]

changes = []
for table in tables_to_sync:
query = f"sys_updated_on>={last_sync_time}"
records = self._get_table(table, query=query)
for record in records:
changes.append({
"table": table,
"sys_id": record.get("sys_id"),
"action": "upsert", # Could be insert or update
"data": record,
"changed_at": record.get("sys_updated_on")
})

return changes

Option B: Webhook from ServiceNow (Real-Time)

Approach: ServiceNow pushes changes to SecurityV0.

┌─────────────────────────────────────────────────────────────┐
│ ServiceNow │ SecurityV0 │
│ │ │
│ [Business Rule: sys_script] │ │
│ On insert/update/delete: │ │
│ - POST to SecurityV0 webhook ─┼──→ [Webhook Receiver] │
│ │ - Validate signature │
│ │ - Queue for processing │
│ │ - Trigger connector │
└─────────────────────────────────────────────────────────────┘

Pros:

  • Real-time (sub-second latency)
  • Efficient (only changed records)
  • Immediate drift detection

Cons:

  • Requires ServiceNow customization (Business Rules)
  • Violates "no changes to source systems" if we install the rules
  • Customer must configure

Compromise: Provide a ServiceNow Update Set that customers can install themselves. SecurityV0 remains read-only; customer chooses to enable real-time push.

ServiceNow Update Set Contents:

1. REST Message: SecurityV0 Webhook
- Endpoint: https://api.securityv0.com/webhooks/servicenow/{tenant_id}
- Authentication: OAuth2 or API Key (configurable)

2. Business Rules (async, after):
- sys_script: Notify SecurityV0 on BR change
- sys_script_include: Notify SecurityV0 on SI change
- sysauto_script: Notify SecurityV0 on Scheduled Job change
- sys_hub_flow: Notify SecurityV0 on Flow change
- oauth_entity: Notify SecurityV0 on OAuth change
- sys_user: Notify SecurityV0 on user status change

3. Script Include: SecurityV0WebhookHelper
- Formats payload
- Handles retries
- Logs failures

Option C: Hybrid (Polling + Webhook)

Approach: Polling as baseline, webhook for critical changes.

┌─────────────────────────────────────────────────────────────┐
│ ServiceNow │ SecurityV0 │
│ │ │
│ [Webhook: oauth_entity] │ │
│ On OAuth change: ─────────────┼──→ [Immediate alert] │
│ │ │
│ [Polling: everything else] │ │
│ Every 15 minutes: ◄───────────┼─── [Scheduled sync] │
└─────────────────────────────────────────────────────────────┘

Rationale:

  • OAuth entity changes are high-risk (new integration, credential rotation)
  • Business Rule changes can wait 15 minutes
  • Balances real-time needs with operational simplicity

ServiceNow sys_audit for Temporal Tracking

What sys_audit Provides

ServiceNow maintains an audit trail in sys_audit for all field changes on audited tables:

sys_audit table:
- tablename: which table was changed
- documentkey: sys_id of changed record
- fieldname: which field changed
- oldvalue: previous value
- newvalue: current value
- sys_created_on: when the change happened
- user: who made the change

Querying sys_audit for Scope Drift

Example: Detect role expansion on integration user

// Find all role changes for integration users in last 90 days
var gr = new GlideRecord('sys_audit');
gr.addQuery('tablename', 'sys_user_has_role');
gr.addQuery('fieldname', 'role');
gr.addQuery('sys_created_on', '>=', gs.daysAgo(90));
gr.orderBy('sys_created_on');
gr.query();

while (gr.next()) {
// documentkey is the sys_user_has_role record
// Look up the user to check if it's an integration account
var userRole = new GlideRecord('sys_user_has_role');
if (userRole.get(gr.documentkey)) {
var userName = userRole.user.user_name.toString();
if (userName.startsWith('svc_') || userName.startsWith('int_')) {
gs.info('Integration user role change: ' + userName);
gs.info(' Role added: ' + gr.newvalue);
gs.info(' Changed by: ' + gr.user);
gs.info(' Changed at: ' + gr.sys_created_on);
}
}
}

SecurityV0 Scope Drift Detection

def detect_scope_drift(self, oauth_entity_sys_id: str, days: int = 90) -> list[dict]:
"""Detect permission/role changes for an OAuth integration."""

# 1. Get audit records for the OAuth entity
oauth_audits = self._get_table("sys_audit",
query=f"tablename=oauth_entity^documentkey={oauth_entity_sys_id}^sys_created_on>={days_ago(days)}")

# 2. Get audit records for associated roles
# First find the integration user
oauth = self._get_record("oauth_entity", oauth_entity_sys_id)
integration_user = oauth.get("default_grant_user")

if integration_user:
role_audits = self._get_table("sys_audit",
query=f"tablename=sys_user_has_role^newvalue.user={integration_user}^sys_created_on>={days_ago(days)}")

# 3. Combine and return drift events
drift_events = []
for audit in oauth_audits + role_audits:
drift_events.append({
"timestamp": audit.get("sys_created_on"),
"field": audit.get("fieldname"),
"old_value": audit.get("oldvalue"),
"new_value": audit.get("newvalue"),
"changed_by": audit.get("user"),
"drift_type": "permission_expansion" if audit.get("fieldname") == "role" else "config_change"
})

return sorted(drift_events, key=lambda x: x["timestamp"])

Veza vs SecurityV0: Feature Matrix

FeatureVezaSecurityV0Notes
User → Role → ACL → Table mappingYesNot in scopeVeza's bread and butter
NHI visibilityShows accessShows executionDifferent angles
AI Agent trackingMentionedNot yetFuture opportunity
Autonomous execution chainsNot addressedCore featureSecurityV0 differentiator
Ownership decay detectionInactive accountsCreator + owner lineageSecurityV0 goes deeper
Scope driftRole changesTemporal authority changesSecurityV0 tracks over time
Blast radiusTables accessiblePhase 2Both can do this
Ticket creationAutomatedRead-onlySecurityV0 generates evidence, not tickets
ServiceNow writebackBidirectionalRead-onlyDesign constraint
Real-time syncVia writebackVia polling or webhookImplementable
Cross-system correlationMany systemsAzure ↔ ServiceNowSecurityV0 focused on execution chains

What SecurityV0 Should NOT Copy from Veza

1. Bidirectional Write Access

Veza writes to ServiceNow. SecurityV0 should not:

  • It increases deployment friction (change management approval)
  • It increases blast radius (what if SecurityV0 has a bug?)
  • It violates the "read-only" constraint

2. Generic Access Visibility

Veza answers "who can access what table". This is valuable but:

  • ServiceNow already has User Administration for this
  • Many IGA tools do this (SailPoint, Saviynt, etc.)
  • It's not the unique insight SecurityV0 provides

3. Remediation Automation

Veza auto-creates tickets and tracks remediation. SecurityV0 should:

  • Generate evidence packs (already planned)
  • Let customers integrate with their existing ITSM
  • Not own the remediation workflow

What SecurityV0 Should Learn from Veza

1. Near Real-Time Updates via Events

Implementation Plan:

Phase 1 (Current): Polling every 15 minutes
- Already implemented in correlator
- Sufficient for initial detection

Phase 2: Incremental sync with sys_updated_on
- Query only changed records since last sync
- Reduces API calls and processing time

Phase 3: Optional webhook (customer-installed)
- Provide Update Set for real-time push
- Customer installs in their ServiceNow
- SecurityV0 receives webhooks for critical changes

2. CMDB Integration for Ownership

Veza uses CMDB for approver lookup. SecurityV0 can:

  • Query cmdb_ci for application owners
  • Cross-reference OAuth apps with CMDB CIs
  • Find "current owner" even when sys_created_by has left

Tables to Query:

cmdb_ci_appl              - Application CIs
cmdb_rel_ci - CI relationships
sys_user_group - Groups (team ownership)
sn_customerservice_account - Business units

3. Graph Visualization of Permissions

Veza shows: Users → Roles → ACLs → Tables

SecurityV0 should show: Automation → OAuth → Azure SP → Azure Resources

Both are graph visualizations. The ReactFlow + Dagre implementation in SecurityV0 is well-suited for this.


Implementation Roadmap for SecurityV0

Phase 1: Enhanced Polling

# Incremental sync implementation
class ServiceNowConnector:

def sync(self, last_sync: datetime) -> SyncResult:
"""Incremental sync since last_sync timestamp."""

changes = []

# Sync automation tables
for table in AUTONOMOUS_TABLES:
records = self._get_changed_records(table, last_sync)
for record in records:
changes.append(self._normalize_record(table, record))

# Sync ownership tables
user_changes = self._get_changed_records("sys_user", last_sync)
for user in user_changes:
# Check if this user is an owner of any automation
if self._is_automation_owner(user):
changes.append(self._create_ownership_event(user))

return SyncResult(
changes=changes,
sync_time=datetime.utcnow()
)

Phase 2: sys_audit Integration

# Scope drift detection via sys_audit
class ScopeDriftDetector:

def detect_drift(self, oauth_entity: dict, window_days: int = 90) -> list[DriftEvent]:
"""Detect permission/role drift for OAuth integration."""

events = []

# 1. Get audit trail for OAuth entity
oauth_audits = self.sn_client.get_audits(
table="oauth_entity",
sys_id=oauth_entity["sys_id"],
since_days=window_days
)

# 2. Get audit trail for integration user roles
if oauth_entity.get("default_grant_user"):
role_audits = self.sn_client.get_role_audits(
user_id=oauth_entity["default_grant_user"],
since_days=window_days
)

for audit in role_audits:
if audit["oldvalue"] == "" and audit["newvalue"]:
events.append(DriftEvent(
type="role_added",
timestamp=audit["sys_created_on"],
role=audit["newvalue"],
added_by=audit["user"]
))

return events

Phase 3: Optional Real-Time Webhook

SecurityV0 Webhook Receiver:

@app.route("/webhooks/servicenow/<tenant_id>", methods=["POST"])
def servicenow_webhook(tenant_id: str):
"""Receive real-time updates from ServiceNow."""

# 1. Validate signature (shared secret or OAuth)
if not validate_webhook_signature(request):
return {"error": "Invalid signature"}, 401

# 2. Parse payload
payload = request.json
table = payload.get("table")
action = payload.get("action") # insert, update, delete
record = payload.get("record")

# 3. Queue for processing
queue_change_event(
tenant_id=tenant_id,
source="servicenow_webhook",
table=table,
action=action,
record=record
)

# 4. Return immediately (async processing)
return {"status": "queued"}, 202

ServiceNow Update Set (customer installs):

<!-- REST Message: SecurityV0 Webhook -->
<sys_rest_message>
<name>SecurityV0 Webhook</name>
<rest_endpoint>${securityv0.webhook_url}</rest_endpoint>
<authentication_type>oauth2</authentication_type>
</sys_rest_message>

<!-- Business Rule: Notify SecurityV0 on OAuth Entity Change -->
<sys_script>
<name>SecurityV0: OAuth Entity Webhook</name>
<collection>oauth_entity</collection>
<when>after</when>
<action_insert>true</action_insert>
<action_update>true</action_update>
<action_delete>true</action_delete>
<script>
var helper = new SecurityV0WebhookHelper();
helper.notifyChange('oauth_entity', current, previous);
</script>
</sys_script>

Competitive Positioning

Where Veza Wins

ScenarioWhy Veza
"Show me who can access HR data"Access graph with ACL resolution
"Automate access reviews"Built-in UAR workflow
"Create tickets for violations"ServiceNow ticket integration
"Track entitlements for SOX audit"Historical entitlement records

Where SecurityV0 Wins

ScenarioWhy SecurityV0
"Which automations are still running after owner left?"Execution + ownership decay detection
"What can this orphaned OAuth integration do in Azure?"Cross-system execution chain
"How did this integration's authority expand over time?"Temporal scope drift
"Prove this autonomous execution has no accountable owner"Evidence-grade finding

The Governance Gap Veza Cannot Close

From the PRD:

"We need to prove a governance gap ServiceNow cannot credibly close alone — even with Veza"

The Gap:

  • Veza shows that User X CAN access Table Y
  • Veza does NOT show that Automation A is EXECUTING as Azure SP B, calling Azure resources, after Owner C left

Example Finding Only SecurityV0 Can Produce:

FINDING: Orphaned Autonomous Execution Chain

EXECUTION CHAIN:
ServiceNow Business Rule: "Sync HR to Azure AD"
→ REST Message: "Microsoft Graph API"
→ OAuth Entity: "HR Integration" (client_id: abc123)
→ Azure Service Principal: "HR-Sync-Bot" (appId: abc123)
→ Azure Permission: User.ReadWrite.All

OWNERSHIP DECAY:
ServiceNow Creator: sarah.chen@company.com
Status: INACTIVE (disabled 6 months ago)
Azure SP Owner: sarah.chen@company.com
Status: DELETED (account removed)

EXECUTION EVIDENCE:
Last Azure Sign-in: 2 hours ago (847 sign-ins in last 30 days)
ServiceNow Scheduled Job: Runs every 15 minutes

CONCLUSION:
This automation continues executing with User.ReadWrite.All
permissions in Azure AD with NO accountable human owner.

VEZA WOULD SHOW:
Yes - HR Integration OAuth app exists
Yes - OAuth app has certain ServiceNow roles
No - Does NOT show the automation is actively executing
No - Does NOT show the Azure SP it authenticates as
No - Does NOT show the Azure permissions
No - Does NOT prove ownership decay across both systems

Summary

What to Implement from Veza's Approach

CapabilityImplementationPriority
Incremental syncQuery sys_updated_on >= last_syncP0
sys_audit for driftQuery audit table for role/permission changesP1
CMDB ownership lookupCross-reference OAuth apps with CI ownersP1
Optional webhookProvide Update Set for real-time pushP2
Graph visualizationAlready have ReactFlow + DagreDone

What NOT to Implement

CapabilityReason
Write to ServiceNowRead-only constraint
Automated ticket creationNot our workflow
User access reviewsNot our problem space
ACL → Table mappingVeza's domain, not ours

SecurityV0 Differentiators to Emphasize

  1. Autonomous execution detection — Not just access, but actual execution
  2. Cross-system correlation — ServiceNow → Azure SP → Azure resources
  3. Ownership decay lineage — Both sides, with temporal proof
  4. Evidence-grade output — Deterministic, walkable, auditable

  • ServiceNow Automation Types — All autonomous execution types
  • PRD: ServiceNow OAuth — Phase 1 requirements (see product/MVP1 - ServiceNow.md)
  • SecurityV0 High-Level Summary — Platform vision (see product/0 vision summary.md)