← All security articles
CRITICALWashington+

Instance Cloning & Data Masking Failures

Domain 13: Change & Release Security·

Cloning copies the whole database to the target instance, so unless clone profiles are configured first, production personal data and working credentials land in dev and test. Masking failures are usually silent: an exclusion covers a table but not its attachments, a data preserver restores a record the exclusion had removed, or a new table is added years after the profile was written and inherits no rule at all. Verify masking by inspecting the cloned target after the clone, because the profile's configuration is a statement of intent and the target is the evidence.

What This Is

Instance cloning is the process of copying the full database and configuration of one ServiceNow instance to another -- typically from production to sub-production (dev, test, staging). ServiceNow's native cloning mechanism copies the entire database, including all tables, records, attachments, and configuration. Clone profiles allow administrators to specify data preservers (records to keep on the target), data excluders (tables to skip during clone), and post-clone cleanup scripts.

Clone Architecture

Component Table / Setting Purpose Risk Level
Clone Profile clone_profile Defines what data to include/exclude HIGH
Data Preservers clone_data_preserver Records to keep unchanged on target MODERATE
Data Excluders clone_data_exclude Tables to exclude from clone CRITICAL
Post-Clone Cleanup clone_cleanup_script Scripts executed after clone completes HIGH
Clone History clone_history Audit trail of clone operations LOW
Data Masking Not native -- requires plugins Obfuscate sensitive field values CRITICAL

What Gets Cloned

When a production instance is cloned to a sub-production instance, the following data is copied by default unless explicitly excluded:

Data Category Tables Sensitivity Default Behavior
User Records sys_user Names, emails, phone numbers, photos Cloned with all PII
Passwords sys_user (password field) Hashed passwords for all users Cloned -- all prod passwords work on target
HR Records sn_hr_core_* Salary, SSN, performance reviews, disciplinary records Cloned unless excluded
Customer Data customer_contact, csm_* Customer PII, case details, communication history Cloned unless excluded
Incident Data incident, sc_req_item May contain PII, financial data, health info in description fields Cloned
Attachments sys_attachment, sys_attachment_doc Documents, screenshots, exports containing sensitive data Cloned
Integration Credentials sys_properties, oauth_entity, connection_alias API keys, OAuth tokens, connection strings Cloned -- credentials point to production systems
MID Server Config ecc_agent MID Server connection details and service accounts Cloned -- MID Servers may connect to target
Discovery Credentials discovery_credentials Network device, server, and database credentials Cloned -- credentials for production infrastructure
Email Config sys_email_account SMTP credentials and configuration Cloned -- target instance may send emails using production mail server
Audit Logs sys_audit, sys_audit_delete Full audit history from production Cloned

The Core Problem

ServiceNow does not provide native data masking during the clone process. The clone copies production data verbatim to sub-production instances that typically have weaker security controls: broader user access, less restrictive network rules, no security monitoring, and shared developer access. An attacker who compromises a dev instance -- or a developer with legitimate dev access -- gains access to full production data.

Why This Is Dangerous

Attack Scenario: Production PII Exposure in Dev Instance

Precondition: Developer access to a sub-production ServiceNow instance that was cloned from production.

