A comprehensive, dual-mode security scanner for SAP ABAP code and SAP BTP environments. Inspired by SAP's Code Vulnerability Analyzer (CVA), this tool performs passive SAST analysis of ABAP source files and active API scanning of live SAP BTP sub-accounts — all from a single, zero-dependency Python file.
124 security checks across 14 SAST categories and 9 BTP API check groups. Detects SQL injection, code injection, missing AUTHORITY-CHECK (with block-aware analysis of whole function modules), hardcoded credentials, weak cryptography, RFC security issues, backdoor / malicious-code patterns, UI5/JavaScript XSS, and more — purpose-built for SAP security teams, ABAP developers, and compliance auditors.
- Why This Scanner?
- Features
- Dual-Mode Architecture
- SAST Rule Categories
- BTP API Checks
- File Types Scanned
- Prerequisites
- Installation
- Quick Start
- Usage
- Report Formats
- How It Works
- Testing with Sample Files
- Project Structure
- CI/CD Integration
- Contributing
- License
Traditional security tools often miss SAP-specific vulnerabilities. ABAP has unique security constructs — AUTHORITY-CHECK, OPEN DATASET, CALL FUNCTION ... DESTINATION, GENERATE SUBROUTINE POOL — that require specialised detection rules:
- Dynamic Open SQL —
SELECT ... WHERE (variable)bypasses parameterisation, enabling SQL injection in ways generic scanners don't detect - ABAP code injection —
INSERT REPORTandGENERATE SUBROUTINE POOLallow runtime code generation, a pattern unique to ABAP - Missing AUTHORITY-CHECK — RFC-enabled function modules without proper authorization checks are a top SAP attack vector
- RFC lateral movement — Dynamic
DESTINATIONparameters allow attackers to pivot across SAP landscapes - BTP cloud misconfig —
xs-security.jsonscope grants, unauthenticated routes, and missing XSUAA validation are BTP-specific risks - SAP-specific CVEs — CommonCryptoLib, SAP_BASIS, RECON (CVE-2020-6287), and HTTP/2 Rapid Reset vulnerabilities require SAP-aware dependency checking
- ABAP comment syntax — ABAP uses
*(column 1) and"(inline) for comments, which generic scanners misparse
- 124 security checks — 94 SAST regex rules (ABAP/CDS, BTP config & UI5/JS) + 30 BTP API checks
- Data-flow (taint) analysis (
--data-flow) — intra-procedural source→sink tracking with sanitizer awareness; confirms tainted-input findings, suppresses literal/sanitized false positives, and prints the source→sink data-flow path (also emitted as SARIFcodeFlows) - Dual-mode scanning — passive SAST for source code, active API for live BTP environments, or both simultaneously
- ABAP-aware parsing — correctly handles ABAP comment syntax (
*and"), case-insensitive keywords, and.statement terminators - BTP configuration analysis — deep inspection of
xs-security.json,xs-app.json,mta.yaml, and CDS annotations - SAP dependency CVE checking — detects known vulnerabilities in CommonCryptoLib, SAP_BASIS, SAP Kernel, SAPUI5, and NetWeaver AS
- Three output formats — colour-coded terminal, structured JSON, and interactive HTML with dark theme and filtering
- Single portable file — entire scanner is one Python file with no pip installs needed for SAST mode
- Scan profiles & rule selection —
--profile full|critical-high|quick, plus--disable/--enable-only(by rule ID or category prefix) and--list-rules - Security grade — weighted A–F score (CRITICAL 10 / HIGH 5 / MEDIUM 2 / LOW 1) in the console header, JSON, and an HTML grade card
- OWASP Top 10 (2021) mapping — every finding tagged with its OWASP category; per-OWASP breakdown in console/JSON and a SARIF
taxonomiesblock with per-rule relationships - Exit codes — returns
1if CRITICAL or HIGH findings,0otherwise (CI/CD friendly) - CWE-mapped — every rule maps to a CWE identifier for compliance and risk management
- BTP OAuth 2.0 — authenticates to SAP BTP via XSUAA client credentials flow with automatic token refresh
┌─────────────────────────────────────────────┐
│ SAP ABAP Code Vulnerability │
│ Analyzer v1.8.0 │
└─────────────────┬───────────────────────────┘
│
┌─────────────────┴───────────────────────────┐
│ │
┌─────┴─────┐ ┌───────┴───────┐
│ SAST Mode │ │ BTP API Mode │
│ (Passive) │ │ (Active) │
└─────┬─────┘ └───────┬───────┘
│ │
┌───────────┼───────────┐ ┌────────────┼────────────┐
│ │ │ │ │ │
.abap files .cds files BTP config XSUAA OAuth SCIM API Dest. Svc
(80 rules) (@requires) (xs-security) Password Pol Role Coll Audit Log
User Access Trust Cfg Comm. Arr.
│ │
└─────────────────┬───────────────────────────┘
│
┌───────┴───────┐
│ Unified Report│
│ (Console/JSON │
│ /HTML) │
└───────────────┘
| Mode | Trigger | Dependencies | What It Scans |
|---|---|---|---|
| SAST (passive) | python abap_scanner.py /path |
None (pure stdlib) | ABAP source, CDS, BTP config files |
| BTP API (active) | --btp-url --client-id --client-secret |
requests |
Live BTP sub-account APIs |
| Both | Provide target path + BTP credentials | requests |
Combined SAST + API findings |
| Rule ID | Severity | Detection |
|---|---|---|
| ABAP-SQLI-001 | CRITICAL | Dynamic WHERE (variable) clause |
| ABAP-SQLI-002 | CRITICAL | CONCATENATE/&& into SQL condition |
| ABAP-SQLI-003 | CRITICAL | ADBC cl_sql_statement->execute_query |
| ABAP-SQLI-004 | CRITICAL | ADBC cl_sql_connection=>get_connection |
| ABAP-SQLI-005 | HIGH | OPEN CURSOR with dynamic WHERE |
| ABAP-SQLI-006 | HIGH | Dynamic ORDER BY (variable) |
| ABAP-SQLI-007 | HIGH | Dynamic GROUP BY (variable) |
| ABAP-SQLI-008 | HIGH | Dynamic HAVING (variable) |
| ABAP-SQLI-009 | CRITICAL | Prepared statement defeated by concatenation |
| ABAP-SQLI-010 | MEDIUM | Dynamic FROM (table) clause |
| ABAP-SQLI-011 | HIGH | Dynamic table name in FROM (variable) (continuation-line aware) |
| Rule ID | Severity | Detection |
|---|---|---|
| ABAP-CINJ-001 | CRITICAL | INSERT REPORT ... FROM (runtime code replacement) |
| ABAP-CINJ-002 | CRITICAL | GENERATE SUBROUTINE POOL (dynamic code compilation) |
| ABAP-CINJ-003 | HIGH | CALL FUNCTION variable (dynamic FM call) |
| ABAP-CINJ-004 | HIGH | CALL TRANSACTION variable (dynamic tcode) |
| ABAP-CINJ-005 | HIGH | SUBMIT (variable) (dynamic report execution) |
| ABAP-CINJ-006 | MEDIUM | CREATE OBJECT ... TYPE (variable) |
| ABAP-CINJ-007 | CRITICAL | CALL TRANSFORMATION variable (dynamic XSLT) |
| ABAP-CINJ-008 | HIGH | READ REPORT ... INTO (source code extraction) |
| ABAP-CINJ-009 | MEDIUM | ASSIGN (variable) (dynamic field-symbol assignment) |
| ABAP-CINJ-010 | HIGH | CALL METHOD (variable) (dynamic method invocation) |
| Rule ID | Severity | Detection |
|---|---|---|
| ABAP-CMDI-001 | CRITICAL | Kernel CALL 'SYSTEM' |
| ABAP-CMDI-002 | HIGH | SXPG_COMMAND_EXECUTE |
| ABAP-CMDI-003 | HIGH | SXPG_CALL_SYSTEM |
| ABAP-CMDI-004 | HIGH | OPEN DATASET/PIPE ... FILTER |
| ABAP-CMDI-005 | HIGH | cl_gui_frontend_services=>execute (frontend OS command) |
| Rule ID | Severity | Detection |
|---|---|---|
| ABAP-PATH-001 | HIGH | OPEN DATASET without FILE_VALIDATE_NAME |
| ABAP-PATH-002 | HIGH | DELETE DATASET without validation |
| ABAP-PATH-003 | HIGH | TRANSFER ... TO variable without validation |
| ABAP-PATH-004 | MEDIUM | ../ path traversal in string concatenation |
| ABAP-PATH-005 | HIGH | READ DATASET without validation |
| Rule ID | Severity | Detection |
|---|---|---|
| ABAP-XSS-001 | HIGH | HTTP response set_cdata/set_data without escaping |
| ABAP-XSS-002 | HIGH | HTML tag concatenation with variable (<script>, <img>, etc.) |
| ABAP-XSS-003 | HIGH | BSP/ICF server->response->set_* unescaped |
| ABAP-XSS-004 | MEDIUM | Cookie without Secure/HttpOnly flags |
| ABAP-XSS-005 | HIGH | cl_abap_browser=>show_html with dynamic content |
| ABAP-XSS-006 | MEDIUM | CL_HTTP_UTILITY used without escape_html |
| Rule ID | Severity | Detection |
|---|---|---|
| ABAP-AUTH-001 | CRITICAL | Function module without AUTHORITY-CHECK |
| ABAP-AUTH-002 | HIGH | AUTHORITY-CHECK with DUMMY (always passes) |
| ABAP-AUTH-003 | HIGH | SY-SUBRC not checked after AUTHORITY-CHECK |
| ABAP-AUTH-004 | HIGH | CALL FUNCTION ... DESTINATION without auth check |
| ABAP-AUTH-005 | HIGH | DELETE FROM table without auth check |
| ABAP-AUTH-006 | HIGH | UPDATE table SET without auth check |
| ABAP-AUTH-007 | MEDIUM | INSERT INTO table without auth check |
| ABAP-AUTH-008 | HIGH | CALL TRANSACTION without S_TCODE check |
| ABAP-AUTH-009 | MEDIUM | Cross-client access via CLIENT SPECIFIED |
| Rule ID | Severity | Detection |
|---|---|---|
| ABAP-CRED-001 | CRITICAL | Hardcoded PASSWORD = 'literal' |
| ABAP-CRED-002 | CRITICAL | Hardcoded API_KEY/TOKEN/SECRET |
| ABAP-CRED-003 | HIGH | RFC DESTINATION with hardcoded password |
| ABAP-CRED-004 | HIGH | Hardcoded Basic auth header |
| ABAP-CRED-005 | MEDIUM | Credentials defined as CONSTANTS |
| ABAP-CRED-006 | HIGH | RFC_*/BAPI_* with embedded USER/PASSWORD |
| Rule ID | Severity | Detection |
|---|---|---|
| ABAP-CRYP-001 | HIGH | MD5 hash via cl_abap_message_digest |
| ABAP-CRYP-002 | MEDIUM | SHA-1 hash usage |
| ABAP-CRYP-003 | HIGH | DES/3DES encryption |
| ABAP-CRYP-004 | HIGH | Hardcoded encryption key or IV |
| ABAP-CRYP-005 | MEDIUM | HMAC with weak hash (MD5/SHA-1) |
| ABAP-CRYP-006 | LOW | Non-cryptographic RNG (cl_abap_random) for security values |
| Rule ID | Severity | Detection |
|---|---|---|
| ABAP-INFO-001 | MEDIUM | Overly broad CATCH cx_root |
| ABAP-INFO-002 | LOW | BREAK-POINT left in code |
| ABAP-INFO-003 | LOW | SY-UNAME/SY-MANDT/SY-HOST in output |
| ABAP-INFO-004 | MEDIUM | Exception get_text() exposed via MESSAGE |
| ABAP-INFO-005 | HIGH | Sensitive data (password, token) in WRITE |
| Rule ID | Severity | Detection |
|---|---|---|
| ABAP-CONF-001 | HIGH | ssl_id = 'ANONYM' (no cert validation) |
| ABAP-CONF-002 | HIGH | http:// in connection URLs |
| ABAP-CONF-003 | MEDIUM | CSRF protection disabled |
| ABAP-CONF-004 | HIGH | cl_http_client=>create_by_url with HTTP |
| ABAP-CONF-005 | MEDIUM | Wildcard ACTVT = '*' in AUTHORITY-CHECK |
| ABAP-CONF-006 | HIGH | ssl_verify = 0/false |
| Rule ID | Severity | Detection |
|---|---|---|
| ABAP-RFC-001 | HIGH | Trusted RFC without validation |
| ABAP-RFC-002 | HIGH | Dynamic DESTINATION variable |
| ABAP-RFC-003 | HIGH | RFC CALLBACK pattern |
| ABAP-RFC-004 | CRITICAL | RFC_REGISTER / registered server program |
| ABAP-RFC-005 | MEDIUM | Async RFC (STARTING NEW TASK) without error handling |
Detects the deliberate subversion of access control — the hallmark of an insider backdoor or supply-chain implant — rather than the accidental mistakes the other categories cover. (Runtime code-generation backdoors such as INSERT REPORT / GENERATE SUBROUTINE POOL are already flagged under Code Injection.)
| Rule ID | Severity | Detection |
|---|---|---|
| ABAP-BKDR-001 | CRITICAL | Hardcoded SY-UNAME = '...' check (logic bomb / privilege bypass) |
| ABAP-BKDR-002 | HIGH | Reference to standard/superuser account (SAP*, DDIC, EARLYWATCH, ...) |
| ABAP-BKDR-003 | CRITICAL | Direct DML write to user/authorization tables (USR02, AGR_*, ...) |
| ABAP-BKDR-004 | HIGH | Programmatic user/role administration (BAPI_USER_* create/change/assign) |
| ABAP-BKDR-005 | MEDIUM | Hardcoded client SY-MANDT = '...' check (client-specific hidden logic) |
| ABAP-BKDR-006 | CRITICAL | Programmatic grant of wide profile (SAP_ALL / SAP_NEW) |
| Rule ID | Severity | Detection |
|---|---|---|
| ABAP-BTP-001 | HIGH | $ACCEPT_GRANTED_AUTHORITIES in xs-security.json |
| ABAP-BTP-002 | HIGH | Wildcard $XSAPPNAME.* scope in role template |
| ABAP-BTP-003 | CRITICAL | Hardcoded credentials in mta.yaml |
| ABAP-BTP-004 | HIGH | Missing @requires in CDS service definition |
| ABAP-BTP-005 | HIGH | authenticationType: "none" in xs-app.json |
| ABAP-BTP-006 | MEDIUM | Missing Content-Security-Policy |
| ABAP-BTP-007 | HIGH | XSUAA token validation disabled |
| ABAP-BTP-008 | MEDIUM | CORS allowedOrigin: "*" |
| Package | CVE | Severity | Impact |
|---|---|---|---|
| CommonCryptoLib < 8.5.50 | CVE-2023-40309 | CRITICAL | Privilege escalation |
| SAP_BASIS 700-793 | CVE-2023-49583 | CRITICAL | Unauthorised RFC calls |
| SAP_BASIS 700-789 | CVE-2022-22536 | CRITICAL | HTTP request smuggling |
| SAP Kernel 700-793 | CVE-2023-44487 | HIGH | HTTP/2 Rapid Reset DoS |
| SAPUI5 1.38-1.120.2 | CVE-2021-21491 | HIGH | Stored XSS |
| NetWeaver AS Java 7.10-7.53 | CVE-2020-6287 | CRITICAL | RECON — unauthenticated admin creation |
When BTP credentials are provided, the scanner connects to the SAP BTP sub-account and runs 30 security checks:
| # | Category | ID Prefix | Checks | What It Audits |
|---|---|---|---|---|
| 1 | Password Policy | ABAP-BTP-PWD | 4 | Min length, complexity, expiry, lockout threshold |
| 2 | User Access | ABAP-BTP-USR | 4 | Inactive users, admin accounts, service accounts, MFA |
| 3 | Role Management | ABAP-BTP-ROLE | 4 | Wildcard scopes, unused roles, excessive roles, defaults |
| 4 | Service Security | ABAP-BTP-SVC | 3 | Stale bindings, key expiry, excessive bindings |
| 5 | Trust Configuration | ABAP-BTP-TRUST | 3 | MFA requirement, default IdP, scope mapping |
| 6 | Audit Logging | ABAP-BTP-AUDIT | 3 | Audit enabled, retention period, event coverage |
| 7 | Communication Security | ABAP-BTP-COMM | 4 | HTTP destinations, basic auth, proxy, user privileges |
| 8 | Security Settings | ABAP-BTP-SEC | 3 | Session timeout, token lifetime, refresh lifetime |
| 9 | Destination Security | ABAP-BTP-DEST | 2 | HTTP protocol, no-authentication destinations |
| File Type | Extensions | What Is Checked |
|---|---|---|
| ABAP Source | .abap, .prog.abap, .clas.abap, .intf.abap, .fugr.abap, .func.abap, .ddls.abap, .dcls.abap, .abp |
SQL injection, code injection, OS command injection, directory traversal, XSS, missing authorization, hardcoded credentials, weak crypto, info disclosure, insecure config, RFC security |
| CDS Definitions | .cds |
Missing @requires annotation, BTP config rules |
| UI5 / JavaScript | .js, .ts (skips .min.js) |
eval, innerHTML, document.write, hardcoded secrets, http:// URLs, jQuery .html() |
| XSUAA Config | xs-security.json |
Scope grants, role template wildcards, deep JSON inspection |
| App Router | xs-app.json |
Unauthenticated routes, missing CSP, CORS wildcards |
| MTA Descriptor | mta.yaml, mta.yml |
Hardcoded credentials, insecure bindings |
| BTP Config | .cdsrc.json, default-env.json |
Hardcoded secrets, token validation |
| Package Config | package.json |
BTP config patterns |
- Python 3.10+ (uses
match-style type hints) - No pip installs needed for SAST mode (pure stdlib)
requestslibrary required only for BTP API mode:pip install requests
Option 1 — Clone the repository:
git clone https://github.com/Krishcalin/SAP-Code-Vulnerability-Analyzer.git
cd SAP-Code-Vulnerability-Analyzer
python abap_scanner.py --versionOption 2 — Download the scanner file directly:
curl -O https://raw.githubusercontent.com/Krishcalin/SAP-Code-Vulnerability-Analyzer/main/abap_scanner.py
python abap_scanner.py --version1. Scan an ABAP project directory:
python abap_scanner.py /path/to/abap/project2. Scan with verbose output and severity filter:
python abap_scanner.py /path/to/project --severity HIGH --verbose3. Generate JSON and HTML reports:
python abap_scanner.py /path/to/project --json report.json --html report.html4. Scan a live BTP environment:
python abap_scanner.py \
--btp-url https://mysubaccount.authentication.eu10.hana.ondemand.com \
--client-id <client-id> \
--client-secret <client-secret>5. Combined SAST + BTP API scan:
python abap_scanner.py /path/to/project \
--btp-url https://mysubaccount.authentication.eu10.hana.ondemand.com \
--client-id <client-id> \
--client-secret <client-secret> \
--json unified_report.json --html unified_report.htmlusage: abap_scanner [-h] [--btp-url BTP_URL] [--client-id CLIENT_ID]
[--client-secret CLIENT_SECRET] [--json FILE] [--html FILE]
[--sarif FILE] [--baseline FILE] [--write-baseline FILE]
[--no-suppress] [--profile {full,critical-high,quick}]
[--disable IDS] [--enable-only IDS] [--list-rules]
[--data-flow] [--severity {CRITICAL,HIGH,MEDIUM,LOW,INFO}]
[--verbose] [--version]
[target]
SAP ABAP Security Scanner v1.8.0 — passive SAST + BTP API
positional arguments:
target File or directory to scan (SAST mode)
options:
-h, --help show this help message and exit
--btp-url BTP_URL BTP XSUAA auth URL (API mode). Env: BTP_AUTH_URL
--client-id CLIENT_ID OAuth 2.0 client ID. Env: BTP_CLIENT_ID
--client-secret CLIENT_SECRET
OAuth 2.0 client secret. Env: BTP_CLIENT_SECRET
--json FILE Save JSON report to FILE
--html FILE Save HTML report to FILE
--sarif FILE Save SARIF 2.1.0 report to FILE (GitHub code scanning / IDE)
--baseline FILE Suppress findings present in this baseline; report only NEW
--write-baseline FILE Write current findings as a baseline to FILE, then exit
--no-suppress Disable inline #NOSEC suppression comments
--profile {full,critical-high,quick}
Scan profile (default: full). critical-high = only
CRITICAL+HIGH; quick = CRITICAL only
--disable IDS Comma-separated rule IDs/prefixes to turn OFF
--enable-only IDS Run ONLY these rule IDs/prefixes (overrides --profile)
--list-rules List every rule with its on/off state, then exit
--data-flow, --taint Enable data-flow (taint) analysis: confirm tainted
findings, suppress literal/sanitized ones
--severity {CRITICAL,HIGH,MEDIUM,LOW,INFO}
Minimum severity to report (default: LOW)
--verbose, -v Verbose output
--version show program's version number and exit
Tune which checks run — useful for fast PR gates vs. deep audits:
# Profiles: full (default) | critical-high | quick (CRITICAL only)
python abap_scanner.py ./src --profile critical-high
python abap_scanner.py ./src --profile quick # fastest, highest-signal
# Turn individual rules or whole categories off (exact id OR id-prefix)
python abap_scanner.py ./src --disable ABAP-XSS,ABAP-SQLI-003
# Run ONLY a focused set (e.g. hunt for backdoors); overrides --profile
python abap_scanner.py ./src --enable-only ABAP-BKDR
# Preview the catalogue and what's active under a given selection
python abap_scanner.py --list-rules --profile critical-highPrecedence: --enable-only (allow-list) overrides --profile (severity band);
--disable is always applied last. Selection is enforced uniformly across SAST,
block, dependency-CVE and BTP API findings.
python abap_scanner.py ./src --data-flow # or --taintA pragmatic, intra-procedural approximation of SAP CVA's local data-flow
analysis — the feature that drives down false positives. Per procedure
(FORM/METHOD/FUNCTION; PARAMETERS/SELECT-OPTIONS treated as external input),
it tracks whether each variable is tainted (from a source such as
request->get_form_field, GET PARAMETER ID, a web request), sanitized
(via CL_ABAP_DYN_PRG methods, FILE_VALIDATE_NAME, allow-list checks), or a
literal/constant. For injection/traversal sink rules it then:
- confirms the finding when tainted input reaches the sink (
confidence: confirmed), - suppresses it when the argument is a literal/constant or has been sanitized,
- leaves it tentative when there's no evidence either way (nothing is dropped on a guess).
confidence appears in the console line, the JSON confidence field, and SARIF
result properties. It is off by default, so standard runs (and baselines)
are unaffected; turn it on for high-signal, low-noise results.
For a confirmed finding the scanner also prints the data-flow path — where the external input entered, how it propagated, and the sink it reached:
[CRITICAL] ABAP-SQLI-001 — Dynamic WHERE clause from variable [data-flow: confirmed]
Data flow:
-> L10 source lv_where = request->get_form_field( 'q' ).
=> L11 sink SELECT * FROM mara WHERE (lv_where) INTO TABLE @DATA(lt1).
The same path is emitted in the JSON flow array and as a SARIF
codeFlows / threadFlows structure, so GitHub code scanning and SARIF
viewers render the source→sink trace inline (as SAP CVA does in-system).
Two complementary mechanisms:
" Inline — suppress on a single line (a comment on the offending line):
SELECT * FROM scarr WHERE (lv_w) INTO TABLE @DATA(t). "#NOSEC ABAP-SQLI-001
SELECT * FROM scarr WHERE (lv_w) INTO TABLE @DATA(t). "#NOSEC (blanket — any rule)# Baseline — accept all current findings, then only fail on NEW ones (great for
# onboarding the scanner onto an existing codebase without a wall of findings):
python abap_scanner.py ./src --write-baseline .abap-baseline.json # once
python abap_scanner.py ./src --baseline .abap-baseline.json # every run afterBaseline fingerprints are line-number-independent (rule + file + normalized code), so edits elsewhere in a file don't resurface accepted findings.
# Scan a single ABAP file
python abap_scanner.py src/zcl_my_class.clas.abap
# Scan an entire abapGit project
python abap_scanner.py /path/to/abapgit/src --verbose
# Only show CRITICAL and HIGH findings
python abap_scanner.py /path/to/project --severity HIGH
# Scan BTP config files only
python abap_scanner.py /path/to/mta-project/
# BTP API mode with env vars
export BTP_AUTH_URL="https://subdomain.authentication.eu10.hana.ondemand.com"
export BTP_CLIENT_ID="my-client-id"
export BTP_CLIENT_SECRET="my-client-secret"
python abap_scanner.py --severity HIGH --json btp_report.json
# Full dual-mode scan with all reports
python abap_scanner.py ./src \
--btp-url $BTP_AUTH_URL --client-id $BTP_CLIENT_ID --client-secret $BTP_CLIENT_SECRET \
--json report.json --html report.html --verbose| Variable | Description |
|---|---|
BTP_AUTH_URL |
BTP XSUAA authentication URL |
BTP_CLIENT_ID |
OAuth 2.0 client ID |
BTP_CLIENT_SECRET |
OAuth 2.0 client secret |
Colour-coded terminal output sorted by severity, with rule ID, file location, code snippet, CWE reference, description, and fix recommendation. The header shows a security grade (A–F).
A weighted A–F grade summarises overall posture at a glance. The scan starts at
100 and deducts per reported finding — CRITICAL 10, HIGH 5, MEDIUM 2, LOW 1,
INFO 0 — floored at 0. The score maps to a letter (≥90 A, ≥80 B, ≥70 C, ≥60 D,
else F) and appears in the console header, the JSON security_grade block, and
an HTML grade card. It reflects findings as reported, so #NOSEC, baseline and
--severity filtering all feed into it.
Structured JSON with metadata, summary counts, and detailed findings array:
{
"scanner": "SAP ABAP Security Scanner v1.8.0",
"generated": "2026-03-14T23:00:00",
"files_scanned": 42,
"total_findings": 15,
"summary": {"CRITICAL": 3, "HIGH": 7, "MEDIUM": 4, "LOW": 1, "INFO": 0},
"owasp_summary": {"A01": 4, "A02": 1, "A03": 7, "A07": 3},
"security_grade": {"score": 26, "grade": "C", "penalty": 74},
"findings": [{"...": "...", "cwe": "CWE-89", "owasp": "A03:2021 - Injection"}]
}Self-contained interactive HTML report with:
- Dark theme (Catppuccin Mocha palette)
- Severity summary cards
- Interactive filtering by severity, category, and text search
- Sortable table with code snippets and recommendations
SARIF 2.1.0 for tool interoperability —
upload to GitHub code scanning (findings appear inline on PRs), open in the
VS Code SARIF Viewer, or ingest into Azure DevOps / other dashboards. Severity
maps to SARIF levels (CRITICAL/HIGH → error, MEDIUM → warning, LOW/INFO →
note) and each rule carries a security-severity score and a CWE helpUri.
The run also includes an OWASP Top 10 (2021) taxonomies component, and each
rule declares a relationships link to its OWASP taxon — so GitHub code scanning
and SARIF viewers can group and filter findings by OWASP category.
- File discovery — recursively walks the target directory, skipping
.git,node_modules, etc. - File dispatch — routes files to the appropriate scanner based on extension (
.abap→ ABAP scanner,.cds→ CDS scanner,xs-security.json→ config scanner) - Comment-aware parsing — strips ABAP comments (
*full-line,"inline) before pattern matching - Regex matching — applies 94 pre-compiled case-insensitive regex patterns line-by-line
- Block-aware analysis — carries state across lines to flag whole
FUNCTION … ENDFUNCTIONblocks that lack anAUTHORITY-CHECK(a check mentioned only in a comment does not count) 5a. Data-flow refinement (optional,--data-flow) — per-procedure taint tracking confirms findings fed by external input and suppresses those with literal/constant or sanitized arguments - Deep JSON inspection — parses
xs-security.jsonto check scope grants and role template wildcards structurally - Version checking — scans for SAP package version references and cross-references against known CVEs
- Finding collection — each match creates a
Findingwith rule ID, severity, CWE, location, and recommendation
- OAuth 2.0 authentication — obtains access token from XSUAA using client credentials flow
- API enumeration — queries 9 BTP API endpoints (SCIM, role collections, destinations, audit log, etc.)
- Policy evaluation — checks settings against security baselines (e.g., min password length ≥ 12, session timeout ≤ 30 min)
- User access review — identifies inactive accounts (>90 days), excessive admin privileges, missing MFA
- Automatic token refresh — re-authenticates transparently when tokens expire during long scans
# Run against the intentionally vulnerable test file
python abap_scanner.py tests/samples/vulnerable_abap.abap --verbose
# Expected: 46 findings (15 CRITICAL, 26 HIGH, 4 MEDIUM, 1 LOW)
# Unit tests (no SAP system / network needed)
PYTHONPATH=. python -m pytest tests/ -q| Test File | Purpose | Expected Findings |
|---|---|---|
tests/samples/vulnerable_abap.abap |
Intentionally vulnerable ABAP code covering the SAST categories | 46 |
tests/samples/auth_check_blocks.abap |
Block-level AUTHORITY-CHECK detection (positive + safe + comment-only cases) | 2× ABAP-AUTH-001 |
tests/samples/vulnerable_extra.abap |
v1.2 extra rules (SQLI-011, AUTH-009, CINJ-009/010, CMDI-005, CRYP-006) | 6× extra rules |
tests/samples/vulnerable_ui5.js |
UI5 / JavaScript rules | 6× ABAP-JS-* |
tests/samples/vulnerable_backdoor.abap |
Backdoor / malicious-code patterns (hardcoded user checks, auth-table writes, ...) | 6× ABAP-BKDR-* |
tests/samples/taint_flow.abap |
Data-flow scopes (tainted / sanitized / literal / unknown sink arguments) | 5 → 2 with --data-flow |
The suite (83 tests, no SAP system / network required) covers the engine,
block scanner, BTP check_func coverage guard, reporting (SARIF / #NOSEC /
baseline), scan profiles, the security grade, the OWASP mapping + taxonomy, and
the data-flow / taint engine (incl. source→sink traces).
SAP-Code-Vulnerability-Analyzer/
├── abap_scanner.py # Main scanner (single file, ~3,050 lines)
├── banner.svg # Project banner
├── CLAUDE.md # AI assistant context
├── LICENSE # MIT License
├── README.md # This file
├── .gitignore
├── .github/workflows/tests.yml # CI: pytest matrix (Python 3.10–3.13)
└── tests/
├── test_block_scan.py # block-level AUTHORITY-CHECK + BTP coverage guard
├── test_scanner.py # engine regression locks (counts, rules, CVE, JSON)
├── test_reporting.py # SARIF, inline #NOSEC suppression, baselining
├── test_btp_checks.py # BTP pure evaluators vs synthetic API data
├── test_extra_rules.py # v1.2 extra ABAP rules + UI5/JS scanning
├── test_backdoor_rules.py # backdoor / malicious-code category
├── test_profiles.py # scan profiles + rule selection
├── test_grade.py # weighted A–F security grade
├── test_owasp.py # OWASP Top 10 mapping + SARIF taxonomy
├── test_taint.py # data-flow / taint analysis engine
└── samples/
├── vulnerable_abap.abap # broad intentional vulnerabilities (46 findings)
├── auth_check_blocks.abap # FUNCTION blocks with/without AUTHORITY-CHECK
├── vulnerable_extra.abap # v1.2 extra rules
├── vulnerable_ui5.js # UI5 / JavaScript rules
├── vulnerable_backdoor.abap # backdoor / malicious-code patterns
└── taint_flow.abap # data-flow scopes for --data-flow
The scanner returns exit code 1 when CRITICAL or HIGH findings are detected, making it suitable for CI/CD pipeline gates:
# GitHub Actions example
- name: ABAP Security Scan
run: |
python abap_scanner.py ./src --severity HIGH --json abap-report.json
# Exit code 1 fails the pipeline if CRITICAL/HIGH findings exist# GitLab CI example
abap_security_scan:
script:
- python abap_scanner.py ./src --severity HIGH --json abap-report.json --html abap-report.html
artifacts:
paths:
- abap-report.json
- abap-report.html
when: always- Add the rule dict to the appropriate
ABAP_*_RULESlist inabap_scanner.py - Follow the ID pattern:
ABAP-{CATEGORY}-{NNN} - Include all required fields:
id,category,severity,name,pattern,description,cwe,recommendation - Test with:
python abap_scanner.py tests/samples/vulnerable_abap.abap --verbose
- Add the check definition to
BTP_API_CHECKS - Implement the
_check_*method inAbapBtpScanner - Follow the ID pattern:
ABAP-BTP-{CATEGORY}-{NNN}
This project is licensed under the MIT License.