← All security articles
CRITICALAustralia (PDI structural validation pending on dev377226 un-activated)

Shared Substrate Cross-Tenant Compromise

Domain 15: Multi-Tenancy & Domain Separation·

Domain separation is a logical boundary, not an isolation boundary. Tenants share Java application servers, one database, a MID Server pool and an admin-account namespace, so anything that operates beneath the platform — a platform-level vulnerability, a compromised admin account, or an integration that runs outside domain context — crosses every domain at once. When a tenant's risk profile cannot tolerate that shared substrate, the answer is a separate instance, not tighter domain configuration.

Activation status: This article describes the limits of what domain separation can defend against — failure modes that depend on the underlying single-instance architecture. Behavioral claims about CVE blast radius and admin-context query rewriting are docs-anchored to ServiceNowDocs Australia (commit 3b27d9a5) and to the documented CVEs in domain-7-threat-modeling. PDI activation block does not affect this article materially because the failure modes are platform-level, not activation-state-level.

What This Is

Domain separation enforces a logical boundary between tenants inside a single ServiceNow instance. The boundary is implemented by query rewriting, ACL evaluation in domain context, and per-tenant business logic — all running on the same Java application servers, the same database, the same MID Server pool, the same admin account namespace. Anything that bypasses the platform — a platform-level CVE, an admin-account compromise, a MID Server compromise, a clone-with-data event — defeats the boundary in a single step [ref: markdown/platform-security/bp-segregate-secure.md @ 3b27d9a5].

The canonical doc lays out four mechanisms for segregating data, ordered by comprehensiveness [ref: markdown/platform-security/bp-segregate-secure.md @ 3b27d9a5]:

  1. Customizing the reference architecture — qualifiers and filters within a single domain
  2. Contextual security + Before-Query business rules — additional layers on top of ACLs
  3. Domain separation — query-level data restriction (the layer this DOM-* module covers)
  4. Separate instances — segregation at the database and application layer

The choice between (3) and (4) is the central architecture decision for MSPs and regulated multi-entity operators. Shared Substrate Cross-Tenant Compromise is the article that names the exact failure modes that justify (4) over (3).

What domain separation does NOT separate

Per the canonical concept doc [ref: markdown/platform-security/bp-what-is-domain-separation.md @ 3b27d9a5]:

When ServiceNow Research published "Cross tenant intelligence" [ref: markdown/platform-security/bp-dom-sep-how-it-works.md @ 3b27d9a5], the framing was a feature — automatic data/metadata/business-logic handling for tenants with access to additional tenant data. The same mechanism is the failure surface when access controls collapse: there is no architectural firewall, only logical scoping.

Why This Is Dangerous

Risk class: single-event total compromise

Every protection in Domain Separation Posture Assessment / Cross-Domain Visibility Leaks / Custom Tables Missing sys_domain / ACL Misconfigurations in Domain Context sits behind the assumption that the platform is intact and admin sessions are honest. When that assumption fails, all per-tenant configuration becomes irrelevant simultaneously. The blast radius is all tenants on the instance, immediately.

The historical incident base for ServiceNow proves this is not theoretical:

CVE / Incident Year Mechanism Cross-tenant impact
CVE-2024-4879 / -5217 / -5178 (Assetnote) 2024 Unauthenticated Jelly template injection → RCE → full-DB read (CVSS 9.3) Single bug, every tenant on the instance exposed simultaneously. → Jelly Template Injection RCE Chain (CVE-2024-4879, CVE-2024-5178, CVE-2024-5217)
CVE-2025-3648 ("Count(er) Strike", Varonis) 2025 Aggregate API count inference via conditional ACL evaluation Domain separation does not change ACL evaluation order; inference works the same way against domain-separated tables. → Empty Condition ACLs on Sensitive Tables
CVE-2025-12420 ("BodySnatcher", AppOmni) 2025 Virtual Agent API allows unauthenticated impersonation by email; bypasses MFA/SSO (CVSS 9.3) An attacker who impersonates an admin gains admin-context queries that bypass domain rewriting. Total tenant compromise from a single bug.
November 2023 ACL emergency response (ServiceNow proactive maintenance) 2023 Empty-condition ACLs across customer base Domain separation does not protect tables whose ACLs grant unconditional read; the OOB tenants suffered the same misconfiguration shape.

