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
| Aspect | Veza | SecurityV0 |
|---|---|---|
| Core Question | "Who has access to what?" | "Who is executing autonomously after ownership decays?" |
| Focus | Access visibility & entitlements | Autonomous execution & ownership decay |
| ServiceNow Role | System of record for access | Source of execution chains |
| Data Direction | Bidirectional (read + write) | Read-only (design constraint) |
| Real-time Sync | Yes (via writeback) | Possible (via events) |
| Action Model | Remediation (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 accountssys_user_role- Role assignmentssys_user_has_role- User-role mappingssys_security_acl- Access Control Listssys_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:
- Veza installs a Business Rule on
change_requestor custom table - When ticket state changes to "Closed", rule fires
- Rule calls outbound REST Message to Veza's webhook endpoint
- 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
| Feature | Veza | SecurityV0 | Notes |
|---|---|---|---|
| User → Role → ACL → Table mapping | Yes | Not in scope | Veza's bread and butter |
| NHI visibility | Shows access | Shows execution | Different angles |
| AI Agent tracking | Mentioned | Not yet | Future opportunity |
| Autonomous execution chains | Not addressed | Core feature | SecurityV0 differentiator |
| Ownership decay detection | Inactive accounts | Creator + owner lineage | SecurityV0 goes deeper |
| Scope drift | Role changes | Temporal authority changes | SecurityV0 tracks over time |
| Blast radius | Tables accessible | Phase 2 | Both can do this |
| Ticket creation | Automated | Read-only | SecurityV0 generates evidence, not tickets |
| ServiceNow writeback | Bidirectional | Read-only | Design constraint |
| Real-time sync | Via writeback | Via polling or webhook | Implementable |
| Cross-system correlation | Many systems | Azure ↔ ServiceNow | SecurityV0 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_cifor application owners - Cross-reference OAuth apps with CMDB CIs
- Find "current owner" even when
sys_created_byhas 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
| Scenario | Why 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
| Scenario | Why 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
| Capability | Implementation | Priority |
|---|---|---|
| Incremental sync | Query sys_updated_on >= last_sync | P0 |
| sys_audit for drift | Query audit table for role/permission changes | P1 |
| CMDB ownership lookup | Cross-reference OAuth apps with CI owners | P1 |
| Optional webhook | Provide Update Set for real-time push | P2 |
| Graph visualization | Already have ReactFlow + Dagre | Done |
What NOT to Implement
| Capability | Reason |
|---|---|
| Write to ServiceNow | Read-only constraint |
| Automated ticket creation | Not our workflow |
| User access reviews | Not our problem space |
| ACL → Table mapping | Veza's domain, not ours |
SecurityV0 Differentiators to Emphasize
- Autonomous execution detection — Not just access, but actual execution
- Cross-system correlation — ServiceNow → Azure SP → Azure resources
- Ownership decay lineage — Both sides, with temporal proof
- Evidence-grade output — Deterministic, walkable, auditable
Related Documentation
- 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)