Attack chain:

  1. A developer queries the dev instance which contains full production user data:

    // Developer on dev instance has access to production PII
    // because instance was cloned without data masking
    var users = new GlideRecord('sys_user');
    users.addQuery('active', true);
    users.setLimit(100);
    users.query();
    while (users.next()) {
        gs.info('User: ' + users.getValue('user_name') +
            ' | Name: ' + users.getValue('first_name') + ' ' +
            users.getValue('last_name') +
            ' | Email: ' + users.getValue('email') +
            ' | Phone: ' + users.getValue('phone') +
            ' | Manager: ' + users.getDisplayValue('manager') +
            ' | Department: ' + users.getDisplayValue('department'));
    }
    
  2. HR records, if present, contain highly sensitive data:

    // HR data cloned from production
    var hr = new GlideRecord('sn_hr_core_case');
    if (hr.isValid()) {
        hr.setLimit(20);
        hr.query();
        while (hr.next()) {
            gs.info('HR Case: ' + hr.getValue('number') +
                ' | Subject: ' + hr.getDisplayValue('subject_person') +
                ' | Type: ' + hr.getDisplayValue('hr_service') +
                ' | Description: ' + hr.getValue('description'));
        }
    }
    
  3. Customer data from CSM modules is also fully accessible:

    // Customer PII from production
    var contacts = new GlideRecord('customer_contact');
    if (contacts.isValid()) {
        contacts.setLimit(50);
        contacts.query();
        while (contacts.next()) {
            gs.info('Contact: ' + contacts.getValue('name') +
                ' | Email: ' + contacts.getValue('email') +
                ' | Phone: ' + contacts.getValue('phone') +
                ' | Account: ' + contacts.getDisplayValue('account'));
        }
    }
    

Impact: Full production PII accessible to all developers on the sub-production instance, violating GDPR Art. 32 (appropriate security measures), data minimization principles, and purpose limitation requirements.

Attack Scenario: Integration Credential Carryover

Precondition: Cloned sub-production instance with production integration credentials intact.

Attack chain:

  1. After a clone, integration credentials still point to production systems:

    // Check for production integration credentials on sub-prod
    gs.info('--- INTEGRATION CREDENTIALS POST-CLONE ---');
    
    // OAuth entities with production credentials
    var oauth = new GlideRecord('oauth_entity');
    oauth.query();
    while (oauth.next()) {
        gs.info('OAuth Entity: ' + oauth.getValue('name') +
            ' | Client ID: ' + oauth.getValue('client_id') +
            ' | Auth URL: ' + oauth.getValue('auth_url') +
            ' | Token URL: ' + oauth.getValue('token_url'));
    }
    
    // REST messages pointing to production endpoints
    var rest = new GlideRecord('sys_rest_message');
    rest.query();
    while (rest.next()) {
        gs.info('REST Message: ' + rest.getValue('name') +
            ' | Endpoint: ' + rest.getValue('rest_endpoint'));
    }
    
    // System properties with production URLs and credentials
    var props = new GlideRecord('sys_properties');
    props.addEncodedQuery(
        'nameLIKEendpoint^OR' +
        'nameLIKEurl^OR' +
        'nameLIKEhost^OR' +
        'nameLIKEcredential'
    );
    props.query();
    while (props.next()) {
        gs.info('Property: ' + props.getValue('name') +
            ' | Value: ' + props.getValue('value'));
    }
    
  2. If a developer triggers a workflow, integration hub flow, or scheduled job on the dev instance, it executes against production external systems using production credentials:

    • REST integrations call production APIs
    • Email configurations send emails through production SMTP
    • LDAP/SSO integrations authenticate against production directories
    • MID Servers attempt to connect to the target instance
  3. An attacker with dev instance access can deliberately trigger integrations to interact with production systems, using the dev instance as a proxy.

Impact: Sub-production instances act as unmonitored backdoors into production external systems via cloned integration credentials.

Attack Scenario: Production Password Reuse on Dev Instance

Precondition: Cloned sub-production instance with production password hashes intact.

Attack chain:

  1. After cloning, all production user passwords work on the dev instance, including admin accounts:

    // Check if production password hashes are present
    var admin = new GlideRecord('sys_user');
    admin.addQuery('user_name', 'admin');
    admin.query();
    if (admin.next()) {
        gs.info('Admin account: ' + admin.getValue('user_name') +
            ' | Password field populated: ' +
            (admin.getValue('password') ? 'YES' : 'NO') +
            ' | Active: ' + admin.getValue('active') +
            ' | Last login: ' + admin.getValue('last_login_time'));
    }
    
  2. An attacker who knows a production user's password can log into the dev instance with that same password and access all cloned production data.

  3. If the dev instance does not enforce SSO, password-based authentication provides direct access even if production requires SSO/MFA.

Impact: Production credentials valid on sub-production instances with weaker security controls, enabling credential reuse attacks.

