narvy-cli 1.0.3__tar.gz → 1.1.1__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.1.1/CHANGELOG.md +116 -0
- {narvy_cli-1.0.3/narvy_cli.egg-info → narvy_cli-1.1.1}/PKG-INFO +87 -11
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/README.md +86 -10
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/__init__.py +1 -1
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/android/source_analyzer.py +4 -10
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/android/split_bundle.py +1 -2
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/android_rule_context.py +1 -2
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/apk_memory_preflight.py +1 -2
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/ci_templates/bitbucket.yml +1 -1
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/ci_templates/gitlab.yml +1 -1
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/cloud/aws_scan.py +3 -6
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/comment_filter.py +1 -3
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/crypto_taint_lite.py +1 -4
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/doctor.py +8 -8
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/host/audit.py +4 -2
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/host/host_knowledge.py +2 -6
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/host/lynis_bootstrap.py +1 -3
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/host/lynis_parser.py +1 -4
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/host/narvy_checks.py +1 -3
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/host/ssh_exec.py +1 -3
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/ios/binary_analyzer.py +17 -26
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/ios/plist_checks.py +1 -2
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/ios/source_analyzer.py +5 -4
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/ios/third_party_filter.py +1 -2
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/ios/trust_all_context.py +1 -3
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/main.py +736 -237
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/native_hardening.py +4 -6
- narvy_cli-1.1.1/narvy/own_reports.py +72 -0
- narvy_cli-1.1.1/narvy/proc.py +92 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/web/go.yml +0 -23
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/web/java.yml +0 -91
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/web/javascript.yml +3 -86
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/web/php.yml +234 -212
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/web/python.yml +282 -1288
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/web/ruby.yml +41 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/web/rust.yml +21 -17
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/web/secrets.yml +4 -4
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/web/secrets_supplement.yml +9 -6
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/sca/android_deps.py +2 -5
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/sca/ios_deps.py +1 -2
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/sca/osv_client.py +1 -2
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/sca/web_deps.py +8 -14
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/scope_config.py +1 -4
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/semgrep_engine.py +70 -6
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/stack_protector_evidence.py +1 -4
- narvy_cli-1.1.1/narvy/telemetry.py +270 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/third_party_filter.py +1 -2
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/uploader.py +6 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/weak_prng_context.py +1 -2
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/web/scan_blocklist.py +1 -4
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/web/scanner.py +12 -16
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/web/source_analyzer.py +330 -90
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/web/ssrf_guard.py +7 -10
- {narvy_cli-1.0.3 → narvy_cli-1.1.1/narvy_cli.egg-info}/PKG-INFO +87 -11
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy_cli.egg-info/SOURCES.txt +3 -14
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/pyproject.toml +4 -6
- narvy_cli-1.1.1/setup.py +4 -0
- narvy_cli-1.0.3/CHANGELOG.md +0 -85
- narvy_cli-1.0.3/narvy/rules/web/local/csharp_narvy/config.yml +0 -61
- narvy_cli-1.0.3/narvy/rules/web/local/csharp_narvy/crypto.yml +0 -64
- narvy_cli-1.0.3/narvy/rules/web/local/csharp_narvy/deserialization.yml +0 -59
- narvy_cli-1.0.3/narvy/rules/web/local/csharp_narvy/injection.yml +0 -122
- narvy_cli-1.0.3/narvy/rules/web/local/csharp_narvy/xxe.yml +0 -48
- narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/auth_jwt.yml +0 -134
- narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/deserialization.yml +0 -108
- narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/mybatis.yml +0 -39
- narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/snakeyaml.yml +0 -34
- narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/spring_authz.yml +0 -32
- narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/spring_config.yml +0 -67
- narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/spring_hardening.yml +0 -291
- narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/sqli.yml +0 -235
- narvy_cli-1.0.3/narvy/rules/web/local/java_narvy/xxe.yml +0 -212
- narvy_cli-1.0.3/setup.py +0 -7
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/LICENSE +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/MANIFEST.in +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/SECURITY.md +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/android/__init__.py +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/auth.py +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/ci_templates/github.yml +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/cloud/__init__.py +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/decompiler.py +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/host/__init__.py +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/host/report.py +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/ios/__init__.py +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/reporter.py +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rule_engine.py +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/android/config.yml +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/android/crypto.yml +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/android/secrets.yml +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/android/storage.yml +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/android/webview.yml +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/android.yml +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/ios/objc/crypto.yml +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/ios/objc/network.yml +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/ios/objc/secrets.yml +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/ios/objc/storage.yml +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/ios/objc/webview.yml +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/ios_swift.yml +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/rules/web/kotlin.yml +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/sca/__init__.py +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/web/__init__.py +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy/web/nuclei_binary.py +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy_cli.egg-info/dependency_links.txt +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy_cli.egg-info/entry_points.txt +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy_cli.egg-info/requires.txt +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/narvy_cli.egg-info/top_level.txt +0 -0
- {narvy_cli-1.0.3 → narvy_cli-1.1.1}/setup.cfg +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.1.1]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Flask cookie checks: `set_cookie()` without `httponly=True` or
|
|
13
|
+
`secure=True`, the same checks Django already had.
|
|
14
|
+
- `scan` warns when files or folders could not be read (permissions) instead
|
|
15
|
+
of skipping them silently; `-v` lists them and the JSON report has
|
|
16
|
+
`summary.unreadable_paths`.
|
|
17
|
+
- `scan --format` is accepted as an alias of `--output`, like the other
|
|
18
|
+
commands.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- A second `scan` of the same folder picked up the report written by the
|
|
23
|
+
first one (`narvy.sarif`, the JSON report) and flagged the rule text quoted
|
|
24
|
+
in it as secrets. Narvy reports are now recognized by their content, and
|
|
25
|
+
the `--file` target is never scanned.
|
|
26
|
+
- `--upload` with only `NARVY_TOKEN` set printed "Uploading ... to for
|
|
27
|
+
server-side curation". The line now names the server (`narvy.io`, or
|
|
28
|
+
`NARVY_URL`), and a failed upload or login says which server refused it.
|
|
29
|
+
- `web-scan http://localhost` said "internal/metadata addresses"; it now says
|
|
30
|
+
the address is loopback and explains why local and private targets are
|
|
31
|
+
refused.
|
|
32
|
+
- The web-scan, host-audit and cloud-scan JSON output carries a `tool` field.
|
|
33
|
+
- `narvy doctor` documents its exit codes (0 all required checks pass, 1 one
|
|
34
|
+
failed).
|
|
35
|
+
|
|
36
|
+
## [1.1.0]
|
|
37
|
+
|
|
38
|
+
First release of the community rule set. The 1.0.x preview releases were
|
|
39
|
+
withdrawn; 1.1.0 replaces them.
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- PHP: reflected XSS where request input is concatenated or interpolated into
|
|
44
|
+
an HTML string, and an `include`/`require` of a variable that is set in
|
|
45
|
+
another file (reported as HIGH, to review).
|
|
46
|
+
- Python and Ruby rules: tainted module import, deprecated `ssl.wrap_socket`,
|
|
47
|
+
response header CRLF injection, SSRF from a request-controlled host.
|
|
48
|
+
- A web repository that also holds an iOS app in a subdirectory (an Xcode
|
|
49
|
+
project, workspace or `Package.swift` next to a server directory) is now
|
|
50
|
+
scanned on both surfaces, and the report lists them. Sample, test and
|
|
51
|
+
vendored projects are skipped.
|
|
52
|
+
- `--upload` prints a direct link to the scan in the dashboard.
|
|
53
|
+
- Outside a terminal (CI logs), a "still running" line every 30 seconds
|
|
54
|
+
(`NARVY_PROGRESS_INTERVAL`, 0 = off).
|
|
55
|
+
- Anonymous usage telemetry, listed field by field in the README, with a
|
|
56
|
+
notice on first run. Off with `narvy telemetry off`, `NARVY_TELEMETRY=0` or
|
|
57
|
+
`DO_NOT_TRACK=1`.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- Framework-specific rules and the C#/.NET code rules are no longer in the
|
|
62
|
+
CLI; they run on Narvy paid plans. The CLI states in one line when a scanned
|
|
63
|
+
project uses one of those frameworks. C#/.NET projects get secrets and NuGet
|
|
64
|
+
dependency checks.
|
|
65
|
+
- Exit codes are distinct and listed in `--help`: 0 completed, 1 `--fail-on`
|
|
66
|
+
threshold reached, 2 `--fail-on` could not certify the result (part of the
|
|
67
|
+
scan did not run), 3 target or arguments not usable, 4 scan, login, upload
|
|
68
|
+
or write failure. Exit 1 on `--fail-on` is unchanged.
|
|
69
|
+
- The structural analysis uses at most 4 workers and a memory ceiling, and
|
|
70
|
+
trees above 20,000 files are scanned in directory batches. On the Linux
|
|
71
|
+
kernel tree, peak memory went from 13 GB to 2.2 GB with the same findings
|
|
72
|
+
(the scan takes longer). `NARVY_SEMGREP_JOBS`,
|
|
73
|
+
`NARVY_SEMGREP_MAX_MEMORY_MB`, `NARVY_SEMGREP_BATCH_FILES` override.
|
|
74
|
+
- Help for `--upload` and `--fail-on` rewritten; the `.narvy-scope.yml` tip
|
|
75
|
+
links to the public documentation.
|
|
76
|
+
- The FairPlay notice no longer says dynamic analysis always covers an
|
|
77
|
+
encrypted app's own code: it does for most apps, and strongly protected apps
|
|
78
|
+
can block it.
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
|
|
82
|
+
- PHP findings changed from one run to the next (49 to 52 on the same code):
|
|
83
|
+
the PHP parser is not safe with parallel workers. PHP rules now run in a
|
|
84
|
+
single worker.
|
|
85
|
+
- PHP command injection with a trailing concatenation
|
|
86
|
+
(`shell_exec('ping ' . $target)`) was not detected, and escaping functions
|
|
87
|
+
were not recognized as sanitizers. Values checked with `is_numeric()`,
|
|
88
|
+
`ctype_digit()` or `filter_var()` are now treated as safe.
|
|
89
|
+
- Private-key detection no longer fires on a lone `BEGIN PRIVATE KEY`
|
|
90
|
+
delimiter string; keys embedded as a single escaped string are still found.
|
|
91
|
+
The high-entropy secret rule ignores public keys, constant names and path
|
|
92
|
+
values. Rust: DDL statements built with `format!` are no longer reported as
|
|
93
|
+
SQL injection.
|
|
94
|
+
- A web repository with an iOS app in a subdirectory was reported as web only,
|
|
95
|
+
with a coverage note pointing to a directory that did not exist.
|
|
96
|
+
- SCA on an app with no identifiable dependency versions printed "0 known
|
|
97
|
+
CVEs across 0 dependencies", which reads as clean. It now says the check
|
|
98
|
+
was not applicable (`applicable: false` in JSON).
|
|
99
|
+
- Upgrade links returned by the server as relative paths are printed as full
|
|
100
|
+
URLs.
|
|
101
|
+
- semgrep installed by pipx or in a non-activated virtualenv was not found,
|
|
102
|
+
and the scan fell back to regex rules only.
|
|
103
|
+
- Cancelling a scan (SIGTERM/SIGHUP, CI cancel) left semgrep workers running,
|
|
104
|
+
including workers that ignore SIGTERM.
|
|
105
|
+
- Without network access, each semgrep pass stalled for about 100 seconds on
|
|
106
|
+
semgrep's own version check. The version check and semgrep metrics are now
|
|
107
|
+
off.
|
|
108
|
+
- `web-scan --active` always timed out: its time budget assumed a third of the
|
|
109
|
+
real template set.
|
|
110
|
+
- A corrupt or truncated APK/AAB/IPA exits 3 (unusable target) instead of 4.
|
|
111
|
+
`host-audit` rejects an invalid host, user or port before connecting (exit 3).
|
|
112
|
+
- An output path that is a directory, or in a missing directory, is refused
|
|
113
|
+
before the scan starts, for `web-scan`, `host-audit` and `cloud-scan` too.
|
|
114
|
+
- `narvy doctor` printed raw color codes from nuclei's version banner.
|
|
115
|
+
- C/C++ files are reported as not analyzed instead of being silently skipped.
|
|
116
|
+
- Plain APK results uploaded with `--upload` were filed as source scans.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: narvy-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
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
|
|
@@ -45,18 +45,20 @@ cryptography, SSRF, injection, and more - in:
|
|
|
45
45
|
`.apks`), or a raw Gradle/Kotlin source checkout
|
|
46
46
|
- **iOS**: an unencrypted `.ipa`, or a raw Xcode/Swift source checkout
|
|
47
47
|
- **Web/backend source**: JavaScript/TypeScript, Python, PHP, Go, Ruby,
|
|
48
|
-
Rust, Java/Kotlin, C#/.NET
|
|
48
|
+
Rust, Java/Kotlin, and C#/.NET (secrets and dependencies)
|
|
49
49
|
|
|
50
50
|
It also ships three standalone scanners that need nothing but network
|
|
51
51
|
access or your own existing credentials:
|
|
52
52
|
|
|
53
|
-
- `web-scan` -
|
|
53
|
+
- `web-scan` - Nuclei-based scan of a live URL (passive by default, `--active` opt-in)
|
|
54
54
|
- `host-audit` - agentless host audit over your own SSH access (Narvy's own
|
|
55
55
|
host checks plus Lynis (GPL-3.0) as the hardening baseline)
|
|
56
56
|
- `cloud-scan` - AWS account posture check using your own ambient AWS
|
|
57
57
|
credentials
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
Your code, binaries and findings are never uploaded unless you explicitly
|
|
60
|
+
pass `--upload`. The CLI does send anonymous usage statistics, described
|
|
61
|
+
exactly in [Telemetry](#telemetry), which you can turn off.
|
|
60
62
|
|
|
61
63
|
## Install
|
|
62
64
|
|
|
@@ -120,14 +122,88 @@ machine's free memory, so an app that won't fit fails in about a second
|
|
|
120
122
|
instead of after several minutes of doomed work. The estimate is a
|
|
121
123
|
heuristic, not a hard limit - `--force` runs the decompile regardless.
|
|
122
124
|
|
|
123
|
-
###
|
|
125
|
+
### Sending results to your Narvy dashboard
|
|
126
|
+
|
|
127
|
+
`narvy login` links the CLI to a Narvy account. Add `--upload` to a scan to
|
|
128
|
+
send its results (the findings, never your code or binary) to your account,
|
|
129
|
+
where they are de-duplicated, false-positive filtered and kept in the
|
|
130
|
+
dashboard history; the CLI prints a direct link to the scan. This needs a
|
|
131
|
+
paid plan (Starter and up) and is never on by default. `narvy upload-all
|
|
132
|
+
<directory>` does the same for every binary target found in a folder in one
|
|
133
|
+
command. `narvy scans` lists recent scans on your account from the terminal.
|
|
134
|
+
|
|
135
|
+
### Exit codes
|
|
136
|
+
|
|
137
|
+
| Code | Meaning |
|
|
138
|
+
|------|---------|
|
|
139
|
+
| 0 | Scan completed (and nothing reached the `--fail-on` threshold) |
|
|
140
|
+
| 1 | `--fail-on`: at least one finding at or above the threshold |
|
|
141
|
+
| 2 | `--fail-on`: nothing reached the threshold, but part of the scan did not run (timed-out pass, unreachable CVE database), so it cannot be certified clean |
|
|
142
|
+
| 3 | The target or arguments are not usable (path not found, unsupported file type or project, refused URL, invalid option) |
|
|
143
|
+
| 4 | The scan, login, upload or report write failed |
|
|
144
|
+
| 130 | Interrupted (Ctrl-C, SIGTERM) |
|
|
145
|
+
|
|
146
|
+
`narvy doctor` exits 0 when every required check passes and 1 when one fails.
|
|
147
|
+
Missing optional tools are shown as INFO and do not change its exit code.
|
|
148
|
+
|
|
149
|
+
### Resource limits
|
|
150
|
+
|
|
151
|
+
The structural analysis pass runs with at most 4 parallel workers and a
|
|
152
|
+
3 GB memory ceiling per analyzed file, so a very large repository does not
|
|
153
|
+
exhaust the machine. `NARVY_SEMGREP_JOBS` and `NARVY_SEMGREP_MAX_MEMORY_MB`
|
|
154
|
+
(0 = no ceiling) override them; files skipped because of the ceiling are
|
|
155
|
+
listed in the scan notes. Outside a terminal (CI logs), a progress line is
|
|
156
|
+
printed every 30 seconds; `NARVY_PROGRESS_INTERVAL` changes that (0 = off).
|
|
157
|
+
|
|
158
|
+
## Telemetry
|
|
159
|
+
|
|
160
|
+
The CLI sends one small anonymous event per command, so we can see which
|
|
161
|
+
scan types are used and where they fail. It is sent in the background with
|
|
162
|
+
a 1 second timeout, is never retried or stored for later, and never fails a
|
|
163
|
+
scan. At most it adds 1 second at exit. A notice is printed the first time it runs.
|
|
164
|
+
|
|
165
|
+
Exactly these fields are sent:
|
|
166
|
+
|
|
167
|
+
| Field | Example |
|
|
168
|
+
|-------|---------|
|
|
169
|
+
| `schema` | `1`, the event format version |
|
|
170
|
+
| `install_id` | a random UUID created on first run and stored in `~/.narvy/config.json` (not derived from your machine, user or account) |
|
|
171
|
+
| `cli_version`, `python_version`, `os` | `1.1.1`, `3.11`, `linux` / `darwin` / `windows` / `other` |
|
|
172
|
+
| `ci` | `true` when a CI environment variable is set |
|
|
173
|
+
| `command` | `scan`, `web-scan`, `host-audit`, ... |
|
|
174
|
+
| `surface` | `android`, `ios`, `source`, `web`, `host`, `cloud` or `none` |
|
|
175
|
+
| `duration_bucket` | `<10s`, `10s-1m`, `1-5m`, `5-15m`, `15-60m`, `>60m` |
|
|
176
|
+
| `findings` | per severity, a bucket: `0`, `1-5`, `6-20`, `21-100`, `101-500`, `>500` |
|
|
177
|
+
| `exit_code` | the process exit code |
|
|
178
|
+
| `error_class` | on a crash, the exception class name only (e.g. `ValueError`), never its message |
|
|
179
|
+
|
|
180
|
+
Never sent: code, file, project, package or repository names, paths, URLs,
|
|
181
|
+
hosts or IPs you scan, finding details, your account id or email.
|
|
182
|
+
|
|
183
|
+
Turn it off with any of:
|
|
124
184
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
185
|
+
```bash
|
|
186
|
+
narvy telemetry off # saved in ~/.narvy/config.json; `narvy telemetry on` to re-enable
|
|
187
|
+
export NARVY_TELEMETRY=0
|
|
188
|
+
export DO_NOT_TRACK=1
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`narvy telemetry status` shows the current state.
|
|
192
|
+
|
|
193
|
+
## Free rules and premium rules
|
|
194
|
+
|
|
195
|
+
The CLI ships the community rule set under Apache 2.0: the language-level
|
|
196
|
+
rules for every supported language (injection, weak cryptography, dangerous
|
|
197
|
+
configuration, storage, WebView, transport), every hardcoded-secret rule, and
|
|
198
|
+
the Android and iOS rules. It is the same engine and the same community rules
|
|
199
|
+
that Narvy runs on its servers.
|
|
200
|
+
|
|
201
|
+
Framework-specific rules (Django, Flask, FastAPI, Express, Laravel, Symfony,
|
|
202
|
+
Ruby on Rails, Spring and the Java persistence/serialization stack, Gin) and
|
|
203
|
+
the C#/.NET code rules are premium: they run when a project is scanned on a
|
|
204
|
+
Narvy paid plan, not in the CLI. When a scanned project uses one of those
|
|
205
|
+
frameworks, the CLI says so in one line at the end of the scan, with the
|
|
206
|
+
number of checks it did not run. It never hides a finding it did compute.
|
|
131
207
|
|
|
132
208
|
## Known limitations
|
|
133
209
|
|
|
@@ -12,18 +12,20 @@ cryptography, SSRF, injection, and more - in:
|
|
|
12
12
|
`.apks`), or a raw Gradle/Kotlin source checkout
|
|
13
13
|
- **iOS**: an unencrypted `.ipa`, or a raw Xcode/Swift source checkout
|
|
14
14
|
- **Web/backend source**: JavaScript/TypeScript, Python, PHP, Go, Ruby,
|
|
15
|
-
Rust, Java/Kotlin, C#/.NET
|
|
15
|
+
Rust, Java/Kotlin, and C#/.NET (secrets and dependencies)
|
|
16
16
|
|
|
17
17
|
It also ships three standalone scanners that need nothing but network
|
|
18
18
|
access or your own existing credentials:
|
|
19
19
|
|
|
20
|
-
- `web-scan` -
|
|
20
|
+
- `web-scan` - Nuclei-based scan of a live URL (passive by default, `--active` opt-in)
|
|
21
21
|
- `host-audit` - agentless host audit over your own SSH access (Narvy's own
|
|
22
22
|
host checks plus Lynis (GPL-3.0) as the hardening baseline)
|
|
23
23
|
- `cloud-scan` - AWS account posture check using your own ambient AWS
|
|
24
24
|
credentials
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Your code, binaries and findings are never uploaded unless you explicitly
|
|
27
|
+
pass `--upload`. The CLI does send anonymous usage statistics, described
|
|
28
|
+
exactly in [Telemetry](#telemetry), which you can turn off.
|
|
27
29
|
|
|
28
30
|
## Install
|
|
29
31
|
|
|
@@ -87,14 +89,88 @@ machine's free memory, so an app that won't fit fails in about a second
|
|
|
87
89
|
instead of after several minutes of doomed work. The estimate is a
|
|
88
90
|
heuristic, not a hard limit - `--force` runs the decompile regardless.
|
|
89
91
|
|
|
90
|
-
###
|
|
92
|
+
### Sending results to your Narvy dashboard
|
|
93
|
+
|
|
94
|
+
`narvy login` links the CLI to a Narvy account. Add `--upload` to a scan to
|
|
95
|
+
send its results (the findings, never your code or binary) to your account,
|
|
96
|
+
where they are de-duplicated, false-positive filtered and kept in the
|
|
97
|
+
dashboard history; the CLI prints a direct link to the scan. This needs a
|
|
98
|
+
paid plan (Starter and up) and is never on by default. `narvy upload-all
|
|
99
|
+
<directory>` does the same for every binary target found in a folder in one
|
|
100
|
+
command. `narvy scans` lists recent scans on your account from the terminal.
|
|
101
|
+
|
|
102
|
+
### Exit codes
|
|
103
|
+
|
|
104
|
+
| Code | Meaning |
|
|
105
|
+
|------|---------|
|
|
106
|
+
| 0 | Scan completed (and nothing reached the `--fail-on` threshold) |
|
|
107
|
+
| 1 | `--fail-on`: at least one finding at or above the threshold |
|
|
108
|
+
| 2 | `--fail-on`: nothing reached the threshold, but part of the scan did not run (timed-out pass, unreachable CVE database), so it cannot be certified clean |
|
|
109
|
+
| 3 | The target or arguments are not usable (path not found, unsupported file type or project, refused URL, invalid option) |
|
|
110
|
+
| 4 | The scan, login, upload or report write failed |
|
|
111
|
+
| 130 | Interrupted (Ctrl-C, SIGTERM) |
|
|
112
|
+
|
|
113
|
+
`narvy doctor` exits 0 when every required check passes and 1 when one fails.
|
|
114
|
+
Missing optional tools are shown as INFO and do not change its exit code.
|
|
115
|
+
|
|
116
|
+
### Resource limits
|
|
117
|
+
|
|
118
|
+
The structural analysis pass runs with at most 4 parallel workers and a
|
|
119
|
+
3 GB memory ceiling per analyzed file, so a very large repository does not
|
|
120
|
+
exhaust the machine. `NARVY_SEMGREP_JOBS` and `NARVY_SEMGREP_MAX_MEMORY_MB`
|
|
121
|
+
(0 = no ceiling) override them; files skipped because of the ceiling are
|
|
122
|
+
listed in the scan notes. Outside a terminal (CI logs), a progress line is
|
|
123
|
+
printed every 30 seconds; `NARVY_PROGRESS_INTERVAL` changes that (0 = off).
|
|
124
|
+
|
|
125
|
+
## Telemetry
|
|
126
|
+
|
|
127
|
+
The CLI sends one small anonymous event per command, so we can see which
|
|
128
|
+
scan types are used and where they fail. It is sent in the background with
|
|
129
|
+
a 1 second timeout, is never retried or stored for later, and never fails a
|
|
130
|
+
scan. At most it adds 1 second at exit. A notice is printed the first time it runs.
|
|
131
|
+
|
|
132
|
+
Exactly these fields are sent:
|
|
133
|
+
|
|
134
|
+
| Field | Example |
|
|
135
|
+
|-------|---------|
|
|
136
|
+
| `schema` | `1`, the event format version |
|
|
137
|
+
| `install_id` | a random UUID created on first run and stored in `~/.narvy/config.json` (not derived from your machine, user or account) |
|
|
138
|
+
| `cli_version`, `python_version`, `os` | `1.1.1`, `3.11`, `linux` / `darwin` / `windows` / `other` |
|
|
139
|
+
| `ci` | `true` when a CI environment variable is set |
|
|
140
|
+
| `command` | `scan`, `web-scan`, `host-audit`, ... |
|
|
141
|
+
| `surface` | `android`, `ios`, `source`, `web`, `host`, `cloud` or `none` |
|
|
142
|
+
| `duration_bucket` | `<10s`, `10s-1m`, `1-5m`, `5-15m`, `15-60m`, `>60m` |
|
|
143
|
+
| `findings` | per severity, a bucket: `0`, `1-5`, `6-20`, `21-100`, `101-500`, `>500` |
|
|
144
|
+
| `exit_code` | the process exit code |
|
|
145
|
+
| `error_class` | on a crash, the exception class name only (e.g. `ValueError`), never its message |
|
|
146
|
+
|
|
147
|
+
Never sent: code, file, project, package or repository names, paths, URLs,
|
|
148
|
+
hosts or IPs you scan, finding details, your account id or email.
|
|
149
|
+
|
|
150
|
+
Turn it off with any of:
|
|
91
151
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
152
|
+
```bash
|
|
153
|
+
narvy telemetry off # saved in ~/.narvy/config.json; `narvy telemetry on` to re-enable
|
|
154
|
+
export NARVY_TELEMETRY=0
|
|
155
|
+
export DO_NOT_TRACK=1
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`narvy telemetry status` shows the current state.
|
|
159
|
+
|
|
160
|
+
## Free rules and premium rules
|
|
161
|
+
|
|
162
|
+
The CLI ships the community rule set under Apache 2.0: the language-level
|
|
163
|
+
rules for every supported language (injection, weak cryptography, dangerous
|
|
164
|
+
configuration, storage, WebView, transport), every hardcoded-secret rule, and
|
|
165
|
+
the Android and iOS rules. It is the same engine and the same community rules
|
|
166
|
+
that Narvy runs on its servers.
|
|
167
|
+
|
|
168
|
+
Framework-specific rules (Django, Flask, FastAPI, Express, Laravel, Symfony,
|
|
169
|
+
Ruby on Rails, Spring and the Java persistence/serialization stack, Gin) and
|
|
170
|
+
the C#/.NET code rules are premium: they run when a project is scanned on a
|
|
171
|
+
Narvy paid plan, not in the CLI. When a scanned project uses one of those
|
|
172
|
+
frameworks, the CLI says so in one line at the end of the scan, with the
|
|
173
|
+
number of checks it did not run. It never hides a finding it did compute.
|
|
98
174
|
|
|
99
175
|
## Known limitations
|
|
100
176
|
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
"""Android source
|
|
2
|
-
|
|
3
|
-
Public API: analyze_source(source_dir, override_config=None) -> dict.
|
|
4
|
-
"""
|
|
1
|
+
"""Android source (Gradle/Java/Kotlin) SAST. Entry point: analyze_source()."""
|
|
5
2
|
from __future__ import annotations
|
|
6
3
|
|
|
7
4
|
import os
|
|
@@ -85,16 +82,13 @@ def _is_prefkey_echo_or_typename(line: str) -> bool:
|
|
|
85
82
|
if not lhs:
|
|
86
83
|
return False
|
|
87
84
|
name = lhs.group(1)
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# "secret_token_pref").
|
|
85
|
+
# The value just echoes the constant name, or it's a KEY_*/PREF_* settings key
|
|
86
|
+
# (e.g. KEY_SECRET_TOKEN = "secret_token_pref").
|
|
91
87
|
return val.lower() in name.lower() or bool(_PREFKEY_CONST_RE.match(name))
|
|
92
88
|
|
|
93
89
|
|
|
94
90
|
def _has_no_string_literal_rhs(line: str) -> bool:
|
|
95
|
-
"""
|
|
96
|
-
function call or identifier (e.g. `val apiKey = getApiKeyOrDefault()`), not a
|
|
97
|
-
hardcoded secret. A real hardcoded secret is always a quoted literal."""
|
|
91
|
+
"""True when a name-based secret match is assigned from a call or identifier, not a literal."""
|
|
98
92
|
return not _QUOTED_RE.search(line)
|
|
99
93
|
|
|
100
94
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""Split-APK
|
|
2
|
-
app module, preferred ABI split and any dex-bearing feature module, without merging splits."""
|
|
1
|
+
"""Split-APK bundles (.apkm/.xapk/.apks): pull out the app module, ABI split and dex feature modules."""
|
|
3
2
|
|
|
4
3
|
import json
|
|
5
4
|
import logging
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
call or manifest element and drop or re-grade the finding (rule_engine can't look around a match)."""
|
|
1
|
+
"""Context gates for the Android regex rules: look at the call site or manifest element around a match."""
|
|
3
2
|
from __future__ import annotations
|
|
4
3
|
|
|
5
4
|
import re
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
heap jadx needs from DEX header counts and warn before OOM. Advisory only; `--force` overrides."""
|
|
1
|
+
"""Estimate the jadx heap from DEX header counts and warn before an OOM. `--force` skips it."""
|
|
3
2
|
|
|
4
3
|
import ctypes
|
|
5
4
|
import logging
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Narvy SAST for Bitbucket Pipelines. Written by `narvy init-ci --bitbucket`; edit freely.
|
|
2
2
|
# Already have a bitbucket-pipelines.yml? Copy the step below into it instead of overwriting.
|
|
3
|
-
#
|
|
3
|
+
# SARIF is kept as a build artifact (not Code Insights); --fail-on still fails the build.
|
|
4
4
|
image: python:3.11
|
|
5
5
|
|
|
6
6
|
pipelines:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Narvy SAST for GitLab CI. Written by `narvy init-ci --gitlab`; edit freely.
|
|
2
2
|
# Already have a .gitlab-ci.yml? Copy the narvy_sast job below into it instead of overwriting.
|
|
3
|
-
#
|
|
3
|
+
# SARIF is kept as a plain artifact (not GitLab's SAST schema); --fail-on still blocks the MR.
|
|
4
4
|
stages:
|
|
5
5
|
- test
|
|
6
6
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
"""AWS
|
|
2
|
-
across every enabled region. Credentials resolve through boto3's normal chain.
|
|
3
|
-
"""
|
|
1
|
+
"""AWS posture checks (security groups, IAM, storage, logging) across enabled regions."""
|
|
4
2
|
|
|
5
3
|
import boto3
|
|
6
4
|
from botocore.exceptions import ClientError, NoCredentialsError, EndpointConnectionError
|
|
@@ -1055,9 +1053,8 @@ def analyze_aws_security(credentials, region=None, endpoint_url=None):
|
|
|
1055
1053
|
# letting the per-check handlers below report an empty, clean scan.
|
|
1056
1054
|
_verify_credentials(endpoint_url=endpoint_url)
|
|
1057
1055
|
|
|
1058
|
-
#
|
|
1059
|
-
#
|
|
1060
|
-
# coverage gap, not as a clean pass.
|
|
1056
|
+
# Analyzers that couldn't finish (AccessDenied, throttling), so a blind scan
|
|
1057
|
+
# shows up as a coverage gap instead of a clean pass.
|
|
1061
1058
|
coverage_errors = []
|
|
1062
1059
|
|
|
1063
1060
|
def _collect(dest, label, fn):
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
mark byte offsets inside `//` and `/* */` comments so regex rules don't fire on
|
|
3
|
-
commented-out code. String literals excluded; when unsure, treated as code (never drops a finding)."""
|
|
1
|
+
"""Find `//` and `/* */` comment ranges in C-family source so regex rules skip commented-out code."""
|
|
4
2
|
from __future__ import annotations
|
|
5
3
|
|
|
6
4
|
from typing import List
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
javax.crypto.spec.{SecretKeySpec,IvParameterSpec} through one level of
|
|
3
|
-
indirection. Correlation is by variable name within a single file, not scope.
|
|
4
|
-
"""
|
|
1
|
+
"""Match hardcoded key/IV literals to SecretKeySpec/IvParameterSpec by variable name, one file at a time."""
|
|
5
2
|
from __future__ import annotations
|
|
6
3
|
|
|
7
4
|
import re
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""`narvy doctor`:
|
|
2
|
-
Prints a PASS/FAIL list with a concrete fix for each failure."""
|
|
1
|
+
"""`narvy doctor`: check the local environment and print a fix for each failure."""
|
|
3
2
|
import os
|
|
4
3
|
import platform
|
|
5
4
|
import re
|
|
@@ -132,12 +131,11 @@ def _check_network():
|
|
|
132
131
|
|
|
133
132
|
|
|
134
133
|
def _check_semgrep():
|
|
135
|
-
"""
|
|
136
|
-
PATH and `scan` silently falls back to regex-only rules."""
|
|
134
|
+
"""semgrep runs the AST/taint pass; without it `scan` falls back to regex rules only."""
|
|
137
135
|
from . import semgrep_engine
|
|
138
136
|
if semgrep_engine.is_available():
|
|
139
|
-
return True, f"{
|
|
140
|
-
return True, "Not
|
|
137
|
+
return True, f"{semgrep_engine.semgrep_bin()}", None
|
|
138
|
+
return True, "Not found - scans fall back to regex-only (reduced coverage)", (
|
|
141
139
|
"semgrep is the deep structural analysis engine for `narvy scan` "
|
|
142
140
|
"(Android/iOS/web source and binary modes). Without it, scans run regex "
|
|
143
141
|
"rules only and miss AST/taint-based findings. Install it: "
|
|
@@ -158,7 +156,8 @@ def _check_nuclei():
|
|
|
158
156
|
"connection can reach github.com, or install nuclei yourself "
|
|
159
157
|
"and put it on PATH: https://github.com/projectdiscovery/nuclei/releases"
|
|
160
158
|
)
|
|
161
|
-
|
|
159
|
+
first = re.sub(r"\x1b\[[0-9;]*m", "", version.splitlines()[0])
|
|
160
|
+
return True, f"{nuclei_bin} ({first[:60]})", None
|
|
162
161
|
|
|
163
162
|
|
|
164
163
|
def _check_ssh_client():
|
|
@@ -238,7 +237,8 @@ def run_doctor():
|
|
|
238
237
|
console.print(f"\n[bold]{name}:[/bold] {_rich_escape(fix)}")
|
|
239
238
|
|
|
240
239
|
for name, hint in optional_hints:
|
|
241
|
-
|
|
240
|
+
label = name if "optional" in name else f"{name} (optional)"
|
|
241
|
+
console.print(f"\n[bold cyan]{label}:[/bold cyan] {_rich_escape(hint)}")
|
|
242
242
|
|
|
243
243
|
if not all_ok:
|
|
244
244
|
sys.exit(1)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""Run Lynis on a remote host over SSH
|
|
2
|
-
A sudo password is never sent, so full coverage needs a root key or NOPASSWD sudo."""
|
|
1
|
+
"""Run Lynis on a remote host over SSH. Full coverage needs a root key or NOPASSWD sudo."""
|
|
3
2
|
|
|
4
3
|
from __future__ import annotations
|
|
5
4
|
|
|
@@ -25,6 +24,8 @@ class AuditOutcome:
|
|
|
25
24
|
hardening_index: Any
|
|
26
25
|
meta: Dict[str, str]
|
|
27
26
|
error: str = ""
|
|
27
|
+
# Raw Lynis .dat report, captured for --verbose; never streamed on its own.
|
|
28
|
+
raw: str = ""
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
def _lynis_prefix(privilege: str) -> str:
|
|
@@ -154,4 +155,5 @@ def audit_host(conn: HostConn, privilege: str = "auto") -> AuditOutcome:
|
|
|
154
155
|
findings=list(parsed["findings"]) + list(narvy_findings),
|
|
155
156
|
hardening_index=parsed["hardening_index"],
|
|
156
157
|
meta=meta,
|
|
158
|
+
raw=dat,
|
|
157
159
|
)
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
Maps each hardening-engine test id to a control with a remediation and severity.
|
|
4
|
-
"""
|
|
1
|
+
"""Map host-audit test ids to a control, severity and remediation."""
|
|
5
2
|
|
|
6
3
|
from __future__ import annotations
|
|
7
4
|
|
|
@@ -975,8 +972,7 @@ HOST_CONTROLS: Dict[str, HostControl] = {
|
|
|
975
972
|
|
|
976
973
|
|
|
977
974
|
def lookup(test_id: str) -> Optional[HostControl]:
|
|
978
|
-
"""Control for
|
|
979
|
-
raw finding for an unknown id rather than dropping it."""
|
|
975
|
+
"""Control for a test id, or None. Callers keep the raw finding for unknown ids."""
|
|
980
976
|
if not test_id:
|
|
981
977
|
return None
|
|
982
978
|
return HOST_CONTROLS.get(test_id.strip().upper())
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
temporary directory, then remove it. Nothing is installed on the host.
|
|
3
|
-
"""
|
|
1
|
+
"""Copy a checksum-pinned Lynis to a temp dir on the host, run it, then remove it."""
|
|
4
2
|
|
|
5
3
|
from __future__ import annotations
|
|
6
4
|
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
"""Parse
|
|
2
|
-
|
|
3
|
-
The `.dat` is a flat key=value file; repeated keys use a `key[]=` suffix.
|
|
4
|
-
"""
|
|
1
|
+
"""Parse `lynis-report.dat` (flat key=value, `key[]=` for repeats) into findings."""
|
|
5
2
|
|
|
6
3
|
from __future__ import annotations
|
|
7
4
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
data services, cloud metadata, container posture, and secret/VCS disclosure under a
|
|
3
|
-
web root. Each fires only on a confirmed exposure; checks take a ``run`` probe callable."""
|
|
1
|
+
"""Extra host exposure checks (open data services, metadata, containers, web-root leaks)."""
|
|
4
2
|
|
|
5
3
|
from __future__ import annotations
|
|
6
4
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Private keys go to a 0600 temp file wiped after use; hostname/user are validated
|
|
3
|
-
before argv is built; host keys use a per-call known_hosts file."""
|
|
1
|
+
"""Run commands on a host with the system ssh client, using a temp key file and per-call known_hosts."""
|
|
4
2
|
|
|
5
3
|
from __future__ import annotations
|
|
6
4
|
|