For each CVE that allowed unauthenticated platform-level access, the existence of domain separation made no difference. This is the substrate problem.

Attack Scenario 1: Admin compromise via phishing

Precondition: A 12-tenant MSP runs domain-separated ServiceNow. An MSP platform engineer has the global admin role to manage instance-wide configuration (plugins, system properties, MID Servers).

Compromise: The engineer clicks a phishing link, enters credentials on a fake SSO page. The attacker now has the engineer's session token.

Blast radius: The attacker logs in as the engineer. With admin role, queries bypass domain rewriting [behavior unverified on PDI; documented as platform behaviour and consistent with the architectural design — admin must be able to manage cross-tenant config]. The attacker runs:

// Read every tenant's incident table — admin role bypasses domain-aware rewriting
var inc = new GlideRecord('incident');
inc.query();
while (inc.next()) { /* exfiltrate */ }

In one session, all 12 tenants' incident records exfiltrate. No domain-separation control fires. No cross-tenant boundary alerts. The blast radius is the entire instance.

Attack Scenario 2: MID Server compromise leaks integration credentials across tenants

Precondition: The MSP runs a single MID Server pool serving discovery + integration jobs for all 12 tenants. (This is the default configuration — operators have to opt into per-tenant MID clusters.)

Compromise: An attacker compromises the MID Server VM (host-level — phishing the sysadmin, exploiting an unpatched OS vulnerability). The MID Server holds integration credentials in its keystore for every job it runs.

Blast radius: The MID Server's credentials cover all 12 tenants. Tenant A's ServiceNow → AD integration credentials, Tenant B's cloud-platform credentials, Tenant C's SAP RFC credentials — all on the same compromised host. Domain separation inside ServiceNow doesn't interact with this — the MID Server is upstream of the platform.

This is the failure mode that Shared Substrate Cross-Tenant Compromise names but the docs only obliquely address. The reference architecture docs [ref: markdown/platform-security/bp-sp-reference-arch-ds.md @ 3b27d9a5] [ref: markdown/platform-security/bp-sp-reference-arch-dedicated.md @ 3b27d9a5] [ref: markdown/platform-security/bp-sp-reference-arch-hybrid.md @ 3b27d9a5] describe alternative deployment patterns, but the trade-off framing stays at the platform level. → Integration Credential and Sensitive Payload Exposure, ECC Queue Security & Message Manipulation

Attack Scenario 3: Clone-with-data leaks cross-tenant production data into sub-prod

Precondition: Operator clones production to a sub-prod instance for performance testing. Production has 12 tenants. Sub-prod is a single test environment.

Outcome: All 12 tenants' production data lands on sub-prod, which has weaker access controls (test ACLs, test users with broader roles, no Domain Separation Center auditing). Anyone with sub-prod access has cross-tenant production data. → Sub-Production Instance Cloning & Data Exposure Risks

The clone preserves domain separation structurally (the sys_domain field values are copied), but the operational controls are different. A tenant whose data was supposed to never leave production now has copies in test. Domain separation didn't fail; the substrate (the clone job) did the wrong thing.

Attack Scenario 4: Platform plugin activation affects all tenants simultaneously

Precondition: The MSP installs a new plugin (e.g., a recommendation engine) into the production instance. The plugin's activation runs as system, activating across all domains.

Outcome: Tenant A wanted the plugin. Tenants B–L did not. The plugin is now active for all 12 tenants because plugin activation is global [ref: markdown/platform-security/bp-what-is-domain-separation.md @ 3b27d9a5 — "You can't separate some global standards and properties, such as system properties and table schema, per tenant"]. Tenant B's contractual scope didn't include the recommendation engine; their data is now flowing through it. This is configuration drift, not security compromise — but it's a contractual failure that domain separation cannot prevent.

How to Detect

