Skip to main content

Security Architecture

This document provides a deep dive into Squid's security architecture, examining how security decisions are made at each layer of the system.

Architectural Principles

Principle 1: Defense in Depth

No single security control is sufficient. Squid implements multiple independent layers:

┌───────────────────────────────────────────────────────────────────────┐
│ Security Layers │
├───────────────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Layer │ │ Layer │ │ Layer │ │ Layer │ │
│ │ 1 │ │ 2 │ │ 3 │ │ 4 │ │
│ │ │ │ │ │ │ │ │ │
│ │ Session │→→│ Role │→→│ Data │→→│ Query │ │
│ │ Auth │ │ Access │ │ Access │ │ Validation │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ Compromise of one layer does not compromise the entire system │
└───────────────────────────────────────────────────────────────────────┘

Principle 2: Fail Secure

When security checks fail, Squid denies access rather than defaulting to permissive behavior:

// Example: Role check
if (!gs.hasRole(ACCESS_ROLE_REST)) {
throw new IllegalAccessException("You are not authorized...");
// Does NOT proceed with reduced access
}

Principle 3: Explicit Over Implicit

Data access requires explicit configuration. There is no "default access to all data":

  • Configurations must be defined
  • Roles must be assigned
  • Views must expose columns
  • Relations must be permitted

Principle 4: Transparency

Security decisions are visible and auditable:

  • Errors explain why access was denied
  • Metadata includes requester information
  • Warnings surface potential security issues

Component Architecture

Request Processing Pipeline

                              ┌─────────────────────────────────────┐
│ Squid Instance │
│ (Single-Use) │
┌─────────────┐ │ │
│ HTTP │ │ ┌─────────────────────────────┐ │
│ Request │─────────────▶│ │ Security Checkpoint │ │
└─────────────┘ │ │ │ │
│ │ 1. _checkReuse() │ │
│ │ 2. _checkAccess() │ │
│ │ 3. _checkConfigAccess() │ │
│ │ 4. _validateQuery() │ │
│ └─────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ Data Processing │ │
│ │ │ │
│ │ • Load Configuration │ │
│ │ • Execute View Query │ │
│ │ • Resolve References │ │
│ │ • Process Relations │ │
│ └─────────────────────────────┘ │
│ │ │
│ ▼ │
┌─────────────┐ │ ┌─────────────────────────────┐ │
│ JSON │◀─────────────│ │ Response Builder │ │
│ Response │ │ │ │ │
└─────────────┘ │ │ • Assemble JSON │ │
│ │ • Add Metadata │ │
│ │ • Include Warnings │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘

Security Components

1. Squid Instance Manager

class Squid {
private _used: boolean = false;

_checkReuse(): void {
if (this._used) {
throw new IllegalStateException("Reuse not permitted");
}
this._used = true;
}
}

Purpose: Prevents state leakage between requests by ensuring each Squid instance handles exactly one request.

2. Access Controller

// Role-based access check
_checkAccess(): void {
if (!gs.hasRole(ACCESS_ROLE_REST)) {
throw new IllegalAccessException("Not authorized for API");
}
}

// Configuration-based access check
_checkConfigAccess(roles: SysId[]): void {
const userRoles = getUserRoles();
if (intersection(userRoles, roles).length === 0) {
throw new IllegalAccessException("Not authorized for configuration");
}
}

Purpose: Enforces role requirements at API and configuration levels.

3. Query Validator

const FORBIDDEN_OPS = /(\^NQ)/g;
const RESTRICTED_OPS = /(%|ENDSWITH|javascript:|...)/g;

_validateQuery(query: string, config: Config): void {
// Always block forbidden operators
if (query.match(FORBIDDEN_OPS)) {
throw new BadRequestException("Forbidden operator");
}

// Block restricted operators if config requires
if (config.queryRestricted && query.match(RESTRICTED_OPS)) {
throw new BadRequestException("Restricted operator");
}
}

Purpose: Prevents security circumvention and performance attacks through query manipulation.

4. Data Access Layer (DbUtils)

function getGlideRecord(table: string, ignoreAcls?: boolean): GlideRecord {
return ignoreAcls
? new GlideRecord(table) // Direct access
: new GlideRecordSecure(table); // ACL-enforced access
}

Purpose: Controls whether ACLs are applied to data queries based on configuration.

Security Decision Points

Decision Point 1: API Access

Input: HTTP Request with authenticated session
Question: Can this user access the Squid API?
Check: Does user have x_a46gh_squidx.rest role?
Result: Allow or 403 Forbidden

Decision Point 2: Configuration Access

