Built 2026-08-29 from SAP's own primary documentation, PCAOB standards, COSO,
ISACA and SEC filings. Companion to COMPETITOR_PATHLOCK.md,
which measures a competitor against this yardstick.
Labels: verified (primary source, quoted) · asserted (credible secondary) ·
inferred (synthesis, evidence named).
Primary sources: SAP Access Control 12.0 SP31 Application Help and
Administrator Guide (SAP's PDF builds — help.sap.com HTML is a JS shell
that returns only a title to any fetcher) · PCAOB AS 2201, AS 1105, Staff
Audit Practice Alert No. 11 · COSO 2013 · ISACA Glossary · SEC EDGAR.
verified — SAP AC 12.0 SP31 §8.2.1.1, Caution:
"If you create organizational rules incorrectly, you could potentially filter out too much. By filtering out too much, you cannot identify possible control concerns with your access. From a control perspective, it is much better to over-report (causing false positives) rather than under-report (causing false negatives)."
Every competitor sells noise reduction. Pathlock's stated differentiator is "reduce false positives". SAP explicitly warns against optimising that axis.
inferred — the defensible claim is not fewer findings. It is precision with
recall: remove a flagged conflict only when the engine can name the org field
and value that makes execution impossible, and say so on the row. That is a
different product from one that quietly suppresses.
verified — SAP ships three reports that measure what a ruleset cannot see:
| Report | SAP's description |
|---|---|
| List Actions in Roles But Not in Rules | "lists all the actions that are in roles but are not part of the rule library" |
| List Permissions in Roles But Not in Rules | "lists all the permissions that are in roles but are not part of the rule library" |
| Embedded Action Calls in Programs of SAP Systems | "identifies embedded transaction calls in custom programs" |
SAP shipping these is SAP conceding the gap exists.
inferred — this is the answer to "36 risks against their 207." Breadth is
a number anyone can inflate; coverage is a number almost nobody computes and
no vendor publishes. A conflict report is only as trustworthy as the fraction
of the estate its ruleset can see. Computing that fraction — and printing it
beside every SoD result — is a differentiator available to us today, on data we
already parse (AGR_1251, role_auth_values, custom_code_scan).
verified — the standards chain that compels validation exists:
- AS 1105.10 — the auditor must "test the accuracy and completeness of the information, or test the controls over" it, and "evaluate whether the information is sufficiently precise and detailed."
- PCAOB SAPA 11 p.26 — a named inspection failure is not testing "the logic of the queries (or parameters) used to extract data".
- AS 2201 ¶.B30 — benchmarking is unavailable for controls dependent on "the related files, tables, data, and parameters."
inferred — the SoD ruleset is the query logic and the parameters. So
AS 1105.10 obliges someone to validate it, ¶.B30 forbids benchmarking it away
— and no published source describes how anyone actually does this. SAP itself
calls rule sets "arbitrary definitions" (§8.2.2.1).
That is the most defensible place to claim differentiated methodology: not a bigger rulebook, but a rulebook that carries its own evidence.
verified — SAP's definition (§8.3): "An access risk is one or more actions or
permissions that, when available to a single user (or single role, profile, or
HR Object), creates the potential for fraud or unintentional errors."
Three risk types, SAP's words: Segregation of Duties (≥2 functions) · Critical Action (1 function) · Critical Permission ("certain permissions (authorization objects) that are considered critical on their own").
Rule Set (container; risks assigned TO rule sets, many-to-many)
└─ Access Risk 4-char ID · Type · Level · Business Process · Owner
├─ Function ─ Action = tcode / Fiori app / OData service
│ └─ Permission = auth object + field + value,
│ ATTACHED TO AN ACTION
└─ Function (≥2 for SoD)
↓ GRAC_GENERATE_RULES
Generated Rules — the Cartesian product actually evaluated
The correction: Permission is not a sibling of Action. It hangs off one.
verified — §8.2.2.2.1.1: "The Permissions screen appears, displaying the
permissions (authorization objects) for all of the actions that have been
added to the function." And the Caution: "This screen allows you to
further restrict the access defined in the permission object. You cannot
expand the access or reconfigure the permission object."
inferred — permission maintenance is monotonically narrowing, and the
proposal set comes from SU24. Therefore the ruleset's permission layer is
downstream of SU24 quality: a badly maintained SU24 silently caps what a ruleset
can even express. Our own ruleset, which states object/field/value directly and
does not inherit from SU24, is structurally free of that ceiling — worth
saying out loud.
verified — SAP's own example (§8.2.1.3): a risk with functions of 21, 46 and
34 actions across three systems "translates to 98,532 distinct rules
(21x46x34x3)". Maximum 1,679,615 rules per risk — which is 36⁴−1, a 4-char
base-36 ID space, and is reachable by three functions of ~120 actions on one
system.
verified — a defect in SAP's own documentation: the preceding example
says two functions × five actions × two systems = "20 distinct rules". Under
SAP's own multiplicative model that is 50. Build against the model, not the 20.
verified — the objects behind PO maintenance: M_BEST_BSA (document type,
BSART) · M_BEST_EKG (purchasing group) · M_BEST_EKO (purchasing org) ·
M_BEST_WRK (plant, WERKS) · M_BEST_LGO (storage location). ACTVT:
01 create · 02 change · 03 display · 06 delete. S_TCODE is only a
start authorization.
Risk: Create PO ∧ Post Goods Receipt (ME21N ∧ MIGO).
| User | S_TCODE |
M_BEST_WRK |
GR plant | Verdict |
|---|---|---|---|---|
| A | both | ACTVT=03, WERKS=* |
01/1000 |
False positive — display only |
| B | both | ACTVT=01, WERKS=1000 |
01/2000 |
False positive — disjoint plants |
| C | both | ACTVT=01, WERKS=1000 |
01/1000 |
True positive |
Action-level analysis flags all three identically. B is the org-level case: the fraud path needs the same plant, and "remediating" B strips access that was never a risk.
Two failure directions, and the second is worse. False positives come from
transactions present without authority. False negatives come from authority
present without the transaction — reachable via SE16/SM30/SE38, a custom
Z transaction, a parameter transaction, a Fiori app, an OData service, a
background job step under another user, or an RFC destination with stored
credentials. None appears in S_TCODE.
verified — SAP's scope table: Fiori Catalog [FCAT] has no standard
ruleset. And asserted from an SAP-authored blog, the critical asymmetry:
"The SAP Fiori APP[FAPP] will not have any permissions. The permissions are associated with Service[SVC]. If you only add SAP Fiori APP in the action's tabs, the permission tab will be empty."
- FAPP vs FAPP → action-level risk only
- SVC vs SVC → action and permission level
- FAPP+SVC → granular, but "you need to manually copy and paste the permission of the Service"
inferred — Fiori-app-level SoD reproduces transaction-code-level SoD's
weakness, one abstraction layer up. Doing it properly means resolving
app → OData service → back-end authorization objects. Start authorization
objects: S_SERVICE (field SRV_NAME) for OData, S_START for Web Dynpro.
Tile visibility ≠ authorization — removing a tile leaves the endpoint callable.
inferred — a GRC 12.0 install that never activated GRAC_RA_RULESET_S4HANA_ALL
and never added the second connector reports SoD on S/4HANA while blind to
every Fiori-initiated action. One of the most common causes of a green report.
Implemented — 2026-08-29. This was our own blind spot too, and closing it took two pieces that are easy to confuse:
- Measuring the gap —
modules/ruleset_coverage.py, check SODCOV-006, reports what share of an estate's published apps and services the ruleset can name, separately from the transaction figure. Averaging them would let 93% of transactions conceal 0% of Fiori behind one number, and on an S/4HANA estate the 0% is the finding. On our own sample the ruleset names none of 5 apps and 13 services, and names neitherS_SERVICEnorS_STARTin any predicate — so it could not express a permission-level Fiori rule even where an app was listed. - Closing it —
modules/access_risk_analysis.pyresolvesrole → tile → app → serviceandrole → S_SERVICE → service, so a rule naming a Fiori app or an OData service now fires. No app is mapped to an "equivalent" transaction: that table is in no export here, and guessing it would put users into conflicts on the strength of an inference.
The two wildcards are kept disjoint. S_TCODE '*' confers no launchpad tile and
no service grant; the first version of the gate let it short-circuit, which made
every super-user an offender on every Fiori rule — on the sample estate, two
users holding zero tiles between them. A negative control caught it, not review.
Per the note above that tile visibility is not authorization, a tile is counted as reach. That can over-report where the tile and authorization exports disagree, which is the direction §1.1 says to err in: an over-report is visible and can be cleared, an under-report is indistinguishable from a clean result.
verified — S_TABU_DIS (fields DICBERCLS, ACTVT; groups in TDDAT) and
S_TABU_NAM (fields TABLE, ACTVT). SAP Note 1541577 is titled "Impact
of S_TABU_NAM in Risk Analysis and Remediation" — SAP conceding the object
breaks existing rulesets.
asserted (two independent practitioners) — the two objects are evaluated as a
logical OR. inferred: a ruleset modelling only one misses everything
granted via the other.
Worked bypass: the rule Maintain Vendor ∧ Post Invoice is FK02 ∧ FB60.
A user with SE16 + S_TABU_DIS ACTVT=02, DICBERCLS=* edits LFA1/LFBK
directly — the "maintain vendor" half without ever holding FK01/FK02.
The rule never fires.
asserted — &NC& is the fallback group for unclassified tables, so every
custom Z* table lands there; one DICBERCLS=&NC& grant exposes the whole
custom data model. Group MA alone holds "more than six hundred tables."
verified — "By default, a job runs under the authorizations of the user who
scheduled it", but S_BTCH_NAM (field BTCUNAME) lets a step run as someone
else. SAP: "You should give this authorization to the batch administrator
only."
inferred — a clerk with FB60, SM36 and S_BTCH_NAM BTCUNAME=BATCH_FI
(where BATCH_FI holds F110) posts the invoice and schedules the payment.
User-level SoD sees the two halves in two identities and reports nothing.
verified — S_RFCACL is deliberately excluded from SAP_ALL, and SAP
warns "individual users might be misused as anonymous users to perform actions
in the target system." An SM59 destination with stored credentials is a
privilege capsule: the caller needs only S_RFC.
verified — reference users are a user-master attribute, CUA-propagated.
inferred — their authorizations are not role assignments, so any engine
building its picture from AGR_USERS alone misses them entirely.
verified — GRC ships User / Role / Profile / HR Object level as separate
reports, crossed with Action / Permission / Critical Action / Critical
Permission / Critical Role-Profile, three views and four formats.
inferred — they cannot agree by construction. Role level cannot see a
conflict formed by combining two clean roles, which is the normal shape of a
real violation. It is favoured because it is cheap and produces smaller
numbers — which is also why it is the level most often quoted to auditors.
Reporting one level and calling it "the SoD position" is the failure.
inferred — mitigation at role level masks user-level conflicts: attach one
control to a role and every current and future holder reads as mitigated.
verified — nearly every SAP AC report carries this note verbatim:
"Report Details — The application displays only objects that you are authorized to see. For example… you may only see the data related to North America, if you are only authorized to see North America."
inferred — a SoD conflict report is silently scoped by the running user's
own authorizations. Two people run "the same report" and get different
populations, and neither report says so. In SAP AC, who ran it is a
completeness attribute, not merely provenance. This is a first-order IPE
finding, stated by the vendor.
verified — SAP: "It is therefore the programmer's responsibility to check
that every user who can call the program is also authorized" and "in ABAP
programs, SQL Statements do not trigger authorization checks in the database
system. Thus, Open SQL and Native SQL statements allow unrestricted access to
all database tables."
inferred — ZPO_CREATE calling BAPI_PO_CREATE1 with no AUTHORITY-CHECK
defeats both the action-level rule (wrong tcode) and the permission-level rule
(object never checked).
verified — SU24 maintains USOBT_C/USOBX_C; "Only these customer default
authorization values are applied when PFCG is used." And
inferred — check indicator set to "no check" → false negative while the
transaction still works. Over-broad proposals (* in ACTVT or an org field) →
false positives and a real unintended capability. SU24 unmaintained for Z*
→ the same custom transaction carries different object sets in different roles.
Every rule above answers could this user do both halves. Change documents answer part of a stronger question — did they — because CDHDR records which user changed which object under which transaction, on which date.
The market's organising use of that data is de-prioritisation: a conflict nobody has exercised is ranked down. We do the opposite half only. Evidence of performance raises severity by one level; absence of evidence lowers nothing.
The reason is that this log cannot support the negative:
- change documents exist only for objects with change logging switched on, so the record is partial by construction;
- a display action leaves no change document at all, so any risk with a read half can never be fully evidenced here;
- the log covers a window, and a conflict exercised before it began looks identical to one never exercised.
Read against §1.1 — SAP's own guidance that it is better to over-report and
clear than to under-report and never see — de-prioritising on this evidence is
optimising the axis SAP warns against, and its quiet results are
indistinguishable from unasked questions. So the three states are kept apart in
the output: realised, no_evidence_in_window, unmeasured.
The sharper finding runs the other way. A user can appear in the log having
performed both halves while the authorization export does not show them holding
either. On our own sample, LWANG is recorded running SU01 and PFCG — both
halves of BASIS-01 — with no AGR_1251 rows at all. Two readings fit, and
ARA-DIDDO-001 asserts neither: the role export may be incomplete, or the
access may have been withdrawn after use. Both matter. The first means every
clean SoD answer about that user was computed from data missing the part that
would have produced the conflict; the second is the correct outcome of an access
review, and a point-in-time snapshot has no other way to show access that once
existed. What is asserted is only what is checkable — the log records it, the
authorization data does not explain it, and a segregation result for that user
cannot be relied on until one of the two readings is confirmed.
The Fiori half of this is specified, not built, and the reason is data.
§4.1 closed the can-do gap for Fiori, but the equivalent did-do evidence has
nowhere to come from: the launchpad usage export we currently take
(fiori_app_usage) is aggregate — APP_ID, TITLE, LAUNCH_COUNT, LAST_LAUNCH, CATALOG — with no user column. Aggregate launch counts cannot evidence that
a particular identity exercised both halves of a conflict, and no amount of
inference recovers the missing dimension.
What would close it is a per-user export, from /UI2/APP_USAGE or the
equivalent launchpad usage table, shaped:
USERNAME, APP_ID, ODATA_SERVICE, LAUNCH_COUNT, FIRST_LAUNCH, LAST_LAUNCH
With that, _execution_index gains a second source and every rule in §4.8
applies unchanged — including the asymmetry, and including ARA-DIDDO-001,
which would then also catch a user who exercised a conflict entirely through
Fiori. Until the export exists, execution evidence covers the transaction
surface only, and that limit is stated in the finding rather than left for a
reader to discover.
verified — SAP's distinguishing sentence: "This is different from segregation
of duties risks in that the person only needs to have access to a single
function."
| SoD | Critical access | |
|---|---|---|
| Predicate | ∃ a ∈ Fn₁, b ∈ Fn₂ with intersecting org scope | ∃ a ∈ Fn₁ |
| Remediation | Split duties | Remove, or wrap in monitored emergency access |
| Mitigation | Detective control over the specific pair | Usually not mitigable |
| Org rules | Applicable, often necessary | Mostly inapplicable |
inferred — debug-replace, SAP_ALL and generic table write grant both
halves of every conflict simultaneously, without appearing in S_TCODE for
any business transaction. Modelling them as SoD legs understates them by an
order of magnitude.
verified — SAP's own critical-access catalogue (EarlyWatch Alert – Security):
SAP_ALL holders · S_DEVELOP with ACTVT=02 and OBJTYPE=DEBUG ·
users who can change/display all tables via SE16/SE16N/SE17 "(or any
corresponding parameter transaction)" · users who can start all reports · RFC
administrators · default passwords on standard users.
verified — debug-replace semantics: S_DEVELOP + OBJTYPE=DEBUG +
ACTVT=02 = "This activity allows editing values of variables in the ABAP
debugger." inferred — the /h + sy-subrc flip happens after the
AUTHORITY-CHECK has run, so no authorization object, org level or SoD rule can
constrain it.
verified — Critical Roles and Critical Profiles are a third mechanism
because SAP_ALL is a profile, not a role. Analysis driven off AGR_USERS
misses it entirely.
verified — PCAOB AS 2201 ¶.68: "To have a mitigating effect, the
compensating control should operate at a level of precision that would prevent
or detect a misstatement that could be material." Exhaustive term counts over
AS 2201: "compensating" 2 (both in ¶.68), "mitigating" 1,
"complementary" 0.
verified — the ISACA Glossary has no entry for "mitigating control."
COSO's phrase is "alternative control activities."
| Term | Home | Denotes |
|---|---|---|
| Compensating control | PCAOB, ISACA | A control credited in the severity evaluation; must be tested |
| Mitigating control | SAP GRC product vocabulary | A row in a table linked to a risk ID and an assignment |
| Alternative control activity | COSO | What management designs when SoD is impractical |
A SAP "mitigating control" is a row; a "compensating control" is an audit conclusion. Nothing makes the former produce the latter.
verified — SAP's model: controls assign to users, roles, profiles or HR
objects, with Valid From / Valid To, monitors and approvers. Six
assignment objects, including User/Role for Organization Rules.
verified — GRAC_MITIGATE appears zero times in SAP's 274-page
Application Help. Do not cite it as a transaction code.
verified — the Invalid Mitigation Monitors checkbox is off by default:
"If you do not select this checkbox, the report shows the Control ID Status. It
will not show the Monitor Status." A shop running these reports without ticking
it sees green Control IDs while the monitors are expired, deleted or locked.
verified — the rubber stamp ships as configuration: MSMP settings
Approve Despite Risk and Risk Analysis Mandatory.
verified — the circularity, from Blount International's FY2014 10-K:
"Many of the Company's mitigating and compensating controls… rely on
information produced and maintained within the SAP system, and therefore such
controls were also ineffective due to the pervasive impact of ineffective
ITGCs." A compensating control whose evidence is an SAP report inherits that
report's IPE problem.
verified — AS 2201 ¶.64, the single most important sentence for this work:
"The severity of a deficiency does not depend on whether a misstatement actually has occurred but rather on whether there is a reasonable possibility that the company's controls will fail to prevent or detect a misstatement."
verified — the empirical proof, Paragon Offshore Q1 2015 10-Q: SAP SoD
material weakness where "This control deficiency did not result in any
adjustments to the consolidated financial statements… However, the deficiency
could result in misstatements."
verified — AS 2201 ¶.69's four indicators of material weakness are: senior-
management fraud, restatement, auditor-identified material misstatement,
ineffective audit-committee oversight. None is an SoD conflict. SoD reaches
material weakness through ¶.63 likelihood/magnitude and the ¶.70 prudent-official
test — not the indicator list. Do not claim otherwise.
verified — sign-off is not evidence, stated in 2013 and restated verbatim in
December 2023: "Verifying that a review was signed off provides little or no
evidence by itself about the control's effectiveness."
Evidence an auditor asks for maps to named SAP artefacts — Access Rule Summary and Detail Reports, User to Role Relationship, the four Risk Violation reports with their flag settings recorded, Mitigated Object Report, User/Role Level Invalid Mitigations with the monitor box ticked, UAR and SoD Review History, Requests with Conflicts and Mitigations, the Firefighter log set, PFCG Change History, and the three coverage-gap reports in §1.2.
- SAP publishes no ruleset counts. Not in the Application Help, the Administrator Guide, the product page or any reachable KBA. The widely-quoted 206 risks / 198 functions / 14 processes comes from one practitioner analysis and could not be corroborated. Do not present it as an SAP figure.
- SAP Note bodies are login-gated. Notes cited by SAP's own public docs and therefore verified-by-citation: 65968, 101146, 987031, 1481950, 1541577, 2655122, 2975653, 3226223, 3430610, 3469294, 3458015.
- GRC ruleset table names (
GRACSODRISK,GRACFUNC,GRACFUNCACT,GRACFUNCPRM) are consistent across practitioner sources but unconfirmed from an SAP-hosted page. - No published guidance exists on SoD ruleset validation or benchmarking, or on whether external auditors accept, test or substitute a client's ruleset. The standards logic compelling validation is established; actual practice is undocumented. This is the gap §1.3 proposes to occupy.
- No formal standards distinction between "compensating" and "mitigating" — confirmed absent from AS 2201, COSO's Executive Summary and the ISACA glossary. COBIT 2019 and ISACA's ITAF were not searched.
- No delivered SAP SoD content for S/4HANA Cloud Public Edition.
GRAC_RA_RULESET_S4HANA_ALLis explicitly on-premise. Do not assert one exists. - ISACA/IIA audit programs, Big Four methodology papers and academic sources were largely unreachable; the web-search budget was exhausted. A genuine gap, not a null result.