This article is largely about what cannot be detected from inside the platform. Domain separation has no instrumentation for "an admin session is doing cross-tenant queries" — admin queries are valid by definition. The detections here are perimeter / configuration audits that surface the substrate-level risks:

PLACEHOLDER — verify table/field names on dev377226 (Australia, un-activated PDI). Most checks are configuration audits that do not require domain separation activation.

/* DOM-004 Detection Script — Read-only, safe in production
 * Shared-substrate cross-tenant compromise audit.
 *
 * This script audits the substrate-level controls that exist OUTSIDE
 * domain-separation enforcement. It runs whether or not domain separation
 * is active because the failure modes apply equally.
 */
(function() {
  var findings = [];

  // 1. Count of users with global admin role across all domains
  // Each is a single-event total-compromise vector.
  var admins = new GlideRecord('sys_user_has_role');
  admins.addQuery('role.name', 'admin');
  admins.addQuery('user.active', true);
  admins.query();
  findings.push({
    check: 'global_admin_count',
    count: admins.getRowCount(),
    severity: admins.getRowCount() > 10 ? 'CRITICAL' : (admins.getRowCount() > 5 ? 'HIGH' : 'WARN'),
    note: 'Each global admin can bypass domain-aware query rewriting. Total tenant compromise on session compromise. Aim for the smallest workable number.',
    cross_ref: 'ACL-004'
  });

  // 2. MID Server pool topology — are MID Servers segmented per tenant?
  // VERIFY on PDI: ecc_agent table schema; MID Server cluster definitions live in ecc_agent_cluster (verify).
  var midServers = new GlideRecord('ecc_agent');
  midServers.addQuery('status', 'Up');
  midServers.query();
  var midCount = midServers.getRowCount();
  findings.push({
    check: 'mid_server_count',
    count: midCount,
    severity: 'INFO',
    note: 'If MID Servers are not segmented per tenant, a single MID compromise leaks integration credentials across all served tenants. Review ecc_agent_cluster definitions and per-tenant assignments.',
    cross_ref: 'INTEG-001'
  });

  // 3. Cross-tenant credential aliases on connection records
  // Connections that reference shared credential aliases break the per-tenant credential isolation assumption.
  // VERIFY on PDI: discovery_credentials / sys_alias schema.
  var creds = new GlideRecord('discovery_credentials');
  creds.addQuery('active', true);
  creds.query();
  findings.push({
    check: 'shared_credential_count',
    count: creds.getRowCount(),
    severity: 'INFO',
    note: 'Audit each credential against per-tenant scope. Credentials used by multiple tenants are a cross-tenant compromise vector.',
    cross_ref: 'INTEG-002'
  });

  // 4. Plugin activation drift detection
  // List plugins active in the last 90 days. Plugin activations affect all tenants;
  // each should be ticketed per-tenant if the plugin's scope spans tenant data.
  // VERIFY on PDI: v_plugin schema.
  var plugins = new GlideRecord('v_plugin');
  plugins.addQuery('active', true);
  plugins.addQuery('sys_updated_on', '>=', gs.beginningOfLast90Days());
  plugins.orderByDesc('sys_updated_on');
  plugins.query();
  var recentPlugins = [];
  while (plugins.next()) {
    recentPlugins.push({
      id: plugins.getValue('id'),
      name: plugins.getValue('name'),
      activated: plugins.getValue('sys_updated_on')
    });
  }
  findings.push({
    check: 'recent_plugin_activations_90d',
    count: recentPlugins.length,
    severity: recentPlugins.length > 5 ? 'WARN' : 'INFO',
    note: 'Plugin activations are global. Each should have a per-tenant impact assessment. Confirm the activation ticket reviewed cross-tenant data flow.',
    plugins: recentPlugins
  });

  // 5. Sub-prod instances ingest production data via clone — is the clone exclusion list configured?
  // The clone-with-data risk is platform-level; the protection is the clone exclusion / data-preserver setup.
  // VERIFY on PDI: sys_clone_data_preserver schema.
  // Note: sys_clone_data_preserver is in the Zurich poison_pills list — verify pre-Australia migration status.
  var preservers = new GlideRecord('sys_clone_data_preserver');
  if (preservers.isValid && preservers.isValid()) {
    preservers.query();
    findings.push({
      check: 'clone_data_preserver_count',
      count: preservers.getRowCount(),
      severity: preservers.getRowCount() === 0 ? 'WARN' : 'INFO',
      note: 'Clone-with-data preservers protect specific tables from being overwritten in clones. Production-clone-to-sub-prod with no preservers risks tenant data leaking into less-controlled environments.',
      cross_ref: 'DATA-005'
    });
  }

  gs.print(JSON.stringify(findings, null, 2));
})();

