Skip to content

Commit 5f1e5b9

Browse files
Krishcalinclaude
andcommitted
Make the ABAP confidence grade mean something, using what the callers pass
I set out to add interprocedural taint and measured first. The engine seeds every inbound parameter of every FORM, METHOD and FUNCTION as tainted, so across every ABAP fixture in this repository: sink-carrying findings: 11 {'confirmed': 11} Eleven of eleven confirmed, none tentative. A grade with one value is decoration, and the label it prints — "tainted input reaches this sink" — was going on a subroutine whose only caller passes the string literal 'SFLIGHT'. Safe to change, because the two grades behave identically downstream: fair_adapter._is_unevidenced treats only `pattern-only` as unevidenced, and release_gate's default policy blocks on `confirmed` and `tentative` alike. Nothing leaves FAIR pricing and nothing stops holding a build. Only the claim changes, and the finding is still reported either way — the downgrade-never-hide contract in `_refine` is untouched. modules/abap_callgraph.py binds actuals to formals across PERFORM, CALL FUNCTION, CALL METHOD and obj->meth( ), and answers whether anything visible hands a parameter something the caller controls. Across the fixtures: 12 confirmed and 2 tentative where it was 11 and 0, and four traces that now open with the call rather than stopping at the procedure header — a developer is pointed at `PERFORM run_query USING p_carr` instead of being told `iv_carrid` is tainted and left to find the caller themselves. WHAT IS NOT CLAIMED. This is not interprocedural data-flow analysis, and docs/CVA_MERGE_PLAN.md's "not claim interprocedural analysis" still holds. One artefact, no fixpoint, and any non-literal actual is treated as tainted rather than proven to be. Only a FORM whose visible callers ALL pass literals is ever cleared: a METHOD or a FUNCTION keeps the conservative seed however clean its visible callers look, because a public method is called by whatever imports the class and a remote-enabled function module by another system. "No caller in this file" is their ordinary state and, for the RFC case, the dangerous one — so it is never read as a clean bill, for a FORM either. Two defects found while building it, both of the kind where a string invents structure: * the formals list captured type names, so `TYPE REF TO cl_thing` yielded `cl_thing` as a parameter and every later positional bind landed on the wrong argument. A keyword list cannot fix this — `TYPE string` would then swallow the next parameter — so the type name is consumed positionally. * parsing arguments off raw text let a literal containing an equals sign contribute a formal named from inside the string, losing the real one. Call structure is now read off the masked text, where literal content is blanked and every offset is preserved; the CALL FUNCTION callee, which IS a literal, is read from the raw text at the masked match's own span. evidence_for is memoised — the analyzer re-walks a scope per sink and the graph cannot change after parsing. With it, the graph's cost is no longer measurable above the noise on a synthetic 4,000-line program with 2,000 findings, which is already past the size of real ABAP objects. 38 tests. Seven mutations run against them; all seven fail. Two escaped the first draft — a METHOD fed only literals and a FORM with no visible caller were both missing from the fixture, which is exactly the pair the kind guard exists for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 871d21b commit 5f1e5b9

5 files changed

Lines changed: 909 additions & 10 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,7 @@ wrong in that way.
717717
| `grcac` | grc_access_control | **GRC Access Control**: EAM/Firefighter usage+ownership, ARM access-request workflow, GRC-native SoD violations, mitigating controls, SoD ruleset governance |
718718
| `rolegov` | role_governance | **role design**: SU24 proposal hygiene for custom tcodes, ungenerated profiles (AGR_1016), derived-role authorization-value drift vs parent |
719719
| `atc` | atc_import | SAP's own ATC/CVA results, ingested rather than re-derived |
720-
| `cva` | abap_sast | **our** ABAP/CDS/BDEF scanner — **135 rules dispatched by file type** (ABAP/CDS/RAP, JS/UI5 and BTP descriptors — the split is in `modules/abap_sast_rules.py`, which is the only place worth counting), statement lexer, intra-procedural taint. `ABAP-XSS-006` is retired and `ABAP-AUTH-003` is handled in the engine, so 116 of the 118 fire from the rule table |
720+
| `cva` | abap_sast | **our** ABAP/CDS/BDEF scanner — **135 rules dispatched by file type** (ABAP/CDS/RAP, JS/UI5 and BTP descriptors — the split is in `modules/abap_sast_rules.py`, which is the only place worth counting), statement lexer, taint refinement with intra-artefact call-graph awareness (`modules/abap_callgraph.py` decides whether a procedure parameter is caller-controlled; it is NOT interprocedural data-flow and must not be described as such). `ABAP-XSS-006` is retired and `ABAP-AUTH-003` is handled in the engine, so 116 of the 118 fire from the rule table |
721721
| `logreview` | log_review | retrospective SM20 review: what the audit log actually recorded |
722722
| `capxsuaa` | cap_xsuaa | **CAP project as written** (`--cap-src`): `xs-security.json` exactly + CDS model lexically. Traces scope ← role-template ← role-collection ← IdP group; `CAPX-TOK-001` closes the application-override blind spot `BTP-TOK-*` declares |
723723
| `codeinv` | code_inventory_report | custom-code estate: size by type, unreachable, dormant, unknown-kept-separate |

0 commit comments

Comments
 (0)