Skip to content

Commit d596c8d

Browse files
Krishcalinclaude
andcommitted
SODCOV-000: one sentence saying how far the SoD result can be believed
This module emits up to ten separate findings about the trustworthiness of a segregation result - coverage on two surfaces, whether a wildcard makes the measurement unbounded, rules that can never fire, a supplied ruleset that is broken or silently overriding ours. An auditor does not want ten. They want one sentence they can put in a workpaper, and a reader made to assemble that sentence themselves will assemble it wrongly or not at all. On the sample estate it reads: This segregation-of-duties result is UNBOUNDED - 2 stated limits: at least one role grants every transaction, so the estate reaches more than was measured and the percentages are a floor rather than a figure; the ruleset names 0% of this estate's Fiori surface, so a conflict reachable only through an app is outside what any result here can find. FOUR VERDICTS not measured the authorization export was not supplied, so no statement about breadth is possible and a percentage would be invented unbounded a wildcard grant means every figure is a floor partial a surface is named too thinly to carry the result usable no limit was found that would qualify it The last one says so EXPLICITLY rather than staying silent. "We looked and found no limit" is a different claim from "we did not look", and keeping those apart is what this module is for. TWO RULES, EACH PINNED BY A TEST The verdict is the WEAKEST dimension, never an average. Averaging 93% of transactions against 0% of Fiori produces a comfortable 47% that describes neither surface, and a summary which buries its worst input is precisely the failure the ten checks beneath it exist to report. It is DERIVED from the findings already emitted, not recomputed. `derived_from` names its sources. A summary that recalculates can disagree with its own detail, and then the report contradicts itself in front of the reader least equipped to tell which half to believe. The limits are ordered worst-first, because the remediation says to work them in order and the first is the one that decides what the report is worth. THE GUARD FROM THE LAST COMMIT EARNED ITS KEEP IMMEDIATELY Adding this check grew the catalogue from 790 to 791, and tests/test_firing_reference.py failed because docs/CHECK_FIRING.md no longer reconciled. That is the CI gate added one commit ago catching a real staleness on the very next change, which is about the best validation it could get. Regenerated: 704 of 791 proven to fire. Full suite green: 4456 passed. Smoke run 409 findings. Nine new tests, a knowledge-base entry, references at 446 literal / 791 total. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent fae5a6d commit d596c8d

7 files changed

Lines changed: 267 additions & 22 deletions

File tree

