appsec-scan-router 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- appsec_scan_router-1.0.0/LICENSE +21 -0
- appsec_scan_router-1.0.0/PKG-INFO +478 -0
- appsec_scan_router-1.0.0/README.md +449 -0
- appsec_scan_router-1.0.0/ado_mobile_scanner.py +5 -0
- appsec_scan_router-1.0.0/appsec_scan_router/__init__.py +269 -0
- appsec_scan_router-1.0.0/appsec_scan_router/__main__.py +5 -0
- appsec_scan_router-1.0.0/appsec_scan_router/activity.py +63 -0
- appsec_scan_router-1.0.0/appsec_scan_router/azure.py +307 -0
- appsec_scan_router-1.0.0/appsec_scan_router/cli.py +182 -0
- appsec_scan_router-1.0.0/appsec_scan_router/constants.py +123 -0
- appsec_scan_router-1.0.0/appsec_scan_router/detection.py +289 -0
- appsec_scan_router-1.0.0/appsec_scan_router/metadata.py +456 -0
- appsec_scan_router-1.0.0/appsec_scan_router/models.py +94 -0
- appsec_scan_router-1.0.0/appsec_scan_router/reports.py +166 -0
- appsec_scan_router-1.0.0/appsec_scan_router/scanner.py +794 -0
- appsec_scan_router-1.0.0/appsec_scan_router/sdk.py +19 -0
- appsec_scan_router-1.0.0/appsec_scan_router/store_lookup.py +384 -0
- appsec_scan_router-1.0.0/appsec_scan_router/utils.py +113 -0
- appsec_scan_router-1.0.0/appsec_scan_router.egg-info/PKG-INFO +478 -0
- appsec_scan_router-1.0.0/appsec_scan_router.egg-info/SOURCES.txt +28 -0
- appsec_scan_router-1.0.0/appsec_scan_router.egg-info/dependency_links.txt +1 -0
- appsec_scan_router-1.0.0/appsec_scan_router.egg-info/entry_points.txt +4 -0
- appsec_scan_router-1.0.0/appsec_scan_router.egg-info/requires.txt +2 -0
- appsec_scan_router-1.0.0/appsec_scan_router.egg-info/top_level.txt +4 -0
- appsec_scan_router-1.0.0/mobile_app_inventory_tracer.py +5 -0
- appsec_scan_router-1.0.0/mobile_scanner/__init__.py +1 -0
- appsec_scan_router-1.0.0/mobile_scanner/__main__.py +5 -0
- appsec_scan_router-1.0.0/pyproject.toml +53 -0
- appsec_scan_router-1.0.0/setup.cfg +4 -0
- appsec_scan_router-1.0.0/tests/test_ado_mobile_scanner.py +751 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AppSec Scan Router contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: appsec-scan-router
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: SDK and CLI for routing Azure DevOps mobile app inventory scans with optional public store validation
|
|
5
|
+
Author: AppSec Scan Router contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/h0p3sf4ll/mobile-app-inventory-tracer
|
|
8
|
+
Project-URL: Repository, https://github.com/h0p3sf4ll/mobile-app-inventory-tracer
|
|
9
|
+
Project-URL: Issues, https://github.com/h0p3sf4ll/mobile-app-inventory-tracer/issues
|
|
10
|
+
Keywords: appsec,sdk,azure-devops,mobile,inventory,android,ios,app-store,google-play
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: openpyxl>=3.1.0
|
|
27
|
+
Requires-Dist: requests>=2.31.0
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# AppSec Scan Router
|
|
31
|
+
|
|
32
|
+
AppSec Scan Router is a Python SDK, CLI, and Dockerized scanner for Azure DevOps mobile application inventory. It identifies mobile apps across large Git estates, extracts app metadata, captures contributor and activity signals, validates public app store listings when requested, and writes Excel-ready reports as the scan runs.
|
|
33
|
+
|
|
34
|
+
It is designed for engineering, platform, security, and enterprise architecture teams that need a reliable inventory of mobile codebases without cloning every repository or depending on broad keyword search.
|
|
35
|
+
|
|
36
|
+
## Highlights
|
|
37
|
+
|
|
38
|
+
- Scans each repository's configured default branch, with a controlled deploy-branch fallback when none is set
|
|
39
|
+
- Detects Android, iOS, Flutter, React Native, Expo, Ionic, Capacitor, Cordova, Xamarin, and .NET MAUI signals
|
|
40
|
+
- Parses structured manifests and project files instead of relying on naive keyword matching
|
|
41
|
+
- Extracts app name, version, bundle/package identifier, source of identifier, contributors, and latest branch activity
|
|
42
|
+
- Splits Excel output into active and older branch worksheets, defaulting to `Active 90d` and `Older 90d`
|
|
43
|
+
- Streams CSV and JSON rows while the scan is running
|
|
44
|
+
- Optionally enriches detected identifiers with public Apple App Store and Google Play metadata
|
|
45
|
+
- Runs as a Python package, CLI, importable library, or Docker container
|
|
46
|
+
- Keeps Azure DevOps access read-only and fetches only allow-listed metadata/configuration files
|
|
47
|
+
|
|
48
|
+
## How It Works
|
|
49
|
+
|
|
50
|
+
The scanner uses the Azure DevOps REST API to list projects, repositories, default-branch tree items, selected file contents, and commit history. For each repository, it scans the branch in Azure DevOps `defaultBranch`, such as `main`, `master`, `develop`, or another configured default.
|
|
51
|
+
|
|
52
|
+
If Azure DevOps does not report a default branch for a repository, the scanner resolves one fallback branch instead of scanning every branch. It first checks Azure DevOps build definitions for repository-linked branch settings and branch filters. If no pipeline-associated branch can be resolved, it selects the strongest deployment-like branch name from the repo refs, prioritizing names such as `production`, `prod`, `preprod`, `release`, `main`, `master`, `development`, `develop`, and `dev`.
|
|
53
|
+
|
|
54
|
+
Azure DevOps does not provide a single universal "production branch" field across all repos and deployment models. Pipeline fallback is therefore best-effort and depends on build definitions being available to the PAT. Release pipelines, external deployment systems, and manually deployed branches may not be visible through the read-only Code APIs.
|
|
55
|
+
|
|
56
|
+
It fetches only files that can provide strong mobile signals or metadata, such as:
|
|
57
|
+
|
|
58
|
+
- `AndroidManifest.xml`
|
|
59
|
+
- `Info.plist`
|
|
60
|
+
- `InfoPlist.strings`
|
|
61
|
+
- `project.pbxproj`
|
|
62
|
+
- `.xcconfig`
|
|
63
|
+
- `build.gradle`
|
|
64
|
+
- `build.gradle.kts`
|
|
65
|
+
- `gradle.properties`
|
|
66
|
+
- `package.json`
|
|
67
|
+
- `app.json`
|
|
68
|
+
- `expo.json`
|
|
69
|
+
- `pubspec.yaml`
|
|
70
|
+
- `.csproj`
|
|
71
|
+
- `.props`
|
|
72
|
+
- `capacitor.config.*`
|
|
73
|
+
- `ionic.config.json`
|
|
74
|
+
- `config.xml`
|
|
75
|
+
- Azure pipeline YAML files
|
|
76
|
+
|
|
77
|
+
Detection is evidence-based. A repository branch is included when structured signals meet the configured confidence threshold. Generic `.csproj` files, generic `config.xml` files, and weak pipeline-only clues are not enough on their own to classify a repository as an app.
|
|
78
|
+
|
|
79
|
+
## What It Detects
|
|
80
|
+
|
|
81
|
+
| Category | Strong signals |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| Android | Android manifest, Gradle Android application plugin, `applicationId`, `namespace`, `versionName` |
|
|
84
|
+
| iOS | `Info.plist`, `InfoPlist.strings`, Xcode build settings, bundle identifiers, marketing version |
|
|
85
|
+
| Flutter | `pubspec.yaml` Flutter SDK dependency with native project layout |
|
|
86
|
+
| React Native / Expo | `package.json`, Expo config, native Android/iOS project layout |
|
|
87
|
+
| Ionic / Capacitor / Cordova | Capacitor config, Cordova widget config, Ionic config, package dependencies |
|
|
88
|
+
| Xamarin / MAUI | `.csproj`, `UseMaui`, mobile target frameworks, `ApplicationId` |
|
|
89
|
+
| Mobile pipelines | Mobile build/task evidence as supporting context |
|
|
90
|
+
|
|
91
|
+
## App Metadata Extraction
|
|
92
|
+
|
|
93
|
+
When possible, the scanner extracts:
|
|
94
|
+
|
|
95
|
+
- `mobile_name`, such as `Agsnap`
|
|
96
|
+
- `mobile_version`, such as `1.0.2`
|
|
97
|
+
- `mobile_identifier`, such as `com.pepsico.agsnap`
|
|
98
|
+
- `mobile_identifier_source`, such as `Info.plist`, `Gradle applicationId/namespace`, or `Xcode build settings`
|
|
99
|
+
- `mobile_identifier_status`, either `found` or `missing_from_scanned_files`
|
|
100
|
+
|
|
101
|
+
It resolves common indirection patterns before deciding that an identifier is missing:
|
|
102
|
+
|
|
103
|
+
- Gradle property placeholders such as `${appId}`
|
|
104
|
+
- Xcode build setting placeholders such as `$(PRODUCT_BUNDLE_IDENTIFIER)`
|
|
105
|
+
- MSBuild `.props` values
|
|
106
|
+
- iOS plist references
|
|
107
|
+
- Android string resources for display names
|
|
108
|
+
|
|
109
|
+
The scanner does not invent identifiers. If a repo generates identifiers from CI/CD variables, private variable groups, secrets, flavors, external files, or runtime build logic that is not present in Azure DevOps, the identifier is reported as missing.
|
|
110
|
+
|
|
111
|
+
## Store Enrichment
|
|
112
|
+
|
|
113
|
+
Store lookup is optional and disabled by default. Enable it with `--store-lookup`.
|
|
114
|
+
|
|
115
|
+
When enabled:
|
|
116
|
+
|
|
117
|
+
- Apple App Store lookup uses the detected bundle identifier against Apple public lookup data
|
|
118
|
+
- Google Play lookup checks the public app details page by package identifier
|
|
119
|
+
- Results are cached by identifier and platform during the scan
|
|
120
|
+
- Lookup runs only after a resolved branch has already been classified as mobile
|
|
121
|
+
|
|
122
|
+
Google Play public lookup can confirm public listings, but it cannot see private/internal apps or Play Console-only listings. Authenticated Google Play Developer API support can be layered in separately for organizations that own the apps and can provide Android Publisher OAuth credentials.
|
|
123
|
+
|
|
124
|
+
## Installation
|
|
125
|
+
|
|
126
|
+
### Requirements
|
|
127
|
+
|
|
128
|
+
- Python 3.10 or newer
|
|
129
|
+
- Azure DevOps PAT with read access to Projects and Code
|
|
130
|
+
- Optional Azure DevOps Build read access for pipeline-associated fallback branches when a repo has no default branch
|
|
131
|
+
- Network access to `dev.azure.com`
|
|
132
|
+
- Optional network access to Apple and Google Play endpoints when `--store-lookup` is enabled
|
|
133
|
+
|
|
134
|
+
### PyPI Install
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
python -m pip install appsec-scan-router
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Local Development Setup
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
git clone https://github.com/h0p3sf4ll/mobile-app-inventory-tracer.git
|
|
144
|
+
cd mobile-app-inventory-tracer
|
|
145
|
+
python3 -m venv .venv
|
|
146
|
+
source .venv/bin/activate
|
|
147
|
+
python -m pip install -r requirements.txt
|
|
148
|
+
python -m pip install -e .
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Set your Azure DevOps token as an environment variable:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
export ADO_PAT="your-token-here"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Quick Start
|
|
158
|
+
|
|
159
|
+
Scan every project in an Azure DevOps organization:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
appsec-scan-router --org PepsiCoIT --out-dir reports
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Scan one project:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
appsec-scan-router --org PepsiCoIT --project "Go_To_Market" --out-dir reports
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Only include medium and high confidence matches:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
appsec-scan-router --org PepsiCoIT --out-dir reports --min-confidence medium
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Fast profile for very large organizations:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
appsec-scan-router \
|
|
181
|
+
--org PepsiCoIT \
|
|
182
|
+
--out-dir reports \
|
|
183
|
+
--min-confidence medium \
|
|
184
|
+
--max-workers 12 \
|
|
185
|
+
--branch-workers 32 \
|
|
186
|
+
--content-workers 32 \
|
|
187
|
+
--activity-mode latest
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Enable public store enrichment:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
appsec-scan-router --org PepsiCoIT --out-dir reports --store-lookup --store-country US
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Legacy commands remain available for compatibility:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
mobile-app-inventory-tracer --org PepsiCoIT --out-dir reports
|
|
200
|
+
ado-mobile-scanner --org PepsiCoIT --out-dir reports
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
You can also run from source:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
python -m appsec_scan_router --org PepsiCoIT --out-dir reports
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Docker
|
|
210
|
+
|
|
211
|
+
Build the image:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
docker build -t appsec-scan-router .
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Run a scan and write reports to a local directory:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
mkdir -p reports
|
|
221
|
+
docker run --rm \
|
|
222
|
+
-e ADO_PAT="$ADO_PAT" \
|
|
223
|
+
-v "$PWD/reports:/reports" \
|
|
224
|
+
appsec-scan-router \
|
|
225
|
+
--org PepsiCoIT \
|
|
226
|
+
--out-dir /reports \
|
|
227
|
+
--min-confidence medium
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Run with public store enrichment:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
docker run --rm \
|
|
234
|
+
-e ADO_PAT="$ADO_PAT" \
|
|
235
|
+
-v "$PWD/reports:/reports" \
|
|
236
|
+
appsec-scan-router \
|
|
237
|
+
--org PepsiCoIT \
|
|
238
|
+
--out-dir /reports \
|
|
239
|
+
--store-lookup \
|
|
240
|
+
--store-country US
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The container runs as a non-root `scanner` user and writes to `/reports`.
|
|
244
|
+
|
|
245
|
+
## CLI Reference
|
|
246
|
+
|
|
247
|
+
| Option | Required | Default | Description |
|
|
248
|
+
| --- | --- | --- | --- |
|
|
249
|
+
| `--org` | Yes | | Azure DevOps organization name |
|
|
250
|
+
| `--project` | No | all projects | Project name to scan |
|
|
251
|
+
| `--pat` | No | `ADO_PAT` | Azure DevOps PAT; prefer the environment variable |
|
|
252
|
+
| `--out-dir` | No | current directory | Output directory |
|
|
253
|
+
| `--out-prefix` | No | `appsec_scan_router` | Output filename prefix |
|
|
254
|
+
| `--max-workers` | No | `8` | Concurrent repository preparation tasks |
|
|
255
|
+
| `--branch-workers` | No | `16` | Concurrent resolved-branch scans |
|
|
256
|
+
| `--content-workers` | No | `16` | Concurrent selected-file fetches |
|
|
257
|
+
| `--max-commits-per-repo` | No | `0` | Commit history limit per matched branch; `0` means all available history |
|
|
258
|
+
| `--timeout` | No | `30` | Azure DevOps HTTP timeout in seconds |
|
|
259
|
+
| `--min-confidence` | No | `low` | Minimum detection confidence: `low`, `medium`, or `high` |
|
|
260
|
+
| `--branch-age-days` | No | `90` | Active/older worksheet cutoff |
|
|
261
|
+
| `--activity-mode` | No | `contributors` | `contributors` walks configured commit history; `latest` only fetches the latest commit |
|
|
262
|
+
| `--store-lookup` | No | disabled | Enable public app store enrichment |
|
|
263
|
+
| `--store-country` | No | `US` | Two-letter public store country code |
|
|
264
|
+
| `--store-timeout` | No | `15` | Store lookup HTTP timeout in seconds |
|
|
265
|
+
| `--verbose` | No | disabled | Enable debug logging |
|
|
266
|
+
|
|
267
|
+
## Outputs
|
|
268
|
+
|
|
269
|
+
The scanner creates output files as soon as the run starts and appends matching rows as resolved branches are detected:
|
|
270
|
+
|
|
271
|
+
- `appsec_scan_router.csv`
|
|
272
|
+
- `appsec_scan_router.json`
|
|
273
|
+
- `appsec_scan_router.xlsx`
|
|
274
|
+
|
|
275
|
+
The Excel workbook includes:
|
|
276
|
+
|
|
277
|
+
- `Active 90d`: matched app branches changed within the active window
|
|
278
|
+
- `Older 90d`: matched app branches with no changes inside the active window
|
|
279
|
+
|
|
280
|
+
If you change `--branch-age-days`, worksheet names change accordingly, such as `Active 60d` and `Older 60d`.
|
|
281
|
+
|
|
282
|
+
## Output Schema
|
|
283
|
+
|
|
284
|
+
Core inventory fields:
|
|
285
|
+
|
|
286
|
+
| Field | Description |
|
|
287
|
+
| --- | --- |
|
|
288
|
+
| `project` | Azure DevOps project name |
|
|
289
|
+
| `repo_name` | Repository name |
|
|
290
|
+
| `branch_name` | Resolved repository branch where the app was detected |
|
|
291
|
+
| `branch_last_updated` | Latest branch commit timestamp seen by the scanner |
|
|
292
|
+
| `branch_age_bucket` | Active/older age bucket |
|
|
293
|
+
| `web_url` | Azure DevOps repository URL |
|
|
294
|
+
| `mobile_name` | Best-effort app display name |
|
|
295
|
+
| `mobile_version` | Best-effort app version |
|
|
296
|
+
| `mobile_identifier` | Best-effort bundle/package identifier |
|
|
297
|
+
| `mobile_identifier_source` | Source family where the identifier was found |
|
|
298
|
+
| `mobile_identifier_status` | `found` or `missing_from_scanned_files` |
|
|
299
|
+
| `contributing_developers` | Semicolon-separated unique commit authors |
|
|
300
|
+
| `last_updated` | Same value as `branch_last_updated`, retained for compatibility |
|
|
301
|
+
| `confidence` | Detection confidence |
|
|
302
|
+
| `score` | Weighted evidence score |
|
|
303
|
+
| `categories` | Semicolon-separated matched category names |
|
|
304
|
+
| `category_*` | Excel-filter-friendly `TRUE` / `FALSE` columns |
|
|
305
|
+
| `detection_evidence` | JSON evidence details used for classification |
|
|
306
|
+
|
|
307
|
+
Store enrichment fields:
|
|
308
|
+
|
|
309
|
+
| Field | Description |
|
|
310
|
+
| --- | --- |
|
|
311
|
+
| `store_lookup_status` | Aggregate store lookup status |
|
|
312
|
+
| `store_validation_passed` | `TRUE` when all requested store validations found a public listing |
|
|
313
|
+
| `store_platforms` | Stores where a public listing was found |
|
|
314
|
+
| `apple_app_store_name` | Public Apple App Store app name |
|
|
315
|
+
| `apple_app_store_identifier` | Bundle identifier returned by Apple |
|
|
316
|
+
| `apple_app_store_url` | Public Apple App Store URL |
|
|
317
|
+
| `apple_app_store_version` | Public Apple App Store version |
|
|
318
|
+
| `apple_app_store_last_updated` | Public Apple App Store release/update timestamp |
|
|
319
|
+
| `apple_app_store_validation_passed` | `TRUE` when Apple lookup found a public listing |
|
|
320
|
+
| `apple_app_store_lookup_status` | Apple lookup status |
|
|
321
|
+
| `google_play_name` | Public Google Play app name |
|
|
322
|
+
| `google_play_identifier` | Google Play package identifier checked |
|
|
323
|
+
| `google_play_url` | Public Google Play URL |
|
|
324
|
+
| `google_play_version` | Best-effort version from public page metadata |
|
|
325
|
+
| `google_play_last_updated` | Best-effort update date from public page metadata |
|
|
326
|
+
| `google_play_validation_passed` | `TRUE` when Google Play lookup found a public listing |
|
|
327
|
+
| `google_play_lookup_status` | Google Play public-page lookup status |
|
|
328
|
+
|
|
329
|
+
Validation fields are always `TRUE` or `FALSE`. They are `FALSE` when lookup is disabled, the identifier is missing, the public listing is not found, or the lookup returns an error.
|
|
330
|
+
|
|
331
|
+
## SDK Usage
|
|
332
|
+
|
|
333
|
+
The scanner can be embedded in another Python application through the `appsec_scan_router` SDK.
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
from pathlib import Path
|
|
337
|
+
|
|
338
|
+
from appsec_scan_router import ScanConfig, scan_to_reports
|
|
339
|
+
|
|
340
|
+
config = ScanConfig(
|
|
341
|
+
org="PepsiCoIT",
|
|
342
|
+
pat="your-token-here",
|
|
343
|
+
project=None,
|
|
344
|
+
out_dir=Path("reports"),
|
|
345
|
+
out_prefix="appsec_scan_router",
|
|
346
|
+
max_workers=8,
|
|
347
|
+
branch_workers=16,
|
|
348
|
+
content_workers=16,
|
|
349
|
+
max_commits_per_repo=2000,
|
|
350
|
+
timeout_seconds=30,
|
|
351
|
+
min_confidence="medium",
|
|
352
|
+
branch_age_days=90,
|
|
353
|
+
activity_mode="contributors",
|
|
354
|
+
store_lookup=True,
|
|
355
|
+
store_country="US",
|
|
356
|
+
store_timeout_seconds=15,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
results, csv_path, json_path, xlsx_path = scan_to_reports(config)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Use the SDK facade when an object-oriented boundary is cleaner for your application:
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
from appsec_scan_router import AppSecScanRouter
|
|
366
|
+
|
|
367
|
+
router = AppSecScanRouter(config)
|
|
368
|
+
results, csv_path, json_path, xlsx_path = router.scan_to_reports()
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Use `scan(config)` to receive rows without writing reports:
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
from appsec_scan_router import scan
|
|
375
|
+
|
|
376
|
+
rows = scan(config)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Stream rows into another process:
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
from appsec_scan_router import scan
|
|
383
|
+
|
|
384
|
+
def handle_row(row):
|
|
385
|
+
print(row["project"], row["repo_name"], row["branch_name"], row["mobile_identifier"])
|
|
386
|
+
|
|
387
|
+
rows = scan(config, on_result=handle_row)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
The legacy `mobile_scanner` and `ado_mobile_scanner` imports remain available as compatibility aliases, but new integrations should import `appsec_scan_router`.
|
|
391
|
+
|
|
392
|
+
## Project Layout
|
|
393
|
+
|
|
394
|
+
```text
|
|
395
|
+
appsec_scan_router/
|
|
396
|
+
activity.py Commit authors and last-updated extraction
|
|
397
|
+
azure.py Azure DevOps REST client
|
|
398
|
+
cli.py CLI argument parsing
|
|
399
|
+
constants.py Shared constants and report schema
|
|
400
|
+
detection.py Evidence-based mobile branch classification
|
|
401
|
+
metadata.py App metadata extraction
|
|
402
|
+
models.py Dataclasses and errors
|
|
403
|
+
reports.py CSV, JSON, and Excel writers
|
|
404
|
+
scanner.py Scan orchestration
|
|
405
|
+
store_lookup.py Optional public app store enrichment
|
|
406
|
+
utils.py Parsing and cleanup helpers
|
|
407
|
+
mobile_scanner/ Compatibility import package
|
|
408
|
+
ado_mobile_scanner.py Compatibility wrapper
|
|
409
|
+
mobile_app_inventory_tracer.py Compatibility wrapper
|
|
410
|
+
tests/ Unit tests
|
|
411
|
+
Dockerfile Container definition
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## Accuracy Notes
|
|
415
|
+
|
|
416
|
+
`mobile_identifier` can be empty when an app identifier is generated outside the files available to the scanner. Common causes include CI/CD variables, private variable groups, build flavors, environment-specific files, secrets, or app catalog packaging steps.
|
|
417
|
+
|
|
418
|
+
`mobile_name` can be empty when the display name is localized, generated, or declared only in native project files that are not present in the scanned branch.
|
|
419
|
+
|
|
420
|
+
`mobile_version` can be empty when versioning is generated by pipeline tasks, Gradle logic, Xcode build settings, or environment-specific files.
|
|
421
|
+
|
|
422
|
+
Placeholder versions such as `999.999.999` are treated as sentinel values and suppressed so they are not mistaken for release versions.
|
|
423
|
+
|
|
424
|
+
Store metadata is not the same as source metadata. App Store and Google Play values reflect public listing data where available; branch timestamps reflect repository activity.
|
|
425
|
+
|
|
426
|
+
## Performance Guidance
|
|
427
|
+
|
|
428
|
+
Start with:
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
appsec-scan-router --org PepsiCoIT --out-dir reports --max-workers 8 --content-workers 16 --min-confidence medium
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
For very large organizations, the scanner uses three independent pools:
|
|
435
|
+
|
|
436
|
+
- `--max-workers` prepares repositories and resolves default or fallback branches
|
|
437
|
+
- `--branch-workers` scans resolved branches after they are prepared
|
|
438
|
+
- `--content-workers` fetches selected manifest and configuration files
|
|
439
|
+
|
|
440
|
+
Increase concurrency only if Azure DevOps responds quickly and throttling is not observed. Reduce it if you see `429`, timeout, or transient service errors.
|
|
441
|
+
|
|
442
|
+
Contributor extraction happens only after a resolved branch passes detection. Use `--max-commits-per-repo` if full commit history is too expensive for large estates.
|
|
443
|
+
|
|
444
|
+
Use `--activity-mode latest` for the fastest large-org inventory pass. It captures `last_updated` from the latest branch commit and leaves `contributing_developers` empty, avoiding full commit-history walks. Use `--activity-mode contributors` when the complete contributor column matters more than speed.
|
|
445
|
+
|
|
446
|
+
Store lookup is also performed only after detection. Leave `--store-lookup` off for the fastest inventory scan.
|
|
447
|
+
|
|
448
|
+
## Security
|
|
449
|
+
|
|
450
|
+
- Use a read-only Azure DevOps PAT scoped to the smallest practical set of projects and repositories
|
|
451
|
+
- Prefer `ADO_PAT` over `--pat` so tokens are not stored in shell history
|
|
452
|
+
- Do not commit generated reports if they may contain internal repository names or contributor emails
|
|
453
|
+
- The scanner does not clone repositories
|
|
454
|
+
- The scanner fetches only allow-listed source/configuration files needed for detection
|
|
455
|
+
- Docker runs as a non-root user
|
|
456
|
+
|
|
457
|
+
## Testing
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
python -m unittest discover -s tests
|
|
461
|
+
python -m compileall ado_mobile_scanner.py mobile_app_inventory_tracer.py appsec_scan_router mobile_scanner tests
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
## Contributing
|
|
465
|
+
|
|
466
|
+
Issues and pull requests are welcome. Useful contributions include:
|
|
467
|
+
|
|
468
|
+
- Additional structured metadata parsers
|
|
469
|
+
- Better evidence rules for mobile frameworks
|
|
470
|
+
- Authenticated Google Play Developer API enrichment
|
|
471
|
+
- Additional report formats
|
|
472
|
+
- Performance improvements for very large Azure DevOps organizations
|
|
473
|
+
|
|
474
|
+
Please include tests for parser, detection, and reporting changes.
|
|
475
|
+
|
|
476
|
+
## License
|
|
477
|
+
|
|
478
|
+
AppSec Scan Router is released under the MIT License. See [LICENSE](LICENSE).
|