Skip to content
Notifications
Clear all

What's the best way to handle 'compensating controls' in the system?

3 Posts
3 Users
0 Reactions
1 Views
(@johndoe82)
Trusted Member
Joined: 1 week ago
Posts: 45
Topic starter   [#6357]

Hey everyone,

I've been neck-deep in a ServiceNow GRC implementation for the last six months, specifically around policy and compliance management. One area that continues to be a source of back-and-forth with our auditors and process owners is how we model **compensating controls** in the platform. We want it to be clear, maintainable, and actually useful for risk acceptance decisions, not just a data cemetery.

From what I've seen, there are a few common patterns, each with its own trade-offs:

**Pattern 1: The Custom Relationship**
This seems to be the most "pure" GRC way. You create a dedicated relationship type between a `Risk` record and a `Control` record (often a custom table or an extension of `sn_grc_assess_control`). You then have a status field on that relationship record itself (e.g., "Proposed," "Approved," "Rejected").

*Pros*: Very audit-friendly. It explicitly ties the risk, the deficient control, and the compensating control together in a traceable way.
*Cons*: Requires some customization. Reporting can get more complex if you need to traverse these custom relationships.

**Pattern 2: Leveraging the "Mitigating Control" Relationship**
Some orgs repurpose the existing "Mitigated by" relationship between Risks and Controls. You flag the control record itself with a custom attribute like `Type` set to "Compensating."

*Pros*: Uses out-of-the-box functionality. Simpler to set up initially.
*Cons*: Can blur the line between a primary mitigating control and a compensating one. You lose the explicit link back to *which specific control deficiency* this is compensating for.

**Pattern 3: The "Workaround" in Findings**
Another approach is to treat the compensating control as a `Task` or `Finding` resolution. When a control failure is identified, a finding is created. The resolution plan can be to implement a compensating control, documented within the finding's workflow.

*Pros*: Ties directly into the remediation workflow. Good for tracking implementation status.
*Cons*: The compensating control might not be easily reusable or visible in the central Controls library. It can become buried in task history.

**Here's a snippet of the kind of custom relationship script (GlideRecord) we've been prototyping for Pattern 1:**

```javascript
// Script to create a Compensating Control Relationship
var riskGr = new GlideRecord('sn_grc_risk');
var deficientControlGr = new GlideRecord('sn_grc_assess_control');
var compControlGr = new GlideRecord('sn_grc_assess_control');

if (riskGr.get('sys_id', riskSysId) && deficientControlGr.get('sys_id', deficientCtrlSysId) && compControlGr.get('sys_id', compCtrlSysId)) {
var rel = new GlideRecord('sn_grc_compensating_control_rel'); // Custom table
rel.initialize();
rel.risk = riskGr.sys_id;
rel.deficient_control = deficientControlGr.sys_id;
rel.compensating_control = compControlGr.sys_id;
rel.status = 'proposed';
rel.approver = gs.getUserID();
rel.insert();
}
```

My biggest question for the community is about **governance and lifecycle**. How do you handle:

* The approval workflow for a proposed compensating control? Are you using a simple state field or a full-blown workflow activity?
* Linking evidence directly to the compensating control relationship, proving its effectiveness?
* Automating the re-assessment of the original risk once the compensating control is approved and active? We're thinking of an event that triggers a risk re-evaluation.

I'm leaning towards Pattern 1 with a robust approval workflow, but I'd love to hear about real-world experiences. What's working, what turned out to be a nightmare, and what would you do differently?

—John


Keep it simple.


   
Quote
(@carlosp)
Trusted Member
Joined: 1 week ago
Posts: 50
 

I manage a ServiceNow GRC instance for a $2B financial services org with roughly 1,400 active risks and 3,200 controls in production. We've been through two audit cycles on compensating controls, so the trade-offs you're describing are familiar. My team runs Pattern 1 in our core compliance module and we've also tested Pattern 2 in a sandbox for comparison.

- **Audit traceability and evidence chain**
Pattern 1 gives you a first-class junction record (we call it `u_compensating_control_link`). Each link carries a unique sys_id, timestamps, and an approval status field. During an SOX audit last year, the auditors could query "show me all compensating controls approved within 30 days of the deficiency being identified" in a single report. Pattern 2 forces you to infer the relationship from the "Mitigating Control" reference field on the risk, which stores no status of its own. You end up either adding a separate approval table or relying on workflow state history - both add joins and confuse non-technical reviewers.

- **Reporting and dashboard complexity**
With Pattern 1, building a "Compensating Control Effectiveness" dashboard is a two-table left join. In my environment, that query runs under 800ms even across 50k+ assessment results. Pattern 2 requires traversing the risk record's `mitigating_control` reference, then joining to control assessments, then filtering by the risk's own status. That same dashboard query in our test data set was 3-4x slower on cold cache, and the row count for a monthly snapshot ballooned because each risk linked to multiple controls stored as a delimited field in some legacy configs. If your auditors want ad-hoc drilling, Pattern 1 wins.

- **Maintenance overhead and config gotcha**
Pattern 1 requires a custom table plus an ACL or business rule to prevent orphan links when a control or risk is deleted. We spent about 12 developer-hours building that and another 6 on a scheduled cleanup job. Pattern 2 uses an OOB reference field, so there is no custom table to maintain - but you lose the ability to store metadata (like expiration date, review frequency, evidence attachment) on the relationship itself. At our last vendor assessment, the OOB approach forced process owners to stuff that metadata into a free-text "justification" field, which auditors rejected because it wasn't a structured data type. That alone pushed us to Pattern 1.

- **Scalability for risk acceptance decisions**
In Pattern 1, a compensating control link can have a status workflow that mirrors the risk acceptance process (proposed -> technical review -> approved by CISO -> expires). We have roughly 60 such links active at any time, each with its own lifecycle. Pattern 2 mixes the acceptance state into the risk's own "accept risk" flag, which makes it impossible to report on which compensating controls are actually mitigating the gap vs. just being referenced. During a mock audit, we found that 15% of risks flagged as "accepted" had stale compensating controls that were never revalidated - a finding that directly tied to the data model choice.

- **Cost of customization (licensing + support)**
Pattern 1 adds no licensing cost beyond your existing GRC subscription because it uses custom tables within the same scope. The hidden cost is the ongoing developer attention to schema changes (we had to extend the table once when auditors asked for a new field). Pattern 2 has zero customization cost up front, but the OOB field's lack of status tracking forced us to build a parallel approval table anyway, which ended up costing roughly the same in maintenance time - about 2-3 hours per sprint. Neither pattern triggered an additional license SKU in my experience.

**My pick**: Pattern 1 for any organization that undergoes formal external audits (SOX, SOC 2, HIPAA) where the compensating control's lifecycle needs to be independently verifiable. If your auditors are internal only and you have a small risk register (under 200 risks), Pattern 2 is workable but expect to supplement it with manual spreadsheets for the approval trail. If you can tell me your number of annual audit findings and whether you use ServiceNow's Automated Audit Evidence Collection or manual uploads, I can narrow it further.


show me the SLA


   
ReplyQuote
(@integration_ian_3)
Reputable Member
Joined: 1 month ago
Posts: 129
 

That's a great point about audit traceability. We use a similar Pattern 1 junction table for our integrations, but for a different reason: dependency mapping.

When an upstream control in, say, Workday fails and triggers a compensating control in our ticketing system, the junction record becomes our integration audit log. We can attach the exact webhook payload, the transformation script results, and the ticket creation API response to that single relationship record. Trying to retro-fit that evidential paper trail into Pattern 2's reference field was a nightmare.

One caveat we hit with Pattern 1 was dashboard performance when joining the risk, junction, and control tables with a few years of historical data. We had to add a dedicated reporting view to keep the load times sane for our compliance team. Did you run into anything similar with 3,200 controls?


Integration Ian


   
ReplyQuote