data/finding_details.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2329,6 +2329,11 @@
23292329
"module": "s4_business_authz",
23302330
"risk": "On SAP BTP, application authorizations are packaged as role collections, and trust configuration can map a role collection to an IdP group. Mapping a role collection to the 'Default' group (or a wildcard / all-authenticated value) turns it into a BIRTHRIGHT assignment: every user who successfully authenticates through the corporate IdP receives that collection automatically, with no explicit provisioning. If the collection carries only genuinely universal, low-privilege access such as a basic launchpad viewer, this is acceptable; if it carries anything beyond baseline, the entire authenticated population is silently over-provisioned. Birthright grants are easy to overlook during recertification because they are not attached to individual users, so the access is invisible in per-user reviews yet fully effective at runtime. Attack scenario: an attacker who obtains any valid federated identity (a compromised SSO account, or an over-broad IdP that lets many people authenticate) immediately inherits whatever the default-mapped collection grants, potentially reaching applications, APIs, or data that should require explicit entitlement, and without any access request to trip an alert. The insider risk is parallel: any employee can reach the function without ever being formally granted it. Impact includes violation of least privilege and need-to-know, GDPR over-exposure if the collection reaches PII, and an audit finding that access is not individually authorized or recertifiable. A real incident looks like users exercising an app they were never explicitly provisioned for, traced back to a default-group mapping."
23312331
},
2332+
"SODCOV-000": {
2333+
"mitigation": "1. Quote the verdict, not the conflict count, when recording what this analysis covered. A conflict count on its own describes the ruleset rather than the estate, and is the figure most often carried into a report without the scope that makes it meaningful.\n2. Work the stated limits in the order given. They are ordered by how much each undermines the result, so the first is the one that decides what the rest of the report is worth.\n3. Where a limit cannot be closed before the deadline, record it as scope rather than treating the areas it affects as assessed. An unasked question and a good answer look identical on a page that does not distinguish them, and only one of them is evidence.\n4. Do not average the underlying measures to produce a friendlier number. Broad transaction coverage against a Fiori surface the ruleset cannot see gives a figure that describes neither surface.\n5. Re-run after closing any limit. The verdict is derived from the checks beneath it, so it moves on its own once the underlying measure does - it does not need editing.",
2334+
"module": "ruleset_coverage",
2335+
"risk": "A segregation-of-duties report is read as a statement about an estate, and it is only ever a statement about the part of that estate the ruleset could see. This finding is the one sentence that says which. Without it a reader has to assemble the qualification themselves from up to ten separate coverage findings - transaction and Fiori breadth, whether a wildcard grant makes the measurement unbounded, rules that can never fire because they name authorization objects this release does not define, a supplied ruleset that is broken or silently replacing rules - and a reader made to do that will either do it wrongly or not at all. The verdict is deliberately the weakest of the measures beneath it rather than an average. Averaging broad transaction coverage against a Fiori surface the ruleset does not name at all produces a comfortable middle number that describes neither, and a summary which buries its worst input is precisely the failure the checks beneath it exist to report. It is also derived from those findings rather than recalculated, so the summary and the detail cannot disagree - a report that contradicts itself does so in front of the person least equipped to tell which half to believe."
2336+
},
23322337
"SODCOV-001": {
23332338
"mitigation": "1. Open the unseen-transaction list on this finding — it names every standard transaction granted somewhere in the estate that no rule refers to.\n2. Sort them into three piles with somebody who knows the business processes: irrelevant to segregation of duties (most display transactions), a genuine gap that belongs in the ruleset, or a transaction whose risk is already covered by a rule naming a different code for the same capability.\n3. Add the second pile to the ruleset, each with the authorization object, field and values that make the action meaningful rather than the transaction code alone.\n4. Re-run the scan and confirm the coverage figure moved.\n5. Record a decision for the first pile. Doing nothing is a legitimate answer; leaving it unexamined is not, and the point of publishing this number is to make the difference visible.",
23342339
"module": "ruleset_coverage",

docs/ARCHITECTURE.html

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,7 @@ <h3 class="ch" id="ch05"><span class="cnum">Chapter 05</span>What MonitorRisk do
689689
<p class="lede">If you read nothing else, read this. Everything after it is elaboration.</p>
690690

691691
<h4>The idea in three sentences</h4>
692-
<p>Somebody takes a set of photographs of how the SAP system is currently configured — lists of users, roles, settings, connections, and so on — and saves them as ordinary files. MonitorRisk reads those files on a computer that has no connection to SAP at all, and applies <strong>790 checks</strong> written by people who know SAP well. It produces a report that says what is wrong, how bad it is, what to do about it, who is able to do it, and what it is worth in money.</p>
692+
<p>Somebody takes a set of photographs of how the SAP system is currently configured — lists of users, roles, settings, connections, and so on — and saves them as ordinary files. MonitorRisk reads those files on a computer that has no connection to SAP at all, and applies <strong>791 checks</strong> written by people who know SAP well. It produces a report that says what is wrong, how bad it is, what to do about it, who is able to do it, and what it is worth in money.</p>
693693

694694
<figure>
695695
<div class="cap"><span class="fid">FIG. 5.1</span><span class="ftx">The whole idea</span></div>
@@ -711,7 +711,7 @@ <h4>The idea in three sentences</h4>
711711

712712
<rect class="box" x="664" y="34" width="250" height="118" rx="4"/>
713713
<text class="sv-h" x="686" y="74">MonitorRisk</text>
714-
<text class="sv-ms" x="686" y="104">790 checks</text>
714+
<text class="sv-ms" x="686" y="104">791 checks</text>
715715
<text class="sv-ms" x="686" y="128">36 modules</text>
716716

717717
<path class="ln" d="M914 93 H 984" marker-end="url(#ar)"/>
@@ -729,7 +729,7 @@ <h4>The seven questions it answers</h4>
729729

730730
<div class="tw"><table>
731731
<tr><th class="k" style="width:4%"></th><th style="width:26%">Question</th><th>How it is answered</th></tr>
732-
<tr><td class="k">1</td><td><strong>What is wrong?</strong></td><td>790 checks across 36 subject areas produce a list of findings, each naming the specific accounts, roles, settings or connections involved.</td></tr>
732+
<tr><td class="k">1</td><td><strong>What is wrong?</strong></td><td>791 checks across 36 subject areas produce a list of findings, each naming the specific accounts, roles, settings or connections involved.</td></tr>
733733
<tr><td class="k">2</td><td><strong>How bad is each one?</strong></td><td>A severity from Critical to Low, assigned by the check itself according to what an attacker could achieve.</td></tr>
734734
<tr><td class="k">3</td><td><strong>What should we do first?</strong></td><td>A priority from P1 to P4 that combines severity with whether the flaw is known to be exploited, how exposed it is, and how much privilege it confers.</td></tr>
735735
<tr><td class="k">4</td><td><strong>What exactly do we do?</strong></td><td>A numbered remediation procedure naming the specific SAP transaction, parameter or table to change, how to verify it worked, and what to be careful of.</td></tr>
@@ -1175,7 +1175,7 @@ <h4>Route C — a source-code export</h4>
11751175
<p>When the customer supplies one, a further module unpacks it and analyses the code itself, applying <strong>136 rules</strong> for the classic weaknesses described in chapter 3, family 6.</p>
11761176

11771177
<h4>Why convergence matters more than it sounds</h4>
1178-
<p>Because all three routes produce the same shape of information, none of the 790 checks contains any logic about provenance. There is no “if this came from the collector, be more careful” branch anywhere. Adding Route B required no changes to the checking logic at all, and required no new tests of the checks, because the checks could not tell the difference.</p>
1178+
<p>Because all three routes produce the same shape of information, none of the 791 checks contains any logic about provenance. There is no “if this came from the collector, be more careful” branch anywhere. Adding Route B required no changes to the checking logic at all, and required no new tests of the checks, because the checks could not tell the difference.</p>
11791179
<p>This is a general principle worth naming, since it recurs later in the book: <strong>push variation to the edges</strong>. Let the outside of the system deal with the messy diversity of the real world, and let the middle see one clean, uniform thing.</p>
11801180

11811181
<div class="call takeaway">
@@ -1374,7 +1374,7 @@ <h4>Line by line</h4>
13741374
</table></div>
13751375

13761376
<h4>Why 685 of these are hard</h4>
1377-
<p>If one check is fifteen lines, 790 checks are not simply nine thousand lines of the same thing. The difficulty is elsewhere:</p>
1377+
<p>If one check is fifteen lines, 791 checks are not simply nine thousand lines of the same thing. The difficulty is elsewhere:</p>
13781378
<ul>
13791379
<li><strong>Knowing what to check.</strong> Most of the value is the accumulated knowledge of which settings matter and why. A cryptic permission with a wildcard value can mean “may impersonate any user from a trusted system”. Knowing that is decades of SAP experience, not programming.</li>
13801380
<li><strong>Knowing when not to fire.</strong> A check that reports something harmless trains the reader to ignore it. The single most common cause of a security tool being abandoned is false alarms.</li>
@@ -1490,7 +1490,7 @@ <h4>What is deliberately not in the record</h4>
14901490
<p>The long explanation of why something is dangerous, and the step-by-step instructions for fixing it, are not stored in the finding. They live in a separate knowledge base, looked up by check identifier when a report is written (chapter <a href="#ch20">20</a>). Two reasons:</p>
14911491
<ul>
14921492
<li>The same finding may appear five hundred times in one scan — once per affected role. Storing a page of narrative five hundred times would be wasteful and would make the record awkward to move around.</li>
1493-
<li>Improving the guidance for 790 checks becomes an edit to a content file rather than a change to thirty programs, which means it can be done by the person who knows SAP best rather than the person who writes code best.</li>
1493+
<li>Improving the guidance for 791 checks becomes an edit to a content file rather than a change to thirty programs, which means it can be done by the person who knows SAP best rather than the person who writes code best.</li>
14941494
</ul>
14951495

14961496
<div class="call takeaway">
@@ -1620,7 +1620,7 @@ <h4>Relationships, not just values</h4>
16201620
<section class="part" id="part5">
16211621
<div class="pnum">Part five</div>
16221622
<h2 class="parth">Making sense of the results</h2>
1623-
<p class="pintro">790 checks against a real estate can produce thousands of findings. An undifferentiated list of thousands of problems is not information; it is a way of guaranteeing that nothing gets fixed. This part is about turning the list into decisions.</p>
1623+
<p class="pintro">791 checks against a real estate can produce thousands of findings. An undifferentiated list of thousands of problems is not information; it is a way of guaranteeing that nothing gets fixed. This part is about turning the list into decisions.</p>
16241624
<ol class="plist">
16251625
<li><b>19</b><a href="#ch19">What to fix first</a></li>
16261626
<li><b>20</b><a href="#ch20">Telling people what to actually do</a></li>
@@ -2583,7 +2583,7 @@ <h3 class="ch" id="appB"><span class="cnum">Appendix B</span>The thirty modules
25832583

25842584
<div class="call amber">
25852585
<span class="k">One honest note about counting</span>
2586-
<p>The headline “790 checks” needs a footnote. <strong>445</strong> check identifiers are written out individually in the source. A further <strong>345</strong> come from <strong>six families generated at run time</strong> from shipped rule lists — one check per Web Dispatcher rule (14), one per technical parameter (79), one per code rule (136), one per duty-separation risk (99), one per imported code family (10), one per conflicting-duty pair (7). Both numbers are true; stating both is more useful than picking whichever is larger.</p>
2586+
<p>The headline “791 checks” needs a footnote. <strong>446</strong> check identifiers are written out individually in the source. A further <strong>345</strong> come from <strong>six families generated at run time</strong> from shipped rule lists — one check per Web Dispatcher rule (14), one per technical parameter (79), one per code rule (136), one per duty-separation risk (99), one per imported code family (10), one per conflicting-duty pair (7). Both numbers are true; stating both is more useful than picking whichever is larger.</p>
25872587
<p>These figures are derived from the code rather than typed here, and the test suite fails if this page and the source ever disagree.</p>
25882588
</div>
25892589

@@ -2714,7 +2714,7 @@ <h4>E.3 — Refusing to print a number</h4>
27142714

27152715
<p style="font-size:15px;color:var(--ink2);max-width:80ch"><strong>Provenance and caveat.</strong> This book was written from the repository’s documentation and its top-level program, at <a href="https://github.com/Krishcalin/SAP-S4HANA-RISE-Security-Scanner">github.com/Krishcalin/SAP-S4HANA-RISE-Security-Scanner</a>. Module names, the command-line surface, the pipeline order, the corpus-split rule, the gate rules and the deployment-mode behaviour are taken from the source and documentation. Descriptions of the internal structure of individual inspectors, the exact shape of a finding record, and the illustrative code in Appendix E are reconstructions written to be faithful in substance rather than literal in syntax; verify against the source before quoting them. All diagrams are architectural rather than exhaustive.</p>
27162716

2717-
<p style="font-size:15px;color:var(--ink2);max-width:80ch">The counts this document states — 790 checks, 445 written as literals, 345 generated at run time from 6 rule families, 38 modules, 136 custom-code rules, 27 duty-separation risks, 78 profile parameters and 135 logical sources — are derived from the code by <code>tests/test_architecture_doc.py</code>, which fails the build if this page and the source disagree. Edition 1.1 corrected three figures that had drifted from the source.</p>
2717+
<p style="font-size:15px;color:var(--ink2);max-width:80ch">The counts this document states — 791 checks, 446 written as literals, 345 generated at run time from 6 rule families, 38 modules, 136 custom-code rules, 27 duty-separation risks, 78 profile parameters and 135 logical sources — are derived from the code by <code>tests/test_architecture_doc.py</code>, which fails the build if this page and the source disagree. Edition 1.1 corrected three figures that had drifted from the source.</p>
27182718

27192719
<p style="font-size:15px;color:var(--ink2)"><strong>Confidential.</strong> This document is not for publication. It describes the internal design, the current limitations and the forward direction of a commercial product; treat it as you would any other confidential product document.</p>
27202720

0 commit comments

Comments
 (0)