narvy-cli 1.0.0__tar.gz → 1.0.2__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.
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/CHANGELOG.md +41 -0
- {narvy_cli-1.0.0/narvy_cli.egg-info → narvy_cli-1.0.2}/PKG-INFO +5 -3
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/README.md +4 -2
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/__init__.py +1 -1
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/apk_memory_preflight.py +3 -3
- narvy_cli-1.0.2/narvy/ci_templates/bitbucket.yml +15 -0
- narvy_cli-1.0.2/narvy/ci_templates/github.yml +48 -0
- narvy_cli-1.0.2/narvy/ci_templates/gitlab.yml +19 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/cloud/aws_scan.py +58 -66
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/doctor.py +6 -6
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/ios/binary_analyzer.py +10 -20
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/main.py +191 -57
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/reporter.py +27 -1
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/android.yml +115 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/ios_swift.yml +34 -1
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/sca/android_deps.py +5 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/sca/ios_deps.py +5 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/sca/osv_client.py +17 -5
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/sca/web_deps.py +5 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/semgrep_engine.py +10 -10
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/web/scanner.py +11 -8
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/web/source_analyzer.py +1 -1
- {narvy_cli-1.0.0 → narvy_cli-1.0.2/narvy_cli.egg-info}/PKG-INFO +5 -3
- narvy_cli-1.0.0/narvy/ci_templates/bitbucket.yml +0 -25
- narvy_cli-1.0.0/narvy/ci_templates/github.yml +0 -60
- narvy_cli-1.0.0/narvy/ci_templates/gitlab.yml +0 -32
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/LICENSE +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/MANIFEST.in +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/SECURITY.md +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/android/__init__.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/android/source_analyzer.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/android/split_bundle.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/android_rule_context.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/auth.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/cloud/__init__.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/comment_filter.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/crypto_taint_lite.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/decompiler.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/__init__.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/audit.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/host_knowledge.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/lynis_bootstrap.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/lynis_parser.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/narvy_checks.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/report.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/ssh_exec.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/ios/__init__.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/ios/plist_checks.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/ios/source_analyzer.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/ios/third_party_filter.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/ios/trust_all_context.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/native_hardening.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rule_engine.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/android/config.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/android/crypto.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/android/secrets.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/android/storage.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/android/webview.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/ios/objc/crypto.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/ios/objc/network.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/ios/objc/secrets.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/ios/objc/storage.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/ios/objc/webview.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/go.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/java.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/javascript.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/kotlin.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/csharp_narvy/config.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/csharp_narvy/crypto.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/csharp_narvy/deserialization.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/csharp_narvy/injection.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/csharp_narvy/xxe.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/auth_jwt.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/deserialization.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/mybatis.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/snakeyaml.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/spring_authz.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/spring_config.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/spring_hardening.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/sqli.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/xxe.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/php.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/python.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/ruby.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/rust.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/secrets.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/secrets_supplement.yml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/sca/__init__.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/scope_config.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/stack_protector_evidence.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/third_party_filter.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/uploader.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/weak_prng_context.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/web/__init__.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/web/nuclei_binary.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/web/scan_blocklist.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/web/ssrf_guard.py +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy_cli.egg-info/SOURCES.txt +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy_cli.egg-info/dependency_links.txt +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy_cli.egg-info/entry_points.txt +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy_cli.egg-info/requires.txt +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy_cli.egg-info/top_level.txt +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/pyproject.toml +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/setup.cfg +0 -0
- {narvy_cli-1.0.0 → narvy_cli-1.0.2}/setup.py +0 -0
|
@@ -5,6 +5,47 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.1]
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- `--fail-on` no longer green-passes a build whose scan silently degraded. An
|
|
13
|
+
SCA run that could not reach OSV.dev (outage / cold cache) is now reported as
|
|
14
|
+
`degraded` and fails the gate (exit 2), not a clean 0. `cloud-scan`,
|
|
15
|
+
`web-scan` and `host-audit` now feed their own coverage gaps (skipped AWS
|
|
16
|
+
analyzers, SSRF-blocked redirect, partial non-root audit) into `--fail-on`.
|
|
17
|
+
- `cloud-scan`: one AccessDenied no longer aborts the remaining analyzers for a
|
|
18
|
+
region; each runs in isolation and skipped checks are reported under
|
|
19
|
+
`coverage` in the output.
|
|
20
|
+
- SARIF output on every surface (`scan`, `web-scan`, `host-audit`, `cloud-scan`)
|
|
21
|
+
now carries a per-result `level` and a `security-severity` property, so
|
|
22
|
+
GitHub/GitLab code scanning can rank findings.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- `web-scan`, `host-audit` and `cloud-scan` now use `-o`/`--output` for the
|
|
27
|
+
format and `-f`/`--file` for the destination, consistent with `scan`. The
|
|
28
|
+
older `--format` / `--output-file` long options still work as aliases. A file
|
|
29
|
+
path that is actually a format name (e.g. `-f json`) is now rejected loudly
|
|
30
|
+
instead of writing a mis-named text file.
|
|
31
|
+
|
|
32
|
+
## [1.0.0]
|
|
33
|
+
|
|
34
|
+
First stable release, published to PyPI as `narvy-cli`.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Distribution name is `narvy-cli` on PyPI (`pip install narvy-cli`); the
|
|
39
|
+
command and import package remain `narvy`.
|
|
40
|
+
- Web/backend rule packs are Narvy-authored and run on the Semgrep OSS engine,
|
|
41
|
+
which is invoked as a dependency.
|
|
42
|
+
|
|
43
|
+
### Notes
|
|
44
|
+
|
|
45
|
+
- Runs entirely on your machine; nothing is uploaded unless you pass `--upload`.
|
|
46
|
+
- Honest coverage limits are surfaced in the tool's own output (for example,
|
|
47
|
+
FairPlay-encrypted iOS binaries and partial multi-ABI native coverage).
|
|
48
|
+
|
|
8
49
|
## [0.9.0]
|
|
9
50
|
|
|
10
51
|
First public release. A free, local security scanner that runs entirely on your
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: narvy-cli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Free, local SAST scanner for Android, iOS, and polyglot web/backend source - plus passive web, host-audit, and cloud-posture scanning.
|
|
5
5
|
Author: Narvy
|
|
6
6
|
License: Apache-2.0
|
|
@@ -161,5 +161,7 @@ as Narvy's own.
|
|
|
161
161
|
## Contributing
|
|
162
162
|
|
|
163
163
|
Issues and pull requests welcome, especially new or improved detection
|
|
164
|
-
rules.
|
|
165
|
-
(`rules/android`, `rules/ios`) and web/backend (`rules/web
|
|
164
|
+
rules. All rule packs are Narvy's own, under `narvy/rules/`: Android/iOS
|
|
165
|
+
(`rules/android`, `rules/ios`) and web/backend (`rules/web`). The
|
|
166
|
+
web/backend packs run on the Semgrep OSS engine, which Narvy invokes as a
|
|
167
|
+
dependency; the rules themselves are authored by Narvy (Apache-2.0).
|
|
@@ -128,5 +128,7 @@ as Narvy's own.
|
|
|
128
128
|
## Contributing
|
|
129
129
|
|
|
130
130
|
Issues and pull requests welcome, especially new or improved detection
|
|
131
|
-
rules.
|
|
132
|
-
(`rules/android`, `rules/ios`) and web/backend (`rules/web
|
|
131
|
+
rules. All rule packs are Narvy's own, under `narvy/rules/`: Android/iOS
|
|
132
|
+
(`rules/android`, `rules/ios`) and web/backend (`rules/web`). The
|
|
133
|
+
web/backend packs run on the Semgrep OSS engine, which Narvy invokes as a
|
|
134
|
+
dependency; the rules themselves are authored by Narvy (Apache-2.0).
|
|
@@ -209,9 +209,9 @@ def check_memory_preflight(apk_path: str, max_mem: str,
|
|
|
209
209
|
f"\n\nRaising --max-mem on its own will not be enough: {suggest_gb}g of heap needs "
|
|
210
210
|
f"roughly {_fmt_mb(need_rss_mb)} of real memory, and this machine only has "
|
|
211
211
|
f"{_fmt_mb(available_mb)} available right now. Options:\n"
|
|
212
|
-
f" -
|
|
213
|
-
f" -
|
|
214
|
-
f" - run it anyway with --force if
|
|
212
|
+
f" - use the hosted scan (`--upload`): the platform decompiles any app size with no local memory ceiling\n"
|
|
213
|
+
f" - or free up that much memory (close other apps) and re-run with --max-mem {suggest_gb}g\n"
|
|
214
|
+
f" - or run it anyway with --force if your setup can take it"
|
|
215
215
|
)
|
|
216
216
|
else:
|
|
217
217
|
avail_note = (f" (this machine has {_fmt_mb(available_mb)} available)"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Narvy SAST for Bitbucket Pipelines. Written by `narvy init-ci --bitbucket`; edit freely.
|
|
2
|
+
# Already have a bitbucket-pipelines.yml? Copy the step below into it instead of overwriting.
|
|
3
|
+
# Note: SARIF is stored as a build artifact (not Code Insights); the --fail-on gate still fails the build.
|
|
4
|
+
image: python:3.11
|
|
5
|
+
|
|
6
|
+
pipelines:
|
|
7
|
+
default:
|
|
8
|
+
- step:
|
|
9
|
+
name: Narvy SAST
|
|
10
|
+
script:
|
|
11
|
+
- pip install --upgrade pip
|
|
12
|
+
- pip install narvy-cli
|
|
13
|
+
- narvy scan . --output sarif --file narvy-results.sarif --fail-on high
|
|
14
|
+
artifacts:
|
|
15
|
+
- narvy-results.sarif
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Narvy SAST for GitHub Actions. Written by `narvy init-ci --github`; edit freely.
|
|
2
|
+
# Scans this repo, uploads SARIF to the Security tab, and fails the build on --fail-on.
|
|
3
|
+
name: Narvy SAST
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [ main, master ]
|
|
8
|
+
pull_request:
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
security-events: write # required to upload SARIF to code scanning
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
narvy:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: '3.11'
|
|
24
|
+
|
|
25
|
+
- name: Install Narvy CLI
|
|
26
|
+
run: |
|
|
27
|
+
python -m pip install --upgrade pip
|
|
28
|
+
pip install narvy-cli
|
|
29
|
+
|
|
30
|
+
- name: Run Narvy scan
|
|
31
|
+
# SARIF is written before the --fail-on gate, so the Security tab fills even on a failing build.
|
|
32
|
+
run: |
|
|
33
|
+
narvy scan . \
|
|
34
|
+
--output sarif \
|
|
35
|
+
--file narvy-results.sarif \
|
|
36
|
+
--fail-on high
|
|
37
|
+
|
|
38
|
+
- name: Upload SARIF to GitHub code scanning
|
|
39
|
+
if: always() # upload even when the scan step failed the build via --fail-on
|
|
40
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
41
|
+
with:
|
|
42
|
+
sarif_file: narvy-results.sarif
|
|
43
|
+
|
|
44
|
+
# To also scan a web app URL, add a step:
|
|
45
|
+
# - run: narvy web-scan "https://staging.example.com" --format sarif --output-file web.sarif --fail-on high
|
|
46
|
+
# - if: always()
|
|
47
|
+
# uses: github/codeql-action/upload-sarif@v3
|
|
48
|
+
# with: { sarif_file: web.sarif, category: narvy-web }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Narvy SAST for GitLab CI. Written by `narvy init-ci --gitlab`; edit freely.
|
|
2
|
+
# Already have a .gitlab-ci.yml? Copy the narvy_sast job below into it instead of overwriting.
|
|
3
|
+
# Note: SARIF is stored as a plain artifact (not GitLab's SAST schema), but the --fail-on gate still blocks the MR.
|
|
4
|
+
stages:
|
|
5
|
+
- test
|
|
6
|
+
|
|
7
|
+
narvy_sast:
|
|
8
|
+
stage: test
|
|
9
|
+
image: python:3.11-slim
|
|
10
|
+
before_script:
|
|
11
|
+
- pip install --upgrade pip
|
|
12
|
+
- pip install narvy-cli
|
|
13
|
+
script:
|
|
14
|
+
- narvy scan . --output sarif --file narvy-results.sarif --fail-on high
|
|
15
|
+
artifacts:
|
|
16
|
+
when: always # keep the SARIF even when --fail-on failed the pipeline
|
|
17
|
+
paths:
|
|
18
|
+
- narvy-results.sarif
|
|
19
|
+
expire_in: 1 week
|
|
@@ -1055,6 +1055,18 @@ def analyze_aws_security(credentials, region=None, endpoint_url=None):
|
|
|
1055
1055
|
# letting the per-check handlers below report an empty, clean scan.
|
|
1056
1056
|
_verify_credentials(endpoint_url=endpoint_url)
|
|
1057
1057
|
|
|
1058
|
+
# Track every analyzer that could not complete (AccessDenied / throttling /
|
|
1059
|
+
# missing permission) so 0 findings from a blinded scan is reported as a
|
|
1060
|
+
# coverage gap, not as a clean pass.
|
|
1061
|
+
coverage_errors = []
|
|
1062
|
+
|
|
1063
|
+
def _collect(dest, label, fn):
|
|
1064
|
+
try:
|
|
1065
|
+
dest.extend(fn())
|
|
1066
|
+
except Exception as e:
|
|
1067
|
+
coverage_errors.append(f"{label}: {e}")
|
|
1068
|
+
logger.error(f"Error analyzing {label}: {e}")
|
|
1069
|
+
|
|
1058
1070
|
all_vulnerable_groups = []
|
|
1059
1071
|
all_vulnerable_vpcs = []
|
|
1060
1072
|
all_vulnerable_rds = []
|
|
@@ -1085,84 +1097,56 @@ def analyze_aws_security(credentials, region=None, endpoint_url=None):
|
|
|
1085
1097
|
logger.info(f"Scanning {len(regions)} region(s)")
|
|
1086
1098
|
except Exception as e:
|
|
1087
1099
|
logger.error(f"Error getting regions: {str(e)}")
|
|
1100
|
+
coverage_errors.append(f"region enumeration (ec2:DescribeRegions): {e}")
|
|
1088
1101
|
regions = []
|
|
1089
1102
|
|
|
1103
|
+
# Each analyzer is isolated: one AccessDenied must not skip its siblings for the region.
|
|
1090
1104
|
for reg in regions:
|
|
1091
1105
|
logger.info(f"Analyzing region: {reg}")
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
all_waf_issues.extend(analyze_waf(reg, endpoint_url=endpoint_url))
|
|
1111
|
-
except Exception as e:
|
|
1112
|
-
logger.error(f"Error analyzing region {reg}: {str(e)}")
|
|
1106
|
+
_collect(all_vulnerable_groups, f"{reg}/ec2_security_groups", lambda r=reg: analyze_security_groups(r, endpoint_url=endpoint_url))
|
|
1107
|
+
_collect(all_vulnerable_vpcs, f"{reg}/vpc_configuration", lambda r=reg: analyze_vpc_configuration(r, endpoint_url=endpoint_url))
|
|
1108
|
+
_collect(all_vulnerable_rds, f"{reg}/rds_instances", lambda r=reg: analyze_rds_instances(r, endpoint_url=endpoint_url))
|
|
1109
|
+
_collect(all_vulnerable_lambdas, f"{reg}/lambda_functions", lambda r=reg: analyze_lambda_functions(r, endpoint_url=endpoint_url))
|
|
1110
|
+
_collect(all_vulnerable_ecs, f"{reg}/ecs_clusters", lambda r=reg: analyze_ecs_clusters(r, endpoint_url=endpoint_url))
|
|
1111
|
+
_collect(all_vulnerable_eb, f"{reg}/elastic_beanstalk", lambda r=reg: analyze_elastic_beanstalk(r, endpoint_url=endpoint_url))
|
|
1112
|
+
_collect(all_vulnerable_apis, f"{reg}/api_gateway", lambda r=reg: analyze_api_gateway(r, endpoint_url=endpoint_url))
|
|
1113
|
+
_collect(all_vulnerable_dynamodb, f"{reg}/dynamodb", lambda r=reg: analyze_dynamodb(r, endpoint_url=endpoint_url))
|
|
1114
|
+
_collect(all_gd_issues, f"{reg}/guardduty", lambda r=reg: analyze_guardduty(r, endpoint_url=endpoint_url))
|
|
1115
|
+
_collect(all_vulnerable_eks, f"{reg}/eks_clusters", lambda r=reg: analyze_eks(r, endpoint_url=endpoint_url))
|
|
1116
|
+
_collect(all_vulnerable_redshift, f"{reg}/redshift_clusters", lambda r=reg: analyze_redshift(r, endpoint_url=endpoint_url))
|
|
1117
|
+
_collect(all_vulnerable_secrets, f"{reg}/secrets_manager", lambda r=reg: analyze_secrets_manager(r, endpoint_url=endpoint_url))
|
|
1118
|
+
_collect(all_vulnerable_sqs, f"{reg}/sqs_queues", lambda r=reg: analyze_sqs(r, endpoint_url=endpoint_url))
|
|
1119
|
+
_collect(all_vulnerable_sns, f"{reg}/sns_topics", lambda r=reg: analyze_sns(r, endpoint_url=endpoint_url))
|
|
1120
|
+
_collect(all_vulnerable_glue_jobs, f"{reg}/glue_jobs", lambda r=reg: analyze_glue(r, endpoint_url=endpoint_url))
|
|
1121
|
+
_collect(all_vulnerable_emr_clusters, f"{reg}/emr_clusters", lambda r=reg: analyze_emr(r, endpoint_url=endpoint_url))
|
|
1122
|
+
_collect(all_vulnerable_kms_keys, f"{reg}/kms_keys", lambda r=reg: analyze_kms(r, endpoint_url=endpoint_url))
|
|
1123
|
+
_collect(all_waf_issues, f"{reg}/waf", lambda r=reg: analyze_waf(r, endpoint_url=endpoint_url))
|
|
1113
1124
|
|
|
1114
1125
|
# Account-scoped checks: run once against the primary region only.
|
|
1115
1126
|
primary_region = regions[0] if regions else (region or 'us-east-1')
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
except Exception as e:
|
|
1120
|
-
logger.error(f"Error analyzing CloudTrail: {str(e)}")
|
|
1121
|
-
try:
|
|
1122
|
-
all_config_issues.extend(
|
|
1123
|
-
analyze_config(primary_region, endpoint_url=endpoint_url))
|
|
1124
|
-
except Exception as e:
|
|
1125
|
-
logger.error(f"Error analyzing Config: {str(e)}")
|
|
1126
|
-
try:
|
|
1127
|
-
all_cloudwatch_issues.extend(
|
|
1128
|
-
analyze_cloudwatch(primary_region, endpoint_url=endpoint_url))
|
|
1129
|
-
except Exception as e:
|
|
1130
|
-
logger.error(f"Error analyzing CloudWatch: {str(e)}")
|
|
1127
|
+
_collect(all_cloudtrail_issues, "cloudtrail", lambda: analyze_cloudtrail(primary_region, endpoint_url=endpoint_url))
|
|
1128
|
+
_collect(all_config_issues, "config", lambda: analyze_config(primary_region, endpoint_url=endpoint_url))
|
|
1129
|
+
_collect(all_cloudwatch_issues, "cloudwatch", lambda: analyze_cloudwatch(primary_region, endpoint_url=endpoint_url))
|
|
1131
1130
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
except Exception as e:
|
|
1136
|
-
logger.error(f"Error analyzing S3 buckets: {str(e)}")
|
|
1137
|
-
vulnerable_buckets = []
|
|
1131
|
+
vulnerable_buckets = []
|
|
1132
|
+
logger.info("Analyzing S3 Buckets...")
|
|
1133
|
+
_collect(vulnerable_buckets, "s3_buckets", lambda: analyze_s3_buckets(endpoint_url=endpoint_url))
|
|
1138
1134
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
except Exception as e:
|
|
1143
|
-
logger.error(f"Error analyzing IAM users: {str(e)}")
|
|
1144
|
-
users_without_mfa = []
|
|
1135
|
+
users_without_mfa = []
|
|
1136
|
+
logger.info("Analyzing IAM Users for MFA...")
|
|
1137
|
+
_collect(users_without_mfa, "iam_users", lambda: analyze_iam_users(endpoint_url=endpoint_url))
|
|
1145
1138
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
except Exception as e:
|
|
1150
|
-
logger.error(f"Error analyzing IAM policies: {str(e)}")
|
|
1151
|
-
risky_policies = []
|
|
1139
|
+
risky_policies = []
|
|
1140
|
+
logger.info("Analyzing IAM Policies...")
|
|
1141
|
+
_collect(risky_policies, "iam_policies", lambda: analyze_iam_policies(endpoint_url=endpoint_url))
|
|
1152
1142
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
except Exception as e:
|
|
1157
|
-
logger.error(f"Error analyzing CloudFront: {str(e)}")
|
|
1158
|
-
vulnerable_cf = []
|
|
1143
|
+
vulnerable_cf = []
|
|
1144
|
+
logger.info("Analyzing CloudFront Distributions...")
|
|
1145
|
+
_collect(vulnerable_cf, "cloudfront", lambda: analyze_cloudfront(endpoint_url=endpoint_url))
|
|
1159
1146
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
except Exception as e:
|
|
1164
|
-
logger.error(f"Error checking CIS compliance: {str(e)}")
|
|
1165
|
-
compliance_issues = []
|
|
1147
|
+
compliance_issues = []
|
|
1148
|
+
logger.info("Checking CIS Compliance...")
|
|
1149
|
+
_collect(compliance_issues, "cis_compliance", lambda: check_cis_compliance(endpoint_url=endpoint_url))
|
|
1166
1150
|
|
|
1167
1151
|
logger.info("Generating report")
|
|
1168
1152
|
report = generate_report(
|
|
@@ -1175,6 +1159,14 @@ def analyze_aws_security(credentials, region=None, endpoint_url=None):
|
|
|
1175
1159
|
all_vulnerable_kms_keys, all_config_issues, all_cloudtrail_issues, all_cloudwatch_issues
|
|
1176
1160
|
)
|
|
1177
1161
|
|
|
1162
|
+
# Coverage: a scan that could not read some services/regions is NOT a clean 0.
|
|
1163
|
+
report["coverage"] = {
|
|
1164
|
+
"status": "partial" if coverage_errors else "complete",
|
|
1165
|
+
"regions_scanned": len(regions),
|
|
1166
|
+
"checks_skipped": len(coverage_errors),
|
|
1167
|
+
"errors": coverage_errors,
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1178
1170
|
logger.info("AWS security analysis complete")
|
|
1179
1171
|
return report
|
|
1180
1172
|
|
|
@@ -87,7 +87,7 @@ def _check_icdump():
|
|
|
87
87
|
else:
|
|
88
88
|
detail = "Not installed - iOS binary scans will fall back to symbol-table-only Obj-C detection"
|
|
89
89
|
return True, detail, (
|
|
90
|
-
"Optional: `pip install narvy[ios-full]` (Linux/macOS/WSL only - no Windows wheel exists) "
|
|
90
|
+
"Optional: `pip install narvy-cli[ios-full]` (Linux/macOS/WSL only - no Windows wheel exists) "
|
|
91
91
|
"for full Obj-C class/method/property introspection on .ipa scans. Not required - iOS binary "
|
|
92
92
|
"scanning still works without it, just with coarser Obj-C detection (class names only, via LIEF's "
|
|
93
93
|
"symbol table, no methods/properties/protocols)."
|
|
@@ -110,10 +110,10 @@ def _check_memory():
|
|
|
110
110
|
)
|
|
111
111
|
if avail < 5 * 1024:
|
|
112
112
|
return True, detail, (
|
|
113
|
-
"Large commercial apps
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
113
|
+
"Large commercial apps need 8-24 GB of free memory to decompile locally. "
|
|
114
|
+
"The hosted scan (`--upload`) removes the local memory ceiling and handles "
|
|
115
|
+
"any app size - use it for large apps, or scan locally within this "
|
|
116
|
+
"machine's memory."
|
|
117
117
|
)
|
|
118
118
|
return True, detail, None
|
|
119
119
|
|
|
@@ -183,7 +183,7 @@ def _check_boto3():
|
|
|
183
183
|
except ImportError:
|
|
184
184
|
return True, "Not installed", (
|
|
185
185
|
"Needed only for `narvy cloud-scan`. Run: "
|
|
186
|
-
"pip install narvy[cloud]"
|
|
186
|
+
"pip install narvy-cli[cloud]"
|
|
187
187
|
)
|
|
188
188
|
|
|
189
189
|
|
|
@@ -1021,25 +1021,15 @@ def analyze_ipa(ipa_path: str, override_config: Optional[ScopeConfig] = None) ->
|
|
|
1021
1021
|
result["encrypted"] = encrypted
|
|
1022
1022
|
if encrypted:
|
|
1023
1023
|
result["encrypted_message"] = (
|
|
1024
|
-
"This IPA is FairPlay-encrypted (Apple
|
|
1025
|
-
"
|
|
1026
|
-
"
|
|
1027
|
-
"
|
|
1028
|
-
"
|
|
1029
|
-
"
|
|
1030
|
-
"
|
|
1031
|
-
"run
|
|
1032
|
-
"
|
|
1033
|
-
"(load commands are never encrypted), and Objective-C class/method "
|
|
1034
|
-
"metadata recovery (this metadata typically lives outside the "
|
|
1035
|
-
"encrypted __TEXT range). What is unreliable: weak-"
|
|
1036
|
-
"crypto/hardcoded-secret/dangerous-API string matching against the "
|
|
1037
|
-
"app's OWN literal string constants, since those live inside the "
|
|
1038
|
-
"encrypted region and read as ciphertext noise - expect a near-empty "
|
|
1039
|
-
"result for those specific checks, not a false 'clean bill of "
|
|
1040
|
-
"health'. The ONLY way to read that encrypted code is a decrypted "
|
|
1041
|
-
"binary (a jailbroken-device dump). `--upload` does NOT decrypt "
|
|
1042
|
-
"anything: the same encrypted binary is analysed, not decrypted."
|
|
1024
|
+
"This IPA is FairPlay-encrypted (Apple App Store DRM). Narvy covers "
|
|
1025
|
+
"it across two surfaces. The hosted engine runs its full rule set + "
|
|
1026
|
+
"ML triage + MASVS mapping over the readable, cleartext surface "
|
|
1027
|
+
"(Info.plist, entitlements, URL schemes / ATS, linked and embedded "
|
|
1028
|
+
"frameworks, Objective-C class/method metadata), and Narvy dynamic "
|
|
1029
|
+
"analysis executes the app decrypted on a real device to cover the "
|
|
1030
|
+
"app's own code and strings. This local run reads the cleartext "
|
|
1031
|
+
"surface only - run `narvy scan --upload` and Narvy dynamic analysis "
|
|
1032
|
+
"on the platform for the complete picture of this app."
|
|
1043
1033
|
)
|
|
1044
1034
|
|
|
1045
1035
|
findings = _analyze_one_binary(main_binary_path, exe_name, is_main_binary=True)
|
|
@@ -1087,7 +1077,7 @@ def analyze_ipa(ipa_path: str, override_config: Optional[ScopeConfig] = None) ->
|
|
|
1087
1077
|
result["notes"].append(
|
|
1088
1078
|
"icdump not installed - full Obj-C class/method/property "
|
|
1089
1079
|
"introspection needs Linux/macOS/WSL "
|
|
1090
|
-
"(pip install narvy[ios-full]). Falling back to binary "
|
|
1080
|
+
"(pip install narvy-cli[ios-full]). Falling back to binary "
|
|
1091
1081
|
"symbol-table scanning only."
|
|
1092
1082
|
)
|
|
1093
1083
|
result["icdump_used"] = icdump_used
|