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.
Files changed (105) hide show
  1. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/CHANGELOG.md +41 -0
  2. {narvy_cli-1.0.0/narvy_cli.egg-info → narvy_cli-1.0.2}/PKG-INFO +5 -3
  3. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/README.md +4 -2
  4. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/__init__.py +1 -1
  5. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/apk_memory_preflight.py +3 -3
  6. narvy_cli-1.0.2/narvy/ci_templates/bitbucket.yml +15 -0
  7. narvy_cli-1.0.2/narvy/ci_templates/github.yml +48 -0
  8. narvy_cli-1.0.2/narvy/ci_templates/gitlab.yml +19 -0
  9. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/cloud/aws_scan.py +58 -66
  10. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/doctor.py +6 -6
  11. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/ios/binary_analyzer.py +10 -20
  12. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/main.py +191 -57
  13. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/reporter.py +27 -1
  14. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/android.yml +115 -0
  15. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/ios_swift.yml +34 -1
  16. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/sca/android_deps.py +5 -0
  17. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/sca/ios_deps.py +5 -0
  18. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/sca/osv_client.py +17 -5
  19. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/sca/web_deps.py +5 -0
  20. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/semgrep_engine.py +10 -10
  21. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/web/scanner.py +11 -8
  22. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/web/source_analyzer.py +1 -1
  23. {narvy_cli-1.0.0 → narvy_cli-1.0.2/narvy_cli.egg-info}/PKG-INFO +5 -3
  24. narvy_cli-1.0.0/narvy/ci_templates/bitbucket.yml +0 -25
  25. narvy_cli-1.0.0/narvy/ci_templates/github.yml +0 -60
  26. narvy_cli-1.0.0/narvy/ci_templates/gitlab.yml +0 -32
  27. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/LICENSE +0 -0
  28. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/MANIFEST.in +0 -0
  29. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/SECURITY.md +0 -0
  30. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/android/__init__.py +0 -0
  31. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/android/source_analyzer.py +0 -0
  32. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/android/split_bundle.py +0 -0
  33. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/android_rule_context.py +0 -0
  34. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/auth.py +0 -0
  35. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/cloud/__init__.py +0 -0
  36. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/comment_filter.py +0 -0
  37. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/crypto_taint_lite.py +0 -0
  38. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/decompiler.py +0 -0
  39. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/__init__.py +0 -0
  40. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/audit.py +0 -0
  41. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/host_knowledge.py +0 -0
  42. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/lynis_bootstrap.py +0 -0
  43. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/lynis_parser.py +0 -0
  44. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/narvy_checks.py +0 -0
  45. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/report.py +0 -0
  46. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/host/ssh_exec.py +0 -0
  47. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/ios/__init__.py +0 -0
  48. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/ios/plist_checks.py +0 -0
  49. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/ios/source_analyzer.py +0 -0
  50. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/ios/third_party_filter.py +0 -0
  51. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/ios/trust_all_context.py +0 -0
  52. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/native_hardening.py +0 -0
  53. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rule_engine.py +0 -0
  54. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/android/config.yml +0 -0
  55. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/android/crypto.yml +0 -0
  56. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/android/secrets.yml +0 -0
  57. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/android/storage.yml +0 -0
  58. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/android/webview.yml +0 -0
  59. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/ios/objc/crypto.yml +0 -0
  60. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/ios/objc/network.yml +0 -0
  61. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/ios/objc/secrets.yml +0 -0
  62. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/ios/objc/storage.yml +0 -0
  63. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/ios/objc/webview.yml +0 -0
  64. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/go.yml +0 -0
  65. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/java.yml +0 -0
  66. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/javascript.yml +0 -0
  67. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/kotlin.yml +0 -0
  68. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/csharp_narvy/config.yml +0 -0
  69. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/csharp_narvy/crypto.yml +0 -0
  70. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/csharp_narvy/deserialization.yml +0 -0
  71. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/csharp_narvy/injection.yml +0 -0
  72. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/csharp_narvy/xxe.yml +0 -0
  73. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/auth_jwt.yml +0 -0
  74. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/deserialization.yml +0 -0
  75. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/mybatis.yml +0 -0
  76. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/snakeyaml.yml +0 -0
  77. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/spring_authz.yml +0 -0
  78. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/spring_config.yml +0 -0
  79. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/spring_hardening.yml +0 -0
  80. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/sqli.yml +0 -0
  81. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/local/java_narvy/xxe.yml +0 -0
  82. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/php.yml +0 -0
  83. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/python.yml +0 -0
  84. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/ruby.yml +0 -0
  85. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/rust.yml +0 -0
  86. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/secrets.yml +0 -0
  87. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/rules/web/secrets_supplement.yml +0 -0
  88. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/sca/__init__.py +0 -0
  89. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/scope_config.py +0 -0
  90. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/stack_protector_evidence.py +0 -0
  91. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/third_party_filter.py +0 -0
  92. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/uploader.py +0 -0
  93. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/weak_prng_context.py +0 -0
  94. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/web/__init__.py +0 -0
  95. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/web/nuclei_binary.py +0 -0
  96. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/web/scan_blocklist.py +0 -0
  97. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy/web/ssrf_guard.py +0 -0
  98. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy_cli.egg-info/SOURCES.txt +0 -0
  99. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy_cli.egg-info/dependency_links.txt +0 -0
  100. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy_cli.egg-info/entry_points.txt +0 -0
  101. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy_cli.egg-info/requires.txt +0 -0
  102. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/narvy_cli.egg-info/top_level.txt +0 -0
  103. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/pyproject.toml +0 -0
  104. {narvy_cli-1.0.0 → narvy_cli-1.0.2}/setup.cfg +0 -0
  105. {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.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. Rule packs live under `narvy/rules/`: Android/iOS
165
- (`rules/android`, `rules/ios`) and web/backend (`rules/web`, Semgrep-based).
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. Rule packs live under `narvy/rules/`: Android/iOS
132
- (`rules/android`, `rules/ios`) and web/backend (`rules/web`, Semgrep-based).
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).
@@ -1,3 +1,3 @@
1
1
  """Narvy CLI - free, local SAST for Android, iOS and polyglot source."""