Input: Configuration name from URL
Question: Can this user access this configuration?
Checks:
1. Does configuration exist?
2. Is configuration a root configuration?
3. Does user have required role(s)?
Result: Allow or 403/404

Decision Point 3: Query Validity

Input: encodedQuery parameter
Question: Is this query safe to execute?
Checks:
1. Contains forbidden operators? → Reject
2. Contains restricted operators AND config restricts? → Reject
3. Valid parameter formats? → Continue
Result: Allow or 400 Bad Request

Decision Point 4: Relation Access

Input: relations parameter
Question: Can this user request these relations?
Checks:
1. Is Relations Restricted enabled?
2. Are requested relations in Allowed Relations?
Result: Allow or 400 Bad Request

Decision Point 5: ACL Application

Input: Data query about to execute
Question: Should ACLs be applied?
Checks:
1. allowAclOverride system property set?
2. Config uses ServiceNow ACLs?
Result: Use GlideRecord or GlideRecordSecure

Data Flow Security

Outbound Data Flow

┌─────────────────────────────────────────────────────────────────────┐
│ ServiceNow Database │
└─────────────────────────────────────────────────────────────────────┘

│ Tables contain ALL data

┌─────────────────────────────────────────────────────────────────────┐
│ Database Views │
│ │
│ Filter: Only configured columns exposed │
│ Join: Only configured table relationships │
└─────────────────────────────────────────────────────────────────────┘

│ Views restrict columns

┌─────────────────────────────────────────────────────────────────────┐
│ View Filters │
│ │
│ Filter: Admin-defined row restrictions │
└─────────────────────────────────────────────────────────────────────┘

│ Filters restrict rows

┌─────────────────────────────────────────────────────────────────────┐
│ User Query │
│ │
│ Filter: Caller-provided (validated) query │
└─────────────────────────────────────────────────────────────────────┘

│ Further row restriction

┌─────────────────────────────────────────────────────────────────────┐
│ JSON Response │
│ │
│ Only data that passed ALL filters │
└─────────────────────────────────────────────────────────────────────┘

Reference Resolution Security

When Squid resolves references, it uses configuration-defined mappings:

┌─────────────────────────────────────────────────────────────────────┐
│ Root Entity: cmdb_ci_server (sys_id: abc123) │
│ Reference Field: manufacturer │
│ Reference Value: core_company (sys_id: xyz789) │
└─────────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ Reference Resolution │
│ │
│ 1. Find reference configuration for core_company │
│ 2. Load core_company entity through its view │
│ 3. Apply core_company view's column restrictions │
│ 4. Do NOT re-check role access (implicit via root) │
└─────────────────────────────────────────────────────────────────────┘

Security Note: Referenced entity access is implicit based on root configuration access. The referenced configuration's view still controls what columns are exposed.

Error Handling Security

Error Information Disclosure

Squid balances helpful error messages with security:

Exposed in errors:

  • Why access was denied (missing role, invalid query)
  • Configuration name requested
  • Operator that caused rejection

NOT exposed in errors:

  • Internal system paths
  • Database schema details
  • Other user information
  • Stack traces (in production)

Error Categorization

// Security errors - clear user feedback
throw new IllegalAccessException("Not authorized"); // 403
throw new BadRequestException("Invalid query"); // 400

// System errors - generic feedback
throw new InternalException("Processing error"); // 500
// Details logged internally, not exposed to user

System Property Security Controls

PropertyPurposeSecurity Impact
x_a46gh_squidx.allowAclOverrideEnable ACL bypassHigh - controls data access model
x_a46gh_squidx.loglevelLogging verbosityMedium - affects audit detail
glide.installation.productionProduction flagLicense enforcement

Security Boundaries

Trust Boundary 1: API Perimeter

Everything outside the API is untrusted:

  • All parameters are validated
  • No user input is trusted implicitly
  • All queries are sanitized

Trust Boundary 2: Configuration Layer

Configurations are trusted as administrator-defined:

  • View filters can use any operators
  • Administrators can enable restricted features
  • Configuration errors are logged, not exploited

Trust Boundary 3: ServiceNow Platform

Squid trusts ServiceNow for:

  • Session authentication
  • Role management
  • Database access
  • ACL enforcement (when enabled)

Threat Model Summary

ThreatMitigation
Unauthorized API accessrest role requirement
Unauthorized configuration accessConfiguration role requirements
Data exfiltration via query manipulationForbidden/restricted operators
ACL bypassExplicit allowAclOverride requirement
Performance DoSRestricted operators, limits
State leakageSingle-use instances
Injection attacksParameter validation, GlideRecord APIs
We track. Ok?