Attack Scenario: Post-Clone Script Bypass

Precondition: Access to modify clone profiles or post-clone cleanup scripts.

Attack chain:

  1. An attacker disables or modifies post-clone cleanup scripts that are supposed to mask data and rotate credentials:

    // Enumerate post-clone cleanup scripts
    var cleanup = new GlideRecord('clone_cleanup_script');
    if (cleanup.isValid()) {
        cleanup.query();
        while (cleanup.next()) {
            gs.info('Cleanup Script: ' + cleanup.getValue('name') +
                ' | Active: ' + cleanup.getValue('active') +
                ' | Order: ' + cleanup.getValue('order') +
                ' | Description: ' + cleanup.getValue('description'));
        }
    }
    
  2. By deactivating the cleanup scripts, the clone completes without:

    • Resetting user passwords
    • Masking PII fields
    • Disabling integration credentials
    • Deactivating MID Servers
    • Disabling outbound email
  3. The target instance now has unmasked production data and active production credentials.

Impact: Data masking and credential cleanup controls bypassed, resulting in full production data exposure on sub-production instances.

How to Detect

Instance Clone Security Audit

/*
 * CHG-007 Detection Script
 * Audits clone profiles, data masking, post-clone scripts,
 * and detects production data on sub-production instances
 *
 * Run as: Background script with admin role
 * Impact: Read-only, safe for production
 * Versions: Washington+
 */

gs.info('=== CHG-007: INSTANCE CLONING SECURITY AUDIT ===');
gs.info('Scan started: ' + new GlideDateTime().getDisplayValue());
gs.info('');

var totalFindings = 0;

// 1. Check clone history
gs.info('--- CLONE HISTORY ---');
var cloneHist = new GlideRecord('clone_history');
if (cloneHist.isValid()) {
    cloneHist.orderByDesc('sys_created_on');
    cloneHist.setLimit(10);
    cloneHist.query();
    gs.info('Recent clone operations: ' + cloneHist.getRowCount());
    while (cloneHist.next()) {
        gs.info('  [CLONE] Source: ' + cloneHist.getValue('source_instance') +
            ' | Target: ' + cloneHist.getValue('target_instance') +
            ' | Date: ' + cloneHist.getValue('sys_created_on') +
            ' | Profile: ' + cloneHist.getDisplayValue('clone_profile') +
            ' | Status: ' + cloneHist.getValue('state'));
    }
} else {
    gs.info('  clone_history table not available');
}
gs.info('');

// 2. Audit clone profile data excluders
gs.info('--- CLONE DATA EXCLUDERS ---');
var sensitiveTables = [
    'sn_hr_core_case', 'sn_hr_core_profile', 'customer_contact',
    'sys_audit', 'sys_audit_delete', 'sys_email',
    'sys_attachment', 'sys_attachment_doc', 'oauth_credential',
    'discovery_credentials', 'sys_journal_field'
];

var excluder = new GlideRecord('clone_data_exclude');
if (excluder.isValid()) {
    excluder.query();
    var excludedTables = [];
    while (excluder.next()) {
        excludedTables.push(excluder.getValue('table'));
        gs.info('  Excluded: ' + excluder.getValue('table') +
            ' | Profile: ' + excluder.getDisplayValue('clone_profile'));
    }

    sensitiveTables.forEach(function(table) {
        if (excludedTables.indexOf(table) < 0) {
            gs.info('  [MISSING] Sensitive table NOT excluded: ' + table);
            totalFindings++;
        }
    });
} else {
    gs.info('  clone_data_exclude table not available');
}
gs.info('');

