Federal Modernization Workbook
Chapter 04 — Drift to a Typed Task
A workflow that posts a finding is easy to build and hard to see. Run its body against a real-shaped event before it runs against a real one.
The kit’s Build 1, the loop’s steps 2 and 3: an ISC event becomes a typed finding in ServiceNow. You can’t run an ISC workflow on a laptop, but you can resolve its HTTP Request body against a saved trigger input, the way its .$ keys say, and see the exact payload ServiceNow would get. workflow_payload.py does that, derives the intended state, checks it against Chapter 01’s contract, and lists the input fields the workflow doesn’t send. Running it found two gaps, both fixed here. Lab 1 runs on a laptop; Lab 2 creates one workflow in a SailPoint sandbox tenant.
Jump to a slide (16)
Deck · Narrated deck (about 10 minutes, captions) · Script · Captions. Recorded on November 1, 2026 from the approved chapter.
Goal and audience
Goal. By the end of this chapter you can say, for each of the three ISC workflows, what payload a given event produces, what intended state the finding will carry, and which event fields are dropped, and you can test a change to a workflow file before importing it.
Audience. Identity engineers who build ISC workflows and the developers who own the Scripted REST resource. You should read JSON and a little JavaScript. You don’t need a tenant until Lab 2.
The code: examples/servicenow-sailpoint
What exists
The implementation example’s Build 1 describes the design; the files are:
sailpoint/workflows/drift-to-servicenow.json: three workflows, one per trigger: Native Change Account Updated (an unauthorized write, on the account), Identity Attributes Changed (attribute drift, on the identity) and Provisioning Action Completed (a closure signal, never a closure). Each is a name, an owner, a trigger and a definition whose one action is an HTTP Request step. Each is created disabled, because SailPoint workflows can’t be created enabled.1servicenow/scripted-rest-identity-finding.js: validates the payload, sets priority and due date, de-duplicates, derives the intended state when the payload has none (a native change reverted, an attribute change kept), and moves matching findings to Awaiting verification on a successful provisioning signal.tests/python/test_contract.py: the workflow file’s shape, no secrets in it, and every field its bodies send read by the script.
What none of it showed was the payload itself. The body’s .$ keys ("account_id.$": "$.account.id") are resolved by SailPoint at run time, so the only way to see the result was to run the workflow in a tenant.
What this chapter adds
clients/python/workflow_payload.py picks the workflow for a trigger, resolves its body against a saved trigger input (simple paths only, $.a.b; anything else is refused, because this isn’t a JSONPath engine), derives the intended state exactly as the script does, checks the payload with finding_contract.validate, and lists the input’s top-level fields the body doesn’t read.
Three fictional trigger inputs in tests/fixtures/events/, in the shapes SailPoint publishes for each trigger’s example input:234 a native change to Alice’s Active Directory account (title changed, a group added, a proxy address added), a department change on Erin’s identity, and a successful provisioning action for Alice.
Two fixes, each in the workflow or script, the Python mirror and a test:
- Multivalued attributes. SailPoint’s Native Change Account Updated input has three kinds of change:
singleValueAttributeChanges,entitlementChangesandmultiValueAttributeChanges(each withname,addedValuesandremovedValues).5 The workflow sent the first two. A native change only to a multivalued attribute, a proxy address added out of band, say, produced an intended state with nothing in it, and a re-read would have found nothing to disagree with. The workflow now sendsmulti_value_changes, and the script reverts it: added values must be absent, removed values present. - The de-duplication key. The script de-duplicated on source, subject and class. Two attributes drifting on one user, department and manager, became one record, and the second finding’s intended state was lost, so the re-read checked only the first. The key now adds the attribute when the payload names one. The README and the implementation example say so.
The tests
tests/python/test_workflow_payload.py, 7 tests: each workflow against its event, the native change reverting all three kinds of change, the vacuous intended state the old workflow produced, the attribute change keeping the new value with the attribute in the key, the closure signal, refused paths and an unknown trigger. Two new node:test tests run the script itself: two attributes on one subject are two findings, and multivalued changes become entitlements absent and present. test_contract.py now also requires the script to read multi_value_changes.
The commands below resolve the workflow file against fictional inputs. They show what the body says to send, not what SailPoint’s HTTP Request step sends: whether the step resolves .$ keys inside its JSON body, and whether the input sits at $ or $.trigger, are on the kit README’s verify list. Lab 2 settles both for your tenant.
Lab 1: fixtures, no tenant
Work in examples/servicenow-sailpoint, with Python 3.10 or later and Node.js 18 or later.
Every command in this lab, in order, from examples/servicenow-sailpoint. Each step below says what it does and what to look for.
# Step 1: Run this chapter's tests
python -m pytest tests/python/test_workflow_payload.py tests/python/test_contract.py -q
# Step 2: See what the native-change workflow posts
python clients/python/workflow_payload.py --trigger idn:native-change-account-updated --event tests/fixtures/events/native-change-account-updated.json
# Step 3: See the mover's finding
python clients/python/workflow_payload.py --trigger idn:identity-attributes-changed --event tests/fixtures/events/identity-attributes-changed.json
# Step 4: See the closure signal
python clients/python/workflow_payload.py --trigger idn:post-provisioning --event tests/fixtures/events/provisioning-completed.json
# Step 5: Run the two new script tests
node --test --test-name-pattern="attribute is in the key|multivalued" tests/servicenow/servicenow_scripts.test.jsRun this chapter’s tests:
python -m pytest tests/python/test_workflow_payload.py tests/python/test_contract.py -qWhat the terminal shows, run on the kit’s fixtures (the same output as text) 11 passed.See what the native-change workflow posts:
python clients/python/workflow_payload.py --trigger idn:native-change-account-updated --event tests/fixtures/events/native-change-account-updated.jsonWorkflow: Drift to ServiceNow - native change (provenance) kind finding, drift_class unauthorized_write, system isc_account intended_state {"attributes": {"title": "Analyst"}, "entitlements_absent": [{"attribute": "proxyAddresses", "value": "smtp:ap-approver@contoso.example"}, {"attribute": "memberOf", "value": "CN=Payables Approvers,OU=Groups,DC=contoso,DC=example"}], "entitlements_present": []} contract: accepted: P2, priority 2, key sailpoint-isc|2c91800000000000000000000a1001ad|unauthorized_write input fields not sent: eventTypeWhat the terminal shows, run on the kit’s fixtures (the same output as text) The title goes back to Analyst, the group and the proxy address must be gone.
eventTypeis alwaysACCOUNT_UPDATEDfor this trigger, so dropping it loses nothing.6 Before the fix, the proxy address wasn’t in the intended state andmultiValueAttributeChangeswas in the last line.See the mover’s finding:
python clients/python/workflow_payload.py --trigger idn:identity-attributes-changed --event tests/fixtures/events/identity-attributes-changed.jsonWhat the terminal shows, run on the kit’s fixtures (the same output as text) intended_state {"attributes": {"department": "Finance"}},contract: accepted: P3, priority 3, key sailpoint-isc|2c9180000000000000000000000e1004|attribute_drift|department, andinput fields not sent: none. The key ends in the attribute.See the closure signal:
python clients/python/workflow_payload.py --trigger idn:post-provisioning --event tests/fixtures/events/provisioning-completed.jsonWhat the terminal shows, run on the kit’s fixtures (the same output as text) kind closure_signal,contract: accepted, andinput fields not sent: action, requester, sources, warnings. A signal moves matching findings to Awaiting verification; it never closes one.Run the two new script tests:
node --test --test-name-pattern="attribute is in the key|multivalued" tests/servicenow/servicenow_scripts.test.jsWhat the terminal shows, run on the kit’s fixtures (the same output as text) tests 2,pass 2,fail 0.
Lab 2: a SailPoint sandbox and a ServiceNow developer instance
Not run by the authors. Use a SailPoint sandbox tenant and the developer instance from Chapters 01 and 02, never production. The ServiceNow endpoint has to be reachable from SailPoint’s cloud, because ISC workflows call out from it (inference; verify list, item 18). Work in examples/servicenow-sailpoint. You’ll replace: <exported-workflow.json> (the path of the workflow you export in step 2), <trigger-input.json> (the path of the trigger input you save in step 3), <instance> (your developer instance’s name), and <verifier-client-id> and <verifier-client-secret> (the verifier’s OAuth integration from Chapter 02).
Every command in this lab, in order, from examples/servicenow-sailpoint. Each step below says what it does and what to look for.
# Step 2: Build one HTTP Request step by hand
python - sailpoint/workflows/drift-to-servicenow.json "<exported-workflow.json>" <<'EOF'
import json, sys
def http_attrs(o, found):
if isinstance(o, dict):
if o.get("actionId") == "sp:http":
found |= set(o.get("attributes", {}))
for v in o.values():
http_attrs(v, found)
elif isinstance(o, list):
for v in o:
http_attrs(v, found)
return found
kit = http_attrs(json.load(open(sys.argv[1], encoding="utf-8")), set())
exported = http_attrs(json.load(open(sys.argv[2], encoding="utf-8")), set())
print("only in the kit's file:", sorted(kit - exported))
print("only in your export:", sorted(exported - kit))
EOF
# Step 3: Point the step at a capture endpoint first
python clients/python/workflow_payload.py --trigger idn:native-change-account-updated --event "<trigger-input.json>"
# Step 4: Repeat the change
export SN_ACCESS_TOKEN=$(curl -s -X POST "https://<instance>.service-now.com/oauth_token.do" -d grant_type=client_credentials -d client_id="<verifier-client-id>" -d client_secret="<verifier-client-secret>" | python -c "import json,sys; print(json.load(sys.stdin)['access_token'])")
curl -s -H "Authorization: Bearer $SN_ACCESS_TOKEN" -H "Accept: application/json" "https://<instance>.service-now.com/api/now/table/u_identity_finding?sysparm_query=u_source=sailpoint-isc&sysparm_fields=number,u_dedupe_key,u_occurrences"
# Step 5: Back it out
unset SN_ACCESS_TOKENTurn on Native Change Detection on one test source, monitoring account updates and at least one attribute. The trigger fires only when that is on, account updates are monitored and a monitored attribute changed.7
Build one HTTP Request step by hand in the workflow editor and export the workflow. Use the workflow client from Chapter 03, with
sp:workflow:manage.8 Compare the step’s attribute names with the kit’s file:python - sailpoint/workflows/drift-to-servicenow.json "<exported-workflow.json>" <<'EOF' import json, sys def http_attrs(o, found): if isinstance(o, dict): if o.get("actionId") == "sp:http": found |= set(o.get("attributes", {})) for v in o.values(): http_attrs(v, found) elif isinstance(o, list): for v in o: http_attrs(v, found) return found kit = http_attrs(json.load(open(sys.argv[1], encoding="utf-8")), set()) exported = http_attrs(json.load(open(sys.argv[2], encoding="utf-8")), set()) print("only in the kit's file:", sorted(kit - exported)) print("only in your export:", sorted(exported - kit)) EOFTwo empty lists mean the names agree. If they differ, correct the kit’s file to the names your export uses (verify list, item 2).
Point the step at a capture endpoint first, or at the developer instance’s Scripted REST resource with a test ACL, enable the workflow, and change the monitored attribute on a test account directly on the source. Save the trigger input from the workflow’s execution history to a file, and run it through the tool:
python clients/python/workflow_payload.py --trigger idn:native-change-account-updated --event "<trigger-input.json>"The payload ServiceNow received must match the tool’s output. If it doesn’t, the
.$keys or the input’s root differ (verify list, item 3).Repeat the change on the same test account. The second event should de-duplicate (
200,deduplicated: true) and add one to the record’s occurrences. Read the findings’ occurrence counts as the verifier:export SN_ACCESS_TOKEN=$(curl -s -X POST "https://<instance>.service-now.com/oauth_token.do" -d grant_type=client_credentials -d client_id="<verifier-client-id>" -d client_secret="<verifier-client-secret>" | python -c "import json,sys; print(json.load(sys.stdin)['access_token'])") curl -s -H "Authorization: Bearer $SN_ACCESS_TOKEN" -H "Accept: application/json" "https://<instance>.service-now.com/api/now/table/u_identity_finding?sysparm_query=u_source=sailpoint-isc&sysparm_fields=number,u_dedupe_key,u_occurrences"Back it out. Disable and delete the workflow, turn Native Change Detection off on the test source if you turned it on for the lab, put the test account’s attribute back, delete the findings from the developer instance, and remove the workflow client. Clear the token from your shell:
unset SN_ACCESS_TOKEN
The verified facts
| Item | Value | Source |
|---|---|---|
| Native Change Account Updated | Fire-and-forget; fires when an aggregation finds an account updated directly on the source, with Native Change Detection on, account updates monitored and a monitored attribute changed; input has account, identity, source, accountChangeTypes, singleValueAttributeChanges, multiValueAttributeChanges, entitlementChanges, eventType (ACCOUNT_UPDATED) |
9 |
| Its trigger ID | Not in the published trigger table; the page names the event type only | 10 11 |
| Identity Attributes Changed | idn:identity-attributes-changed, fire-and-forget; input identity.id, identity.name, changes[].attribute, oldValue, newValue |
12 13 |
| Provisioning Action Completed | idn:post-provisioning, fire-and-forget; input includes trackingNumber, recipient, accountRequests (source, accountId, accountOperation, provisioningResult, provisioningTarget, ticketId, attributeRequests), errors |
14 15 |
| Workflow body | name, owner, description, definition, enabled (default false; can’t be created enabled), trigger |
16 |
| Creating a workflow | sp:workflow:manage |
17 |
| Scripted REST resource | REST_Endpoint ACL; web_service_admin to create |
18 |
What couldn’t be verified
- The Native Change Account Updated trigger ID. The kit uses
idn:native-change-account-updated; SailPoint’s trigger table doesn’t list it. Verify list, item 1. - The HTTP Request step’s JSON attribute names, and whether it resolves
.$keys in its body. SailPoint documents the step’s fields in the editor, not its JSON. Verify list, items 2 and 3. - The shape of each item in
entitlementChanges[].added. The script and the fixture readvalue; the trigger page lists the fields to the level ofaddedandremoved(research gaps, row 31). provisioningResultvalues beyondSUCCESS. Verify list, item 5.
Federal requirements and evidence
| What the output proves | Requirement it supports | KSI | Who keeps it |
|---|---|---|---|
| A write made outside the governed process is detected and raised as an owned, clocked finding whose intended state is the revert | NIST SP 800-53 CM-3 and SI-4 (system monitoring) at Moderate;19 SP 800-37’s call to monitor for unauthorized changes, as Book 13, Chapter 10 cites | KSI-CMT-LMC; KSI-IAM-SUS | The ServiceNow finding, with u_origin and u_raw_event |
| Each drifting attribute is its own piece of work with its own intended state | CM-3; Book 13’s evidence record, one result per finding | KSI-CMT-LMC | The finding records; test_workflow_payload.py and the node tests |
| A workflow change is tested before it runs | CM-3 (change control), SA-11 (developer testing) at Moderate20 | KSI-CMT-VTD; KSI-SCR-MIT | The pull request and its test run |
| Lab 1, step 1: the workflow payloads and the kit’s contract hold, tested before a change merges | CM-3(2) (testing, validation and documentation of changes); SA-11, at Moderate21 | KSI-CMT-VTD; KSI-SCR-MIT | The pytest tests/python/test_workflow_payload.py tests/python/test_contract.py run, in the pull request’s checks |
| Lab 1, step 2: a native change becomes an unauthorized-write finding whose intended state is the revert | CM-3; SI-4 | KSI-CMT-LMC; KSI-IAM-SUS | The workflow_payload.py --trigger idn:native-change-account-updated output, the reference Lab 2 compares against |
| Lab 1, step 3: a mover’s attribute changes become one finding per attribute | CM-3 | KSI-CMT-LMC | The workflow_payload.py --trigger idn:identity-attributes-changed output |
| Lab 1, step 4: a completed provisioning action moves matching findings to Awaiting verification and never closes them | CM-3; CM-4(2) (verification of controls) at Moderate22 | KSI-CMT-LMC; KSI-CMT-VTD | The workflow_payload.py --trigger idn:post-provisioning output |
| Lab 1, step 5: the attribute is in the de-duplication key, and multivalued changes are handled | CM-3(2) | KSI-CMT-VTD | The node --test --test-name-pattern run, in the pull request’s checks |
| Lab 2, step 1: Native Change Detection watches account updates and the chosen attributes on the test source | CM-3; SI-4 | KSI-CMT-LMC; KSI-IAM-SUS | The source’s Native Change Detection settings, recorded with the workflow |
| Lab 2, step 2: the hand-built HTTP Request step and the kit’s workflow file name the same attributes | CM-3: the workflow in the tenant matches the version-controlled one | KSI-CMT-RMV | The comparison’s output, kept with the workflow’s pull request |
Lab 2, step 3: the live workflow posts the payload workflow_payload.py predicts |
CM-3(2) | KSI-CMT-VTD | The captured request beside the workflow_payload.py output for the same trigger input |
| Lab 2, step 4: a repeat of the change de-duplicates into the open finding | CM-3; CA-7 (continuous monitoring) at Moderate23 | KSI-CMT-LMC; KSI-MLA-EVC | The finding’s u_dedupe_key and u_occurrences, from the Table API query |
| Lab 2, step 5: no test workflow, finding, workflow client or token is left | AC-2 (account management) at Moderate24 | KSI-IAM-SNU | The tenant’s workflow and API client lists; unset SN_ACCESS_TOKEN clears the shell |
The KSI column names the FedRAMP 20x Key Security Indicators whose related SP 800-53 controls include the row’s control.25 KSIs are indicators for cloud service providers: the agency authorizes its own system and lines its own evidence up with what its providers report against them; it doesn’t report KSIs itself.
Treating a native change as Book 03’s unauthorized write is the program’s (inference), as the example guide says. Provenance drift on a claim in active use is P1 in Book 13, Appendix B; the workflow can’t know whether a claim is in use, so a person or a rule raises it to P1.
Dangers
| Danger | What happens | The guard |
|---|---|---|
| An event field the workflow doesn’t send | Part of the change is never reverted, and the re-read passes | workflow_payload.py lists unmapped fields; the multivalued fix and its tests |
| Two drifts on one subject folded into one record | The second intended state is lost and never re-read | The attribute in the de-duplication key; a node test |
| A workflow imported enabled, or edited in the editor and not in the file | It runs untested, or the repository stops describing what runs | Workflows can’t be created enabled; export and diff after any editor change |
| Credentials in the workflow file | They reach the repository | REPLACE-IN-THE-UI placeholders; test_contract.py fails on a secret |
| A closure signal treated as closure | A provisioning success closes the finding without a re-read | The script only moves findings to Awaiting verification; the business rule (Chapter 05) blocks any other close |
| An unreachable or unauthenticated endpoint | Findings are lost, or anyone can post them | Reachability tested in Lab 2; authentication and a REST_Endpoint ACL required |
Checklist
Footnotes
SailPoint, Identity Security Cloud OpenAPI specification, v2025,
WorkflowBody(“Workflows cannot be created in an enabled state”), https://github.com/sailpoint-oss/api-specs/blob/main/idn/v2025/schemas/workflows/WorkflowBody.yaml (read November 1, 2026).↩︎SailPoint Developer, “Native Change Account Updated” event trigger (conditions, fire-and-forget, example input), https://developer.sailpoint.com/docs/extensibility/event-triggers/triggers/native-change-account-updated (read November 1, 2026).↩︎
SailPoint Developer, “Identity Attributes Changed” event trigger (example input), https://developer.sailpoint.com/docs/extensibility/event-triggers/triggers/identity-attribute-changed (read November 1, 2026).↩︎
SailPoint Developer, “Provisioning Completed” event trigger (example input), https://developer.sailpoint.com/docs/extensibility/event-triggers/triggers/provisioning-completed (read November 1, 2026).↩︎
SailPoint Developer, “Native Change Account Updated” event trigger (conditions, fire-and-forget, example input), https://developer.sailpoint.com/docs/extensibility/event-triggers/triggers/native-change-account-updated (read November 1, 2026).↩︎
SailPoint Developer, “Native Change Account Updated” event trigger (conditions, fire-and-forget, example input), https://developer.sailpoint.com/docs/extensibility/event-triggers/triggers/native-change-account-updated (read November 1, 2026).↩︎
SailPoint Developer, “Native Change Account Updated” event trigger (conditions, fire-and-forget, example input), https://developer.sailpoint.com/docs/extensibility/event-triggers/triggers/native-change-account-updated (read November 1, 2026).↩︎
SailPoint, Identity Security Cloud OpenAPI specification, v2025,
createWorkflow, https://github.com/sailpoint-oss/api-specs/blob/main/idn/v2025/paths/workflows/workflows.yaml (read November 1, 2026).↩︎SailPoint Developer, “Native Change Account Updated” event trigger (conditions, fire-and-forget, example input), https://developer.sailpoint.com/docs/extensibility/event-triggers/triggers/native-change-account-updated (read November 1, 2026).↩︎
SailPoint Developer, “Native Change Account Updated” event trigger (conditions, fire-and-forget, example input), https://developer.sailpoint.com/docs/extensibility/event-triggers/triggers/native-change-account-updated (read November 1, 2026).↩︎
SailPoint Developer, “Triggers,” Available Event Triggers table, https://developer.sailpoint.com/docs/api/triggers (read November 1, 2026).↩︎
SailPoint Developer, “Triggers,” Available Event Triggers table, https://developer.sailpoint.com/docs/api/triggers (read November 1, 2026).↩︎
SailPoint Developer, “Identity Attributes Changed” event trigger (example input), https://developer.sailpoint.com/docs/extensibility/event-triggers/triggers/identity-attribute-changed (read November 1, 2026).↩︎
SailPoint Developer, “Triggers,” Available Event Triggers table, https://developer.sailpoint.com/docs/api/triggers (read November 1, 2026).↩︎
SailPoint Developer, “Provisioning Completed” event trigger (example input), https://developer.sailpoint.com/docs/extensibility/event-triggers/triggers/provisioning-completed (read November 1, 2026).↩︎
SailPoint, Identity Security Cloud OpenAPI specification, v2025,
WorkflowBody(“Workflows cannot be created in an enabled state”), https://github.com/sailpoint-oss/api-specs/blob/main/idn/v2025/schemas/workflows/WorkflowBody.yaml (read November 1, 2026).↩︎SailPoint, Identity Security Cloud OpenAPI specification, v2025,
createWorkflow, https://github.com/sailpoint-oss/api-specs/blob/main/idn/v2025/paths/workflows/workflows.yaml (read November 1, 2026).↩︎ServiceNow, “Scripted REST APIs” (Zurich API reference), https://www.servicenow.com/docs/bundle/zurich-api-reference/page/integrate/custom-web-services/concept/c_CustomWebServices.html (read November 1, 2026).↩︎
National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, CA-7, CM-3, CM-3(2), CM-4(2), SA-11 and SI-4), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (read November 1, 2026).↩︎
National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, CA-7, CM-3, CM-3(2), CM-4(2), SA-11 and SI-4), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (read November 1, 2026).↩︎
National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, CA-7, CM-3, CM-3(2), CM-4(2), SA-11 and SI-4), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (read November 1, 2026).↩︎
National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, CA-7, CM-3, CM-3(2), CM-4(2), SA-11 and SI-4), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (read November 1, 2026).↩︎
National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, CA-7, CM-3, CM-3(2), CM-4(2), SA-11 and SI-4), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (read November 1, 2026).↩︎
National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, CA-7, CM-3, CM-3(2), CM-4(2), SA-11 and SI-4), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (read November 1, 2026).↩︎
FedRAMP, “Key Security Indicators,” FedRAMP Consolidated Rules for 2026, indicator titles and “Related SP 800-53 Controls,” https://www.fedramp.gov/2026/reference/key-security-indicators/ (read November 2, 2026).↩︎