2
2
 
3
- __version__ = "1.0.0"
3
+ __version__ = "1.0.2"
@@ -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" - free up that much memory (close other apps), then re-run with --max-mem {suggest_gb}g\n"
213
- f" - scan this app on a bigger machine, or use the hosted scan (`--upload`)\n"
214
- f" - run it anyway with --force if you know your setup can take it"
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
- try:
1093
- all_vulnerable_groups.extend(analyze_security_groups(reg, endpoint_url=endpoint_url))
1094
- all_vulnerable_vpcs.extend(analyze_vpc_configuration(reg, endpoint_url=endpoint_url))
1095
- all_vulnerable_rds.extend(analyze_rds_instances(reg, endpoint_url=endpoint_url))
1096
- all_vulnerable_lambdas.extend(analyze_lambda_functions(reg, endpoint_url=endpoint_url))
1097
- all_vulnerable_ecs.extend(analyze_ecs_clusters(reg, endpoint_url=endpoint_url))
1098
- all_vulnerable_eb.extend(analyze_elastic_beanstalk(reg, endpoint_url=endpoint_url))
1099
- all_vulnerable_apis.extend(analyze_api_gateway(reg, endpoint_url=endpoint_url))
1100
- all_vulnerable_dynamodb.extend(analyze_dynamodb(reg, endpoint_url=endpoint_url))
1101
- all_gd_issues.extend(analyze_guardduty(reg, endpoint_url=endpoint_url))
1102
- all_vulnerable_eks.extend(analyze_eks(reg, endpoint_url=endpoint_url))
1103
- all_vulnerable_redshift.extend(analyze_redshift(reg, endpoint_url=endpoint_url))
1104
- all_vulnerable_secrets.extend(analyze_secrets_manager(reg, endpoint_url=endpoint_url))
1105
- all_vulnerable_sqs.extend(analyze_sqs(reg, endpoint_url=endpoint_url))
1106
- all_vulnerable_sns.extend(analyze_sns(reg, endpoint_url=endpoint_url))
1107
- all_vulnerable_glue_jobs.extend(analyze_glue(reg, endpoint_url=endpoint_url))
1108
- all_vulnerable_emr_clusters.extend(analyze_emr(reg, endpoint_url=endpoint_url))
1109
- all_vulnerable_kms_keys.extend(analyze_kms(reg, endpoint_url=endpoint_url))
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
- try:
1117
- all_cloudtrail_issues.extend(
1118
- analyze_cloudtrail(primary_region, endpoint_url=endpoint_url))
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
- try:
1133
- logger.info("Analyzing S3 Buckets...")
1134
- vulnerable_buckets = analyze_s3_buckets(endpoint_url=endpoint_url)
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
- try:
1140
- logger.info("Analyzing IAM Users for MFA...")
1141
- users_without_mfa = analyze_iam_users(endpoint_url=endpoint_url)
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
- try:
1147
- logger.info("Analyzing IAM Policies...")
1148
- risky_policies = analyze_iam_policies(endpoint_url=endpoint_url)
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
- try:
1154
- logger.info("Analyzing CloudFront Distributions...")
1155
- vulnerable_cf = analyze_cloudfront(endpoint_url=endpoint_url)
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
- try:
1161
- logger.info("Checking CIS Compliance...")
1162
- compliance_issues = check_cis_compliance(endpoint_url=endpoint_url)
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 (large production app scale) need 8-24 GB of free "
114
- "memory to decompile. `narvy scan` will tell you before it starts, and "
115
- "won't waste your time failing halfway - but on this machine expect to be "
116
- "limited to smaller apps, or use `--upload` for the hosted scan."
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's App Store DRM, cryptid=1): "
1025
- "the executable __TEXT code is ciphertext until the device that "
1026
- "purchased/installed it decrypts it at launch time using a key Apple "
1027
- "grants only to that device - no static analysis tool, Narvy "
1028
- "included, can read the actual instruction stream of an encrypted "
1029
- "App Store export. This is a physical limitation of the distribution "
1030
- "format, not a gap in this scanner. What still works and was still "
1031
- "run: Info.plist / entitlements / URL-scheme / ATS analysis (always "
1032
- "cleartext), linked-framework and embedded-framework-name listing "
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