// 3. Audit post-clone cleanup scripts
gs.info('--- POST-CLONE CLEANUP SCRIPTS ---');
var cleanup = new GlideRecord('clone_cleanup_script');
if (cleanup.isValid()) {
    cleanup.query();
    var activeCleanup = 0;
    while (cleanup.next()) {
        var active = cleanup.getValue('active') === 'true' ||
            cleanup.getValue('active') === '1';
        if (active) activeCleanup++;
        gs.info('  [SCRIPT] ' + cleanup.getValue('name') +
            ' | Active: ' + active +
            ' | Order: ' + cleanup.getValue('order'));
    }
    gs.info('Active cleanup scripts: ' + activeCleanup);
    if (activeCleanup === 0) {
        gs.info('  [CRITICAL] No active post-clone cleanup scripts!');
        totalFindings++;
    }
} else {
    gs.info('  clone_cleanup_script table not available');
}
gs.info('');

// 4. Check for production data markers on current instance
gs.info('--- PRODUCTION DATA INDICATORS ---');

// Check if user records have real-looking email domains
var emailDomains = {};
var userCheck = new GlideRecord('sys_user');
userCheck.addQuery('active', true);
userCheck.addNotNullQuery('email');
userCheck.setLimit(500);
userCheck.query();
while (userCheck.next()) {
    var email = userCheck.getValue('email') || '';
    var domain = email.split('@')[1];
    if (domain) {
        emailDomains[domain] = (emailDomains[domain] || 0) + 1;
    }
}
gs.info('  Email domains found in sys_user:');
for (var d in emailDomains) {
    gs.info('    ' + d + ': ' + emailDomains[d] + ' users');
}
gs.info('');

// 5. Check for live integration credentials
gs.info('--- INTEGRATION CREDENTIAL STATUS ---');
var oauthCheck = new GlideRecord('oauth_entity');
oauthCheck.addQuery('active', true);
oauthCheck.query();
var activeOauth = oauthCheck.getRowCount();
gs.info('Active OAuth entities: ' + activeOauth);
while (oauthCheck.next()) {
    gs.info('  [OAUTH] ' + oauthCheck.getValue('name') +
        ' | Active: ' + oauthCheck.getValue('active') +
        ' | Token URL: ' + oauthCheck.getValue('token_url'));
    totalFindings++;
}
gs.info('');

// 6. Check for active email accounts
gs.info('--- EMAIL ACCOUNT STATUS ---');
var emailAcct = new GlideRecord('sys_email_account');
if (emailAcct.isValid()) {
    emailAcct.addQuery('active', true);
    emailAcct.query();
    var activeEmail = emailAcct.getRowCount();
    gs.info('Active email accounts: ' + activeEmail);
    while (emailAcct.next()) {
        gs.info('  [EMAIL] ' + emailAcct.getValue('name') +
            ' | Server: ' + emailAcct.getValue('server') +
            ' | Active: ' + emailAcct.getValue('active'));
    }
    if (activeEmail > 0) {
        gs.info('  [WARNING] Active email accounts may send from sub-prod!');
        totalFindings += activeEmail;
    }
}
gs.info('');

// 7. Check MID Server status
gs.info('--- MID SERVER STATUS ---');
var midCheck = new GlideRecord('ecc_agent');
midCheck.addQuery('status', 'Up');
midCheck.query();
var activeMids = midCheck.getRowCount();
gs.info('Active MID Servers: ' + activeMids);
while (midCheck.next()) {
    gs.info('  [MID] ' + midCheck.getValue('name') +
        ' | Host: ' + midCheck.getValue('host_name') +
        ' | Status: ' + midCheck.getValue('status'));
}
if (activeMids > 0) {
    gs.info('  [WARNING] Active MID Servers may execute against production!');
    totalFindings += activeMids;
}
gs.info('');

// Summary
gs.info('=== SUMMARY ===');
gs.info('Total clone security findings: ' + totalFindings);
gs.info('');
gs.info('CRITICAL ACTIONS:');
gs.info('1. Add sensitive tables to clone data excluders');
gs.info('2. Implement post-clone data masking for PII fields');
gs.info('3. Create post-clone scripts to disable integrations and MID Servers');
gs.info('4. Reset all user passwords after clone');
gs.info('5. Disable outbound email on sub-production instances');
gs.info('6. Rotate or invalidate all integration credentials post-clone');

Remediation

Step 1: Configure Clone Profile Data Exclusions