What this detection does NOT cover

Remediation

1. Constrain global admin count

Per the canonical principle that the smallest workable number of admins minimizes blast radius:

This is a generic ServiceNow security recommendation that becomes critical in domain-separated MSPs. → ACL Role Breadth & Overly Permissive Role Assignments

2. Segment MID Server pools per tenant or per trust boundary

Per the reference architecture options [ref: markdown/platform-security/bp-sp-reference-arch-ds.md @ 3b27d9a5] [ref: markdown/platform-security/bp-sp-reference-arch-hybrid.md @ 3b27d9a5]:

3. Configure clone-with-data exclusions

Before any production-to-sub-prod clone:

4. Plugin activation gate

Treat every plugin activation as a cross-tenant change. Pre-activation checklist:

5. Use the dedicated-instance decision tree to escalate

Per [ref: markdown/platform-security/bp-ded-instance-decision-tree.md @ 3b27d9a5], the canonical decision framework: when a tenant's regulatory scope, data sensitivity, or contractual isolation requirement exceeds what domain separation can provide, recommend separate instances. The Shared Substrate Cross-Tenant Compromise detection findings are the inputs — high admin count, shared MID, regulated tenants, plugin churn — into that decision.

Regulatory Impact

Expert Notes

Why this article is CRITICAL severity

Domain Separation Posture Assessment / Cross-Domain Visibility Leaks / Custom Tables Missing sys_domain / ACL Misconfigurations in Domain Context are HIGH because their failure modes leak data inside the platform's own bounded blast radius. Shared Substrate Cross-Tenant Compromise is CRITICAL because its failure modes have no platform-bounded blast radius. A platform-level CVE or admin compromise breaks every tenant simultaneously, and the platform offers no detection or mitigation for the breach until forensics post-event.

When domain separation is the wrong answer

The four-layer hierarchy from [ref: markdown/platform-security/bp-segregate-secure.md @ 3b27d9a5] (reference architecture → contextual security → domain separation → separate instances) is presented as a comprehensiveness ladder. Shared Substrate Cross-Tenant Compromise is the article that names the threshold: when a tenant's risk profile crosses what level (3) can credibly defend, level (4) is required. Recommending domain separation for tenants with that risk profile is the operator's decision — and a documented bad one when those tenants get compromised through one of the named scenarios above.

The dedicated-instance decision tree [ref: markdown/platform-security/bp-ded-instance-decision-tree.md @ 3b27d9a5] is the formal framework. Shared Substrate Cross-Tenant Compromise is the operational restatement of why the tree exists.

What to tell auditors

When an auditor asks "how does domain separation protect tenant X from tenant Y?" the honest answer has two parts:

  1. Inside the platform under normal conditions: query rewriting, ACL evaluation in domain context, per-tenant business logic — listed in Domain Separation Posture Assessment / Cross-Domain Visibility Leaks / ACL Misconfigurations in Domain Context.
  2. Failure modes outside that envelope: documented in Shared Substrate Cross-Tenant Compromise. The mitigations are at the architecture level (separate instances for the highest-sensitivity tenants), at the operational level (admin discipline, MID segmentation, clone exclusions), and at the SIEM level (admin-session anomaly detection).

A tenant model document that lists "highest-sensitivity tenants on dedicated instance, lower-sensitivity tenants on shared domain-separated instance" is the artefact auditors will accept as the right answer to the segregation question.