1. Add data excluders for sensitive tables:
   - sn_hr_core_case, sn_hr_core_profile (HR data)
   - customer_contact, csm_case (Customer data)
   - sys_email, sys_email_log (Email content)
   - sys_audit, sys_audit_delete (Audit trails -- large and sensitive)
   - sys_attachment, sys_attachment_doc (Attachments with sensitive content)
   - oauth_credential (OAuth tokens)
   - discovery_credentials (Network credentials)
   - sys_journal_field (Comments and work notes with PII)

2. Review data preservers:
   - Ensure data preservers do not override excluders
   - Validate that preserved data does not contain PII

Step 2: Implement Post-Clone Cleanup Scripts

1. Create mandatory post-clone scripts (in execution order):

   Order 100: Disable outbound email
   - Set glide.email.smtp.active = false
   - Deactivate all email accounts

   Order 200: Deactivate MID Servers
   - Set all ecc_agent records to status = Down
   - Clear MID Server credentials

   Order 300: Disable integrations
   - Deactivate all OAuth entities
   - Deactivate all scheduled imports/exports
   - Clear REST message authentication credentials
   - Disable Integration Hub connections

   Order 400: Reset user passwords
   - Reset all user passwords to a known value
   - Or lock all accounts and require SSO

   Order 500: Mask PII data
   - Anonymize sys_user: first_name, last_name, email, phone
   - Hash or randomize identifying fields
   - Replace real data with synthetic test data

   Order 600: Update system properties
   - Set instance name property to reflect sub-prod
   - Update glide.servlet.uri to sub-prod URL
   - Clear any environment-specific credentials in properties

2. Protect cleanup scripts from modification:
   - Restrict clone_cleanup_script write access to admin role
   - Enable audit on cleanup script modifications
   - Verify scripts execute successfully after each clone

Step 3: Implement Data Masking Strategy

1. Use ServiceNow Data Masking plugin (if available) or custom scripts:
   - Mask sys_user: email -> user123@test.example.com
   - Mask sys_user: phone -> 555-0100 + sequential
   - Mask sys_user: first_name/last_name -> "TestUser" + number
   - Mask customer records similarly

2. Preserve referential integrity:
   - Use deterministic masking (same input = same output)
   - Maintain unique constraints
   - Keep user_name values for testing purposes
   - Preserve role assignments for functional testing

3. Validate masking completeness:
   - Run post-clone verification script to check for unmasked PII
   - Audit free-text fields (description, comments) for sensitive content
   - Check attachments for sensitive documents

Step 4: Network-Level Controls for Sub-Production

1. Restrict sub-production instance network access:
   - Block outbound connections to production external systems
   - Whitelist only test/dev endpoints
   - Isolate sub-production network segment

2. Configure sub-production instance identification:
   - Set clear visual indicators (banner, theme color)
   - Include instance type in system properties
   - Add warning on login page: "This is a DEV/TEST instance"

Regulatory Impact

NIS2 Mapping

Article Requirement How Clone Insecurity Violates It Evidence After Fix
Art.21§2(d) Supply chain security including data processing security Production data copied to instances with weaker controls; integration credentials enable lateral access to production systems Clone profiles with data exclusions, post-clone masking, credential cleanup, network isolation

DORA Mapping

Article Requirement How Clone Insecurity Violates It Evidence After Fix
Art.9§4(d) ICT operations security Production data and credentials present on sub-production instances without equivalent security controls Data masking, integration disabling, MID Server deactivation, email blocking post-clone

ISO 27001:2022 Mapping

Control Requirement How Clone Insecurity Violates It Evidence After Fix
A.8.11 Data masking No native data masking during clone; production PII accessible on sub-production instances Post-clone masking scripts, data excluders, PII anonymization verified

GDPR Mapping

Article Requirement How Clone Insecurity Violates It Evidence After Fix
Art.32 Security of processing Personal data processed in environments with insufficient security measures; data minimization principle violated Data masking, purpose-limited test data, sub-production access controls, clone audit trail

Expert Notes

Practitioner annotations pending -- article content has been technically validated.