@gpzhang2001/sharpkit-skills 0.2.1
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.
- package/LICENSE +201 -0
- package/README.md +12 -0
- package/THIRD_PARTY_NOTICES.md +48 -0
- package/lib/index.d.ts +2027 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +70 -0
- package/lib/index.js.map +1 -0
- package/package.json +46 -0
- package/skills/analysis/counterevidence.md +185 -0
- package/skills/analysis/fix_verification.md +129 -0
- package/skills/analysis/severity_calibration.md +130 -0
- package/skills/analysis/source_aware_discovery.md +211 -0
- package/skills/cloud/aws.md +231 -0
- package/skills/cloud/azure.md +262 -0
- package/skills/cloud/gcp.md +194 -0
- package/skills/cloud/kubernetes.md +223 -0
- package/skills/coordination/root_agent.md +105 -0
- package/skills/coordination/source_aware_whitebox.md +47 -0
- package/skills/custom/api_spec_testing.md +61 -0
- package/skills/custom/dependency_cve_scanning.md +341 -0
- package/skills/custom/npx_confusion.md +233 -0
- package/skills/custom/source_aware_sast.md +192 -0
- package/skills/frameworks/django.md +214 -0
- package/skills/frameworks/fastapi.md +191 -0
- package/skills/frameworks/nestjs.md +225 -0
- package/skills/frameworks/nextjs.md +228 -0
- package/skills/protocols/graphql.md +276 -0
- package/skills/protocols/oauth.md +185 -0
- package/skills/reconnaissance/asset_discovery.md +150 -0
- package/skills/reconnaissance/infrastructure_lifecycle.md +226 -0
- package/skills/scan_modes/deep.md +164 -0
- package/skills/scan_modes/diff.md +86 -0
- package/skills/scan_modes/quick.md +68 -0
- package/skills/scan_modes/standard.md +99 -0
- package/skills/technologies/active_directory.md +233 -0
- package/skills/technologies/auth0.md +188 -0
- package/skills/technologies/electron_desktop_apps.md +181 -0
- package/skills/technologies/firebase.md +263 -0
- package/skills/technologies/grafana_prometheus.md +189 -0
- package/skills/technologies/llm_applications.md +257 -0
- package/skills/technologies/supabase.md +268 -0
- package/skills/tooling/agent_browser.md +551 -0
- package/skills/tooling/ffuf.md +72 -0
- package/skills/tooling/httpx.md +82 -0
- package/skills/tooling/hurl.md +99 -0
- package/skills/tooling/hypothesis.md +100 -0
- package/skills/tooling/katana.md +102 -0
- package/skills/tooling/naabu.md +68 -0
- package/skills/tooling/nmap.md +66 -0
- package/skills/tooling/nuclei.md +67 -0
- package/skills/tooling/python.md +109 -0
- package/skills/tooling/semgrep.md +72 -0
- package/skills/tooling/sqlmap.md +67 -0
- package/skills/tooling/subfinder.md +66 -0
- package/skills/vulnerabilities/agentic_system_security.md +207 -0
- package/skills/vulnerabilities/argument_injection.md +157 -0
- package/skills/vulnerabilities/authentication_jwt.md +166 -0
- package/skills/vulnerabilities/broken_function_level_authorization.md +154 -0
- package/skills/vulnerabilities/browser_security.md +192 -0
- package/skills/vulnerabilities/business_logic.md +178 -0
- package/skills/vulnerabilities/csrf.md +198 -0
- package/skills/vulnerabilities/header_injection.md +216 -0
- package/skills/vulnerabilities/http_request_smuggling.md +255 -0
- package/skills/vulnerabilities/idor.md +217 -0
- package/skills/vulnerabilities/information_disclosure.md +187 -0
- package/skills/vulnerabilities/insecure_deserialization.md +210 -0
- package/skills/vulnerabilities/insecure_file_uploads.md +194 -0
- package/skills/vulnerabilities/llm_prompt_injection.md +187 -0
- package/skills/vulnerabilities/mass_assignment.md +153 -0
- package/skills/vulnerabilities/nosql_injection.md +288 -0
- package/skills/vulnerabilities/open_redirect.md +165 -0
- package/skills/vulnerabilities/path_traversal_lfi_rfi.md +218 -0
- package/skills/vulnerabilities/prototype_pollution.md +142 -0
- package/skills/vulnerabilities/race_conditions.md +181 -0
- package/skills/vulnerabilities/rce.md +250 -0
- package/skills/vulnerabilities/semantic_confusion.md +189 -0
- package/skills/vulnerabilities/sql_injection.md +190 -0
- package/skills/vulnerabilities/ssrf.md +186 -0
- package/skills/vulnerabilities/ssti.md +270 -0
- package/skills/vulnerabilities/subdomain_takeover.md +167 -0
- package/skills/vulnerabilities/weak_password_detection.md +200 -0
- package/skills/vulnerabilities/xss.md +206 -0
- package/skills/vulnerabilities/xxe.md +223 -0
- package/src/index.ts +89 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dependency-cve-scanning
|
|
3
|
+
description: Supply-chain / SCA playbook — scan repository lockfiles for known dependency CVEs and report them with create_dependency_report (no dynamic PoC required)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Dependency / Supply-Chain CVE Scanning (SCA)
|
|
7
|
+
|
|
8
|
+
Use this skill on white-box / repository scans to make sure a repository pinning a
|
|
9
|
+
**known-vulnerable dependency** is actually reported as a finding, instead of being
|
|
10
|
+
discovered and then silently dropped because it cannot be dynamically exploited.
|
|
11
|
+
|
|
12
|
+
Known-CVE dependency findings are a first-class deliverable. Report each one with
|
|
13
|
+
the dedicated `create_dependency_report` tool.
|
|
14
|
+
|
|
15
|
+
## Why this skill exists
|
|
16
|
+
|
|
17
|
+
A vulnerable dependency pinned in a lockfile (e.g. `lodash@4.17.4` with a known
|
|
18
|
+
prototype-pollution CVE) usually cannot be dynamically PoC'd from the outside —
|
|
19
|
+
the vulnerable code path may not even be reachable from a running endpoint. The
|
|
20
|
+
normal "no report without a dynamic PoC" rule would suppress it. For these
|
|
21
|
+
findings the proof is the **lockfile entry + scanner output + published
|
|
22
|
+
advisory**, not an exploit script. This is the one explicit exception to the
|
|
23
|
+
dynamic-validation rule, and it exists only for `create_dependency_report`.
|
|
24
|
+
|
|
25
|
+
## Scan procedure
|
|
26
|
+
|
|
27
|
+
Run from the repo root and store output in the shared artifact directory used by
|
|
28
|
+
the source-aware pass:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
ART=/workspace/.source-aware
|
|
32
|
+
mkdir -p "$ART"
|
|
33
|
+
|
|
34
|
+
# Record the vuln DB age so a stale DB is a visible signal, not a silent clean scan.
|
|
35
|
+
trivy version --format json 2>/dev/null | tee "$ART/trivy-version.json"
|
|
36
|
+
# inspect .VulnerabilityDB.UpdatedAt / NextUpdate
|
|
37
|
+
|
|
38
|
+
# Lockfile/manifest -> known-CVE matching. Try a best-effort DB refresh first so a
|
|
39
|
+
# sandbox with egress gets the freshest CVEs; if the update fails, fall back to the
|
|
40
|
+
# cached DB instead of failing the scan. --offline-scan keeps per-package advisory
|
|
41
|
+
# lookups offline.
|
|
42
|
+
# --list-all-pkgs includes the package graph (Relationship + DependsOn) needed
|
|
43
|
+
# to attribute transitive CVEs to the direct dependency that introduces them.
|
|
44
|
+
trivy fs --scanners vuln --timeout 30m --offline-scan --list-all-pkgs \
|
|
45
|
+
--format json --output "$ART/trivy-sca.json" . \
|
|
46
|
+
|| trivy fs --scanners vuln --timeout 30m --offline-scan --skip-db-update --list-all-pkgs \
|
|
47
|
+
--format json --output "$ART/trivy-sca.json" . \
|
|
48
|
+
|| true
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If `.VulnerabilityDB.UpdatedAt` is more than a few weeks old (the sandbox had no
|
|
52
|
+
egress to refresh it), treat it as a scan limitation and note it in the
|
|
53
|
+
`assumptions` of dependency findings — a stale DB that still returns *some* results
|
|
54
|
+
will not trip the "zero results is suspicious" heuristic, so its age is the only
|
|
55
|
+
staleness signal.
|
|
56
|
+
|
|
57
|
+
Trivy reads the lockfiles/manifests it finds, including:
|
|
58
|
+
`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `poetry.lock`,
|
|
59
|
+
`requirements.txt`, `Pipfile.lock`, `go.mod`/`go.sum`, `Gemfile.lock`,
|
|
60
|
+
`pom.xml`/`gradle.lockfile`, `Cargo.lock`, `composer.lock`, etc.
|
|
61
|
+
|
|
62
|
+
If trivy returns zero vulnerabilities on a repo with dependencies, treat it as
|
|
63
|
+
suspicious: confirm the vuln DB is present (`trivy-version.json`) and that
|
|
64
|
+
lockfiles exist.
|
|
65
|
+
|
|
66
|
+
## Interpreting results
|
|
67
|
+
|
|
68
|
+
For each entry under `.Results[].Vulnerabilities[]` in `trivy-sca.json`, collect:
|
|
69
|
+
|
|
70
|
+
- `VulnerabilityID` — the CVE (or GHSA; prefer the CVE if both are present)
|
|
71
|
+
- `PkgName` and `InstalledVersion` — the affected package + pinned version
|
|
72
|
+
- `FixedVersion` — the version that resolves it
|
|
73
|
+
- `Target` — the lockfile path it came from
|
|
74
|
+
- `.Results[].Type` (e.g. `npm`, `pip`, `gomod`, `pom`, `gemspec`, `cargo`) — the
|
|
75
|
+
package ecosystem; normalize to the registry name lowercased (`npm`, `pypi`,
|
|
76
|
+
`go`, `maven`, `rubygems`, `cargo`, `composer`, `nuget`, ...)
|
|
77
|
+
- `CVSS` — the published advisory base score
|
|
78
|
+
- `PrimaryURL` / references — to verify the advisory
|
|
79
|
+
|
|
80
|
+
Deduplicate by `(CVE, PkgName, Target)` — the same CVE/package observed in two
|
|
81
|
+
different manifests (e.g. two workspaces of a monorepo) is two findings, one
|
|
82
|
+
per manifest. File one `create_dependency_report` per CVE — do not batch
|
|
83
|
+
multiple CVEs into one report.
|
|
84
|
+
|
|
85
|
+
### Attribute transitive CVEs to the direct dependency
|
|
86
|
+
|
|
87
|
+
With `--list-all-pkgs`, each `.Results[].Packages[]` entry carries `ID`
|
|
88
|
+
(`name@version`), `Relationship` (`direct` / `indirect`) and `DependsOn` (the
|
|
89
|
+
`ID`s it resolves to). For every vulnerable package that is **indirect**, walk
|
|
90
|
+
the `DependsOn` graph backwards to find the `direct` package(s) whose closure
|
|
91
|
+
contains it, then pass to `create_dependency_report`:
|
|
92
|
+
|
|
93
|
+
- `introduced_by` — the direct dependency as `name@version` (e.g.
|
|
94
|
+
`express@4.18.1`). If several direct dependencies pull it in, pick the
|
|
95
|
+
primary one and name the rest in `technical_analysis`.
|
|
96
|
+
- `dependency_path` — the shortest resolution chain from that direct
|
|
97
|
+
dependency to the vulnerable package, joined with ` > ` (e.g.
|
|
98
|
+
`express@4.18.1 > body-parser@1.20.0 > qs@6.10.2`).
|
|
99
|
+
- Omit both when the vulnerable package is itself a direct dependency.
|
|
100
|
+
|
|
101
|
+
If the ecosystem's lockfile gives trivy no graph (`DependsOn` absent), derive
|
|
102
|
+
the chain from the package manager instead (`npm ls <pkg>`, `pnpm why <pkg>`,
|
|
103
|
+
`yarn why <pkg>`, `pipdeptree --reverse -p <pkg>`, `go mod graph`,
|
|
104
|
+
`mvn dependency:tree`, ...) — and if that also fails, leave the fields out
|
|
105
|
+
rather than guessing.
|
|
106
|
+
|
|
107
|
+
For transitive findings, `remediation_steps` must be actionable at the
|
|
108
|
+
**direct-dependency level**: upgrading the vulnerable package directly is
|
|
109
|
+
usually impossible from the app's own manifest. Say which direct dependency to
|
|
110
|
+
bump (a version whose closure resolves the fixed version), or how to force the
|
|
111
|
+
resolution (npm `overrides` / yarn `resolutions` / pnpm `pnpm.overrides` /
|
|
112
|
+
Maven `dependencyManagement` / Gradle resolution strategy / `go mod edit`),
|
|
113
|
+
not just "upgrade <vulnerable pkg> to <fixed>".
|
|
114
|
+
|
|
115
|
+
### Usage / reachability analysis (required for every dependency CVE)
|
|
116
|
+
|
|
117
|
+
For every CVE you are about to report, run a static usage analysis and record
|
|
118
|
+
the result in the structured `reachability` + `reachability_evidence` fields.
|
|
119
|
+
The level is an **evidence ladder, never an exploitability verdict** — claim
|
|
120
|
+
only what you proved, and cite the proof. It never changes severity (that is
|
|
121
|
+
`advisory_cvss` alone); it exists so the reader can prioritize.
|
|
122
|
+
|
|
123
|
+
**Go — use govulncheck (real call-graph analysis):**
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Symbol-level: reports only vulnerabilities whose vulnerable functions are
|
|
127
|
+
# actually reachable from application code. Needs the Go toolchain + module
|
|
128
|
+
# deps; if either is missing, fall back to the checks below rather than
|
|
129
|
+
# claiming a level.
|
|
130
|
+
if command -v govulncheck >/dev/null && go version >/dev/null 2>&1; then
|
|
131
|
+
govulncheck -format json ./... > "$ART/govulncheck.json" || true
|
|
132
|
+
fi
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- A finding with a call stack ⇒ `reachability=reachable_call_path`, put the
|
|
136
|
+
call-path excerpt (entrypoint → vulnerable function) in
|
|
137
|
+
`reachability_evidence`.
|
|
138
|
+
- Listed as affecting a required module but with no reachable symbol ⇒ fall
|
|
139
|
+
back to the import/symbol checks below (`imported` / `not_imported`).
|
|
140
|
+
|
|
141
|
+
**All other ecosystems — import check, then symbol match:**
|
|
142
|
+
|
|
143
|
+
1. **Import check.** Search application code (exclude lockfiles, vendored
|
|
144
|
+
deps, `node_modules`, build output) for imports of the vulnerable package:
|
|
145
|
+
`ast-grep`/`rg` for `import`/`require`/`from X import` of the package (and
|
|
146
|
+
its ecosystem import name, which may differ from the registry name, e.g.
|
|
147
|
+
`PyYAML` → `yaml`). No hits ⇒ `not_imported`, with the search scope stated
|
|
148
|
+
in `reachability_evidence`. For a **transitive** dependency, the check is
|
|
149
|
+
whether application code imports it directly; if not, it is reachable only
|
|
150
|
+
through the direct dependency — check whether the direct dep's usage can
|
|
151
|
+
hit it (if unclear, use `imported` when the direct dep is used at all).
|
|
152
|
+
2. **Symbol match — per CVE, not per package.** Read each CVE's own advisory
|
|
153
|
+
(GHSA/NVD/OSV `affected[].ecosystem_specific.imports` or the advisory
|
|
154
|
+
text) for the affected functions/classes/APIs. Search application code for
|
|
155
|
+
those symbols (`ast-grep` pattern or `rg -n`). Hits ⇒
|
|
156
|
+
`vulnerable_symbol_used`, with repo-relative `file:line` of each hit (up
|
|
157
|
+
to a handful) in `reachability_evidence`. Imported but no affected-symbol
|
|
158
|
+
usage found (or the advisory names no symbols) ⇒ `imported`.
|
|
159
|
+
Different CVEs on the same package usually affect **different** symbols
|
|
160
|
+
(one hits a parser, another a header check) — never copy one CVE's
|
|
161
|
+
verdict/evidence onto its siblings; run the symbol search against each
|
|
162
|
+
CVE's own affected-symbol list. The import check (step 1) is the only
|
|
163
|
+
part shared across a package's CVEs.
|
|
164
|
+
3. **Source-to-sink trace — do this whenever step 2 found a symbol hit.** A
|
|
165
|
+
symbol hit alone says the code calls the vulnerable API; it does not say
|
|
166
|
+
who can reach it. Start at the sink (the exact line that calls the
|
|
167
|
+
vulnerable function) and walk backwards hop by hop to the source: the
|
|
168
|
+
entry point that carries untrusted input (HTTP route, CLI argument, queue
|
|
169
|
+
or webhook payload, uploaded file, config value). Read each intermediate
|
|
170
|
+
function; when a hop is a thin wrapper, go one step deeper — never stop at
|
|
171
|
+
the first caller. Record what each hop enforces: authentication, a role
|
|
172
|
+
check, validation, a feature flag, a size or type limit, a default that is
|
|
173
|
+
off in production.
|
|
174
|
+
Write the chain into `reachability_evidence` as
|
|
175
|
+
`entry point -> intermediate call -> package call` with a
|
|
176
|
+
repository-relative `file:line` for every hop, and say who controls the
|
|
177
|
+
input. If no source reaches the sink, say that too — the level stays
|
|
178
|
+
`vulnerable_symbol_used` (the call is real), and the trace is what tells
|
|
179
|
+
the reader it is only reachable from, say, an operator CLI.
|
|
180
|
+
4. If the analysis was not performed or is inconclusive (obfuscated code,
|
|
181
|
+
dynamic loading, unparsable sources) ⇒ `unknown` and say why in
|
|
182
|
+
`assumptions`.
|
|
183
|
+
|
|
184
|
+
Cheap-first budgeting: the import check is one search per package — always do
|
|
185
|
+
it. Do the per-CVE symbol match for every CVE whose advisory names affected
|
|
186
|
+
symbols (they can be batched into one multi-pattern search per package);
|
|
187
|
+
prioritize `critical`/`high`/KEV when the budget is tight; a CVE whose symbol
|
|
188
|
+
search was skipped may still be reported as `imported` (the import check is
|
|
189
|
+
real evidence), but its `reachability_evidence` must state that the
|
|
190
|
+
affected-symbol check was not performed, so a skipped search is never
|
|
191
|
+
mistaken for a completed one with no hits. Never let this analysis stall
|
|
192
|
+
reporting — `unknown` with a reason beats an unverified claim.
|
|
193
|
+
|
|
194
|
+
Anti-overclaim rules:
|
|
195
|
+
|
|
196
|
+
- `not_imported` still does NOT mean safe (dynamic `import()`/reflection/
|
|
197
|
+
framework wiring evade static search) — never phrase it as "not exploitable".
|
|
198
|
+
- `reachable_call_path` is reserved for call-graph tools (govulncheck); a
|
|
199
|
+
symbol grep hit is `vulnerable_symbol_used`, no matter how convinced you are.
|
|
200
|
+
- The tool rejects any level other than `unknown` without
|
|
201
|
+
`reachability_evidence`.
|
|
202
|
+
|
|
203
|
+
### Reachability is a confidence modifier, not a gate
|
|
204
|
+
|
|
205
|
+
Do NOT suppress or downgrade a known CVE just because you could not prove the
|
|
206
|
+
vulnerable code path is reachable. Report it, set `advisory_cvss` from the
|
|
207
|
+
advisory, record the usage analysis in `reachability`/`reachability_evidence`,
|
|
208
|
+
and use `assumptions` for anything softer. If you *can* actually trigger the
|
|
209
|
+
vulnerable path or chain it into a dynamic exploit, additionally report that
|
|
210
|
+
as a normal dynamic finding with `create_vulnerability_report` (the standalone
|
|
211
|
+
CVE stays in its own `create_dependency_report`).
|
|
212
|
+
|
|
213
|
+
## Reporting
|
|
214
|
+
|
|
215
|
+
Report each confirmed known CVE with the dedicated `create_dependency_report`
|
|
216
|
+
tool (NOT `create_vulnerability_report` — that tool is for dynamically validated
|
|
217
|
+
findings and rejects empty PoC fields):
|
|
218
|
+
|
|
219
|
+
- Set `cve` to the verified `CVE-YYYY-NNNNN` id (required). If you only have a
|
|
220
|
+
GHSA, look up the mapped CVE; if there is genuinely no CVE, do not report it
|
|
221
|
+
with this tool.
|
|
222
|
+
- There are no PoC fields — `create_dependency_report` does not take
|
|
223
|
+
`poc_description` / `poc_script_code` / `code_locations`. The proof lives in
|
|
224
|
+
`description` and `technical_analysis` (scanner output + advisory).
|
|
225
|
+
- **Always fill the structured dependency fields** (they power the dedicated
|
|
226
|
+
dependency-report card; do not leave them only in free-text):
|
|
227
|
+
- `package_name` — `PkgName` (required).
|
|
228
|
+
- `installed_version` — `InstalledVersion` (required).
|
|
229
|
+
- `package_ecosystem` — normalized ecosystem from `.Results[].Type` (lowercased,
|
|
230
|
+
e.g. `npm`, `pypi`, `go`, `maven`, `rubygems`, `cargo`) (required).
|
|
231
|
+
- `fixed_version` — `FixedVersion` (leave empty only if no fix is published).
|
|
232
|
+
- `manifest_path` — the repo-relative `Target` lockfile/manifest path
|
|
233
|
+
(required). Strip any scan-workspace or repo checkout directory prefix so
|
|
234
|
+
the path is relative to the repository root (e.g. `package-lock.json`,
|
|
235
|
+
`services/api/pom.xml`); the tool rejects absolute paths and `..` segments.
|
|
236
|
+
This binds the finding to the exact file so remediation can target the
|
|
237
|
+
right repository.
|
|
238
|
+
- Reference the repo-relative `Target` lockfile path in `description` /
|
|
239
|
+
`technical_analysis` (no leading slash) so the finding is traceable.
|
|
240
|
+
- Put the concrete proof in `description` / `technical_analysis`: package name,
|
|
241
|
+
installed/affected version, fixed version, lockfile path, and the relevant
|
|
242
|
+
trivy output excerpt.
|
|
243
|
+
- **Always set `advisory_cvss` to the published advisory base score (0.0–10.0).**
|
|
244
|
+
It is the published reference, and it rates the finding whenever you give no
|
|
245
|
+
contextual breakdown: read it off the advisory (`CVSS` in trivy output, or the
|
|
246
|
+
NVD/GHSA page) and pass the real value. The tool rejects a call that omits it,
|
|
247
|
+
because guessing a score both inflates low CVEs and deflates critical ones.
|
|
248
|
+
- Set `cwe` to the most specific `CWE-NNN` when the advisory names one.
|
|
249
|
+
- Do NOT cap severity at LOW just because there is no dynamic reproduction — use
|
|
250
|
+
the advisory score.
|
|
251
|
+
- Set `reachability` + `reachability_evidence` from the usage analysis above —
|
|
252
|
+
the tool rejects a report with no evidence, so for `unknown` write what you
|
|
253
|
+
searched and why the result is inconclusive;
|
|
254
|
+
use `assumptions` for anything softer (confidence, caveats, analysis limits).
|
|
255
|
+
- **Always set `contextual_cvss_breakdown` + `contextual_cvss_reasoning`.** Every
|
|
256
|
+
dependency finding carries a contextual rating of the CVE in this codebase
|
|
257
|
+
(see below). Start from the published metrics and change only what your
|
|
258
|
+
evidence proves.
|
|
259
|
+
- Set every other field the report accepts when the information exists:
|
|
260
|
+
`package`, `ecosystem`, `installed_version`, `fixed_version`, `manifest_path`,
|
|
261
|
+
`introduced_by` for a transitive package, `dependency_path`, `cwe`,
|
|
262
|
+
`assumptions`, and the remediation instruction. A blank field costs the reader
|
|
263
|
+
a triage step.
|
|
264
|
+
|
|
265
|
+
### Contextual CVSS
|
|
266
|
+
|
|
267
|
+
The published score rates the CVE in the abstract. `contextual_cvss_breakdown`
|
|
268
|
+
rates it **here**, in this codebase, and every dependency report must carry
|
|
269
|
+
one. It is the same 8-metric CVSS v3.1 object as a
|
|
270
|
+
normal finding's `cvss_breakdown` (`attack_vector`, `attack_complexity`,
|
|
271
|
+
`privileges_required`, `user_interaction`, `scope`, `confidentiality`,
|
|
272
|
+
`integrity`, `availability`). You never pass a score: the contextual score and
|
|
273
|
+
vector are computed from the breakdown, and when you provide one it determines
|
|
274
|
+
the finding's severity. `advisory_cvss` stays the published reference.
|
|
275
|
+
|
|
276
|
+
Start from the advisory's own published metrics and change only what your
|
|
277
|
+
evidence proves is different in this codebase:
|
|
278
|
+
|
|
279
|
+
- `attack_vector` `N`/`A`/`L`/`P` — as deployed. A library reached only by a
|
|
280
|
+
local CLI is `L`, not `N`.
|
|
281
|
+
- `attack_complexity` `L`/`H` — raise to `H` when the vulnerable path needs a
|
|
282
|
+
precondition the code enforces (input validation, a non-default flag, an
|
|
283
|
+
internal-only route).
|
|
284
|
+
- `privileges_required` `N`/`L`/`H`, `user_interaction` `N`/`R` — what this
|
|
285
|
+
deployment requires before the path is reachable.
|
|
286
|
+
- `scope` `U`/`C` — whether exploitation here escapes the component boundary.
|
|
287
|
+
- `confidentiality`/`integrity`/`availability` `N`/`L`/`H` — the impact in this
|
|
288
|
+
codebase. `not_imported` code the build still ships is usually `N` across all
|
|
289
|
+
three.
|
|
290
|
+
|
|
291
|
+
Ground every metric in the **source-to-sink trace** from the usage analysis
|
|
292
|
+
(step 3 above), not in a general impression of the package. Derive the metrics
|
|
293
|
+
from that chain: `attack_vector`, `privileges_required`, and `user_interaction`
|
|
294
|
+
come from what the source requires; `attack_complexity` comes from the
|
|
295
|
+
preconditions the hops enforce; `confidentiality`, `integrity`, and
|
|
296
|
+
`availability` come from the data and privileges available at the sink.
|
|
297
|
+
|
|
298
|
+
When you have no source-to-sink trace, still rate the finding: copy the
|
|
299
|
+
published metrics, change only the metrics the usage level itself proves, and
|
|
300
|
+
say so in the reasoning. For example, for a `not_imported` package that the
|
|
301
|
+
build still ships, keep the published metrics and lower `confidentiality`,
|
|
302
|
+
`integrity`, and `availability` to `N`, because no code path reaches the
|
|
303
|
+
vulnerable symbol. Never invent a hop you did not read.
|
|
304
|
+
|
|
305
|
+
`contextual_cvss_reasoning` is required with the breakdown. Write two to four
|
|
306
|
+
sentences that another engineer can check without opening the repository. Name
|
|
307
|
+
the chain hop by hop as `entry point -> intermediate call -> package call`, with
|
|
308
|
+
a repository-relative `file:line` for each hop, say who controls the input, and
|
|
309
|
+
say what the contextual rating changes. Example: lowering `attack_vector` to
|
|
310
|
+
`L` and `confidentiality` to `L` with "The only caller of `yaml.load` is
|
|
311
|
+
`parse_manifest` in `scripts/import.py:88`, which `cli/commands.py:212` invokes
|
|
312
|
+
for an operator-supplied path behind the `--allow-unsafe-import` flag that
|
|
313
|
+
`deploy/prod.yaml` never sets. No HTTP route reaches that function, so an
|
|
314
|
+
attacker must already hold shell access on the job host, and the parsed data is
|
|
315
|
+
build metadata rather than customer records."
|
|
316
|
+
|
|
317
|
+
When the published rating already fits this codebase, repeat the published
|
|
318
|
+
metrics in the breakdown and say in the reasoning that the deployment matches
|
|
319
|
+
the advisory. A contextual rating is a claim you must be able to defend, and it
|
|
320
|
+
never replaces `advisory_cvss` as the published reference.
|
|
321
|
+
|
|
322
|
+
Verify the CVE with `web_search` when available before reporting. Never guess or
|
|
323
|
+
hallucinate a CVE id.
|
|
324
|
+
|
|
325
|
+
## Anti-patterns
|
|
326
|
+
|
|
327
|
+
- Do not report a dependency CVE with `create_vulnerability_report`; use
|
|
328
|
+
`create_dependency_report`.
|
|
329
|
+
- Do not report a finding without a verified CVE id.
|
|
330
|
+
- Do not batch multiple CVEs into one report.
|
|
331
|
+
- Do not omit `advisory_cvss` — the tool rejects it, and it rates every finding
|
|
332
|
+
that carries no contextual breakdown.
|
|
333
|
+
- Do not silently drop a known CVE because it lacks a dynamic PoC — that is the
|
|
334
|
+
exact failure this skill prevents.
|
|
335
|
+
- Do not downgrade advisory severity for lack of dynamic reproduction.
|
|
336
|
+
- Do not claim a `reachability` level the evidence does not prove — `unknown`
|
|
337
|
+
with a reason is always acceptable; an overclaimed level never is.
|
|
338
|
+
- Do not send a report without `contextual_cvss_breakdown` and
|
|
339
|
+
`contextual_cvss_reasoning` — the reader rates and ranks the finding with them.
|
|
340
|
+
- Do not use the contextual breakdown to quietly de-rate a CVE you could not
|
|
341
|
+
analyze. State the limit of the analysis in the reasoning instead.
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: npx-confusion
|
|
3
|
+
description: Test package and executable identity confusion in npx, npm exec, and bunx fallback, plus explicit auto-fetch runners such as pnpm/yarn dlx and deno run npm:, with runner-specific resolution analysis, registry-state controls, reporting gates, and false-positive elimination
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# npx Confusion
|
|
7
|
+
|
|
8
|
+
Use this skill when a package runner may execute code from a package other than the publisher or package the workflow intended. For `npx`, `npm exec`, and `bunx`, the recurring case is a missing local executable being reinterpreted as a remotely fetched package spec. Explicit auto-fetch runners such as `pnpm dlx`, `yarn dlx`, and `deno run npm:` have different semantics; analyze them as an adjacent package-identity problem rather than pretending they share npm's fallback order.
|
|
9
|
+
|
|
10
|
+
Load `dependency_cve_scanning` for known vulnerable versions, `infrastructure_lifecycle` for abandoned domains or registry resources, `agentic_system_security` for the authority of an MCP/agent process, and `semantic_confusion` for the general lookup-order model.
|
|
11
|
+
|
|
12
|
+
## Core Condition
|
|
13
|
+
|
|
14
|
+
Choose the branch that matches the runner.
|
|
15
|
+
|
|
16
|
+
For local-first fallback (`npx`, `npm exec`, or `bunx`), require all of the following:
|
|
17
|
+
|
|
18
|
+
1. A target-controlled workflow invokes a bare executable or ambiguous package token.
|
|
19
|
+
2. The intended package and its executable name differ, or other evidence establishes the expected publisher/package.
|
|
20
|
+
3. The executable is not resolved in the workflow's real local, workspace, global, or cache context as applicable to that runner.
|
|
21
|
+
4. The runner consequently selects an unintended remote package spec from its configured registry.
|
|
22
|
+
5. The affected workflow reaches that package's executable with security-relevant authority.
|
|
23
|
+
|
|
24
|
+
For explicit auto-fetch runners (`pnpm dlx`/`pnx`/`pnpx`, `yarn dlx`, or `deno run npm:`), do not require or claim a missing-local-binary fallback. Require evidence that the command names or infers a package different from the one the workflow intended, such as a scoped-package/bin mismatch, typo, generated configuration error, or wrong publisher. Then prove the exact fetched package, chosen binary/module, execution path, and inherited authority.
|
|
25
|
+
|
|
26
|
+
A public package merely being outside the target's ownership is not a vulnerability. Third-party packages are normal; the mismatch between intended executable provenance and actual registry resolution is the finding.
|
|
27
|
+
|
|
28
|
+
## Resolution Model
|
|
29
|
+
|
|
30
|
+
Record the npm version because `npx` has used `npm exec` since npm 7 and resolver behavior changes between releases. For npm, model these decisions:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
bare command
|
|
34
|
+
-> executable in ancestor node_modules/.bin?
|
|
35
|
+
-> executable in global bin?
|
|
36
|
+
-> matching local/global package and usable bin?
|
|
37
|
+
-> matching environment in the npx cache?
|
|
38
|
+
-> treat the command token as a package spec
|
|
39
|
+
-> fetch its manifest from the configured registry
|
|
40
|
+
-> infer one executable from package.json#bin
|
|
41
|
+
-> install into the npx cache and execute
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Also record:
|
|
45
|
+
|
|
46
|
+
- working directory and workspace root
|
|
47
|
+
- local dependency tree and generated `node_modules/.bin` links
|
|
48
|
+
- global prefix/bin directory and npx cache
|
|
49
|
+
- `registry`, scope-specific registry rules, proxy and authentication configuration
|
|
50
|
+
- command form, flags, package spec/version, TTY/CI state, and `yes` policy
|
|
51
|
+
- npm's executable-inference result when the package exposes zero, one, or several `bin` entries
|
|
52
|
+
|
|
53
|
+
Do not collapse package-name lookup and bin selection into one step. npm can fetch a manifest yet fail because it cannot infer exactly one executable.
|
|
54
|
+
|
|
55
|
+
### Runner distinctions
|
|
56
|
+
|
|
57
|
+
Record the exact runner and version. Do not reuse npm's local/global/cache ordering for another implementation.
|
|
58
|
+
|
|
59
|
+
| Runner | Resolution behavior to model | Package binding / fetch control |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `npx` / `npm exec` | Local/workspace/global/cache resolution followed by package-spec fallback; executable inference depends on `package.json#bin` | `--package <pkg>` binds the provider; `--no` rejects an install prompt |
|
|
62
|
+
| `bunx` | Checks a locally installed package, then can install from npm into Bun's cache | `--package <pkg>` binds the provider; `--no-install` forbids installation |
|
|
63
|
+
| `yarn dlx` | Downloads the command-named package into a temporary environment by default; this is not a local-bin fallback | `--package <pkg>` selects a different provider package |
|
|
64
|
+
| `pnpm dlx` / `pnx` / `pnpx` | Fetches and hotloads a registry package, then runs its default binary; project trust policies are version-dependent | `--package=<pkg>` selects the provider; prefer declared dependencies plus `pnpm exec` when remote fetch is unintended |
|
|
65
|
+
| `deno run npm:<pkg>` | Uses an explicit npm package spec and cache; a subpath can select a binary | Pin the package/subpath and model lock, cache, lifecycle-script, and Deno permission settings |
|
|
66
|
+
|
|
67
|
+
Treat mutable tags and ranges such as `latest`, `next`, `@2`, caret, and tilde ranges as selectors, not pins. A privileged repeatable workflow needs an exact reviewed version plus lockfile/integrity enforcement where the runner supports it.
|
|
68
|
+
|
|
69
|
+
## High-Signal Patterns
|
|
70
|
+
|
|
71
|
+
### Bare executable fallback
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
npx internal-tool
|
|
75
|
+
npx -y internal-tool
|
|
76
|
+
npm exec -- internal-tool
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The signal is strongest in CI, release scripts, bootstrap commands, developer setup, and tool/agent configuration where the same command is run repeatedly.
|
|
80
|
+
|
|
81
|
+
### Scoped package versus unscoped bin
|
|
82
|
+
|
|
83
|
+
A scoped package can expose an unscoped executable:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"name": "@org/tooling",
|
|
88
|
+
"bin": { "org-tool": "./bin/run.js" }
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Inside a correctly installed workspace, `npx org-tool` may resolve `node_modules/.bin/org-tool`. Outside that tree, the same command can fall back to the public package named `org-tool`. Treat documentation, MCP configuration, and bootstrap scripts as separate execution contexts rather than assuming the repository-local result applies everywhere.
|
|
93
|
+
|
|
94
|
+
### Agent and MCP launchers
|
|
95
|
+
|
|
96
|
+
Inspect `.mcp.json`, editor/desktop agent configuration, devcontainers, and generated tool launchers for `command: npx` plus `-y` and a bare package or binary name. Combine this resolver analysis with `agentic_system_security` to determine the credentials, tools, files, and network access inherited by that process.
|
|
97
|
+
|
|
98
|
+
## Candidate Collection
|
|
99
|
+
|
|
100
|
+
Search executable surfaces and retain file, line, command, and execution context:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
rg -n --no-heading -g '!node_modules' -g '!**/dist/**' \
|
|
104
|
+
-e '\b(npx|npm\s+exec|bunx|pnx|pnpx|pnpm\s+dlx|yarn\s+dlx)\s+[^[:space:]]+' \
|
|
105
|
+
-e '\bdeno\s+run\b[^\n]*\bnpm:' \
|
|
106
|
+
-e '"command"\s*:\s*"(npx|bunx|pnx|pnpx|pnpm|yarn|deno)"' \
|
|
107
|
+
-e '"args"\s*:\s*\[[^]]*"(dlx|npm:[^"]+|-y)"' \
|
|
108
|
+
.
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Search the source/configuration tree rather than a fixed file list: these commands also live in
|
|
112
|
+
`scripts/`, husky/lint-staged hooks, `turbo.json`/`nx.json` task definitions,
|
|
113
|
+
`.circleci/`, composite-action `action.yml`, devcontainer `postCreateCommand`,
|
|
114
|
+
nested workspace `package.json` files, and editor/agent config under
|
|
115
|
+
`.cursor/`, `.vscode/`, and `.mcp.json`. If generated output is itself shipped or executed, search its specific directory separately instead of globally including every `dist/` artifact.
|
|
116
|
+
|
|
117
|
+
Also inspect:
|
|
118
|
+
|
|
119
|
+
- package scripts and lifecycle hooks
|
|
120
|
+
- workspace package `name` and `bin` maps
|
|
121
|
+
- READMEs and generated setup instructions
|
|
122
|
+
- CI composite actions and reusable workflows
|
|
123
|
+
- source maps or bundled package metadata that reveal internal commands
|
|
124
|
+
|
|
125
|
+
Discard paths, shell variables, flags, Node built-ins, and text that is not executed or presented as an executable command.
|
|
126
|
+
|
|
127
|
+
## Establish the Actual Resolution
|
|
128
|
+
|
|
129
|
+
Prefer inspecting the existing dependency tree, lockfile, workspace packages, and `.bin` links. Do not run `npm ci` merely to decide whether a command is local: it changes the tree and can execute lifecycle scripts.
|
|
130
|
+
|
|
131
|
+
For a version-controlled reproduction environment, record npm's registry lookup without allowing a missing package to be installed:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npx --no --loglevel=http <candidate>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Interpret this carefully:
|
|
138
|
+
|
|
139
|
+
- a local executable may run immediately; `--no` only refuses missing-package installation
|
|
140
|
+
- an HTTP registry request shows fallback, not ownership or successful execution
|
|
141
|
+
- a cancellation naming the missing package shows npm's chosen package spec
|
|
142
|
+
- cache, global installs, parent directories, workspaces, and registry configuration can change the result
|
|
143
|
+
|
|
144
|
+
Repeat the resolution analysis in every context that matters: repository root, documented launch directory, CI checkout, generated agent configuration, and bootstrap-before-install flow. Do not substitute a clean empty directory for the target context except to understand npm's generic name mapping.
|
|
145
|
+
|
|
146
|
+
Do not apply `npx --no` as a generic dry-run flag. Use `bunx --no-install` only for Bun's local-resolution question. `dlx` and `deno run npm:` already name a remotely resolvable package, so validate their package spec, registry, cache/lock, selected binary or subpath, and permissions using that runner's own behavior.
|
|
147
|
+
|
|
148
|
+
## Ownership and Registry State
|
|
149
|
+
|
|
150
|
+
Query the exact registry selected by the target configuration, then distinguish:
|
|
151
|
+
|
|
152
|
+
- intended package owned by the expected publisher
|
|
153
|
+
- unrelated public package with the same name
|
|
154
|
+
- unregistered name (`404` from a functioning registry)
|
|
155
|
+
- private or access-controlled name (`401`/`403`)
|
|
156
|
+
- transient/rate-limited/blocked lookup (`429`, `5xx`, timeout)
|
|
157
|
+
- placeholder, reserved, disputed, or previously unpublished name
|
|
158
|
+
|
|
159
|
+
Before trusting any of those states, check whether the target's lookup path can distinguish a known existing package from a newly generated negative control. Resolve the registry from the same working directory and configuration used by the target:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Public npm example; use a known package from the actual registry when different.
|
|
163
|
+
task_registry="$(npm config get registry)"
|
|
164
|
+
npm view --registry="$task_registry" lodash name --json
|
|
165
|
+
npm view --registry="$task_registry" "$(openssl rand -hex 12)" name --json
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Run the pair through the same `.npmrc`, scope routing, authentication, proxy, and egress path as the candidate. Direct `curl` requests to the public registry are a separate observation unless the target runner uses that exact route. A successful pair establishes coarse positive/negative discrimination, not authenticity of every candidate response; verify that returned documents name the requested package and contain plausible registry metadata.
|
|
169
|
+
|
|
170
|
+
If the pair fails or returns indistinguishable responses, mark the target-path registry state `UNKNOWN`. An independently verified public-registry response may characterize public state, but it does not prove what the target runner resolves. Re-confirm candidate absence before relying on it.
|
|
171
|
+
|
|
172
|
+
A `404` proves absence from that registry at that time; it does not by itself prove that registration would be accepted. Registry similarity, trademark, reservation, security-hold, and unpublish rules remain separate facts. Two concrete cases to check rather than infer:
|
|
173
|
+
|
|
174
|
+
- A registry-owned security placeholder occupies the name even when its only version is `0.0.1-security`. Do not identify one from the version alone: inspect the packument, description, dist-tags, top-level and version-level maintainers, and version publisher such as `_npmUser`.
|
|
175
|
+
- npm rejects new unscoped names that collide with an existing package after `.`, `-`, and `_` are removed. Normalize both the candidate and existing names: looking up only the candidate's stripped form catches `some-tool` versus `sometool`, but misses the reverse direction when the existing package contains punctuation. Treat this as registry-policy eligibility evidence, not a guarantee that registration would otherwise succeed.
|
|
176
|
+
|
|
177
|
+
When a candidate name is already registered, distinguish the target's own
|
|
178
|
+
organization from an unrelated party before calling it a clash. Correlate `npm owner ls <name>`, version-level publisher metadata, known target-controlled npm organizations, and independently verified repository provenance. Repository/homepage fields are self-asserted supporting evidence and do not settle ownership alone. If publisher identity remains ambiguous, mark it `UNKNOWN`.
|
|
179
|
+
|
|
180
|
+
## Validation and Impact
|
|
181
|
+
|
|
182
|
+
Demonstrate the complete resolver statement:
|
|
183
|
+
|
|
184
|
+
```text
|
|
185
|
+
target-controlled invocation and context
|
|
186
|
+
-> intended executable absent
|
|
187
|
+
-> exact public package spec selected
|
|
188
|
+
-> package ownership/availability state
|
|
189
|
+
-> execution trigger and inherited authority
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Do not report an unregistered name without an execution path, or an execution path whose command is satisfied locally in every relevant context. Derive impact from the environment that executes the package: developer workstation, CI job, release pipeline, agent runtime, container build, or documentation-only workflow.
|
|
193
|
+
|
|
194
|
+
## Reporting
|
|
195
|
+
|
|
196
|
+
There is no CVE and no vulnerable installed version here, so this does not go through `create_dependency_report`; that tool requires an advisory-matched CVE. Use `create_vulnerability_report` only after the applicable core condition is fully verified.
|
|
197
|
+
|
|
198
|
+
A registry lookup or `404` alone is candidate evidence, not a working PoC. The report must preserve the target invocation and execution context, show the exact selected package and binary/module, demonstrate the runner's execution transition in a representative controlled setup without publishing the contested name, and establish the authority inherited by that process. When source is available, include the responsible invocation/configuration and concrete fix in `code_locations`.
|
|
199
|
+
|
|
200
|
+
Do not file documentation/comment-only references, locally satisfied commands, unregisterable names, ambiguous ownership, or chains that stop before package execution. Retain them as investigation notes only when useful.
|
|
201
|
+
|
|
202
|
+
Derive CVSS from the demonstrated path rather than a fixed severity label. Account for required developer/user action, registry and configuration prerequisites, runner permissions, credential availability, and the confidentiality, integrity, and availability actually exposed. A CI, release, container-build, or agent context can be severe, but the context name alone does not establish High or Critical impact.
|
|
203
|
+
|
|
204
|
+
Deduplicate by root cause, affected asset/workflow, and remediation. Combine call sites when the same configuration mistake and fix apply; keep separate findings when the same candidate name affects different products, tenants, runner semantics, authority, or fixes.
|
|
205
|
+
|
|
206
|
+
## False Positives
|
|
207
|
+
|
|
208
|
+
- The executable is provided by a declared dependency in every real execution context.
|
|
209
|
+
- `npx --package @scope/pkg <bin>` explicitly binds the executable to the intended package.
|
|
210
|
+
- A versioned package spec or scope-specific registry points to the intended publisher.
|
|
211
|
+
- The public package is the deliberately selected third-party tool.
|
|
212
|
+
- npm fetches the manifest but cannot infer or execute a bin.
|
|
213
|
+
- The reference appears only in generated/minified text with no executable call site.
|
|
214
|
+
- A registry/proxy error is misread as an unregistered name, or the target-path control pair is inconclusive.
|
|
215
|
+
- A package is absent but registry policy prevents the contested registration.
|
|
216
|
+
- The command resolves to the deliberately selected ecosystem tool and expected publisher.
|
|
217
|
+
- The already-registered name belongs to the target's own organization.
|
|
218
|
+
- An explicit `dlx` or `npm:` package spec is treated as missing-local fallback without evidence of a package/publisher mismatch.
|
|
219
|
+
|
|
220
|
+
## Remediation
|
|
221
|
+
|
|
222
|
+
- Install the intended package and invoke its local executable through an npm script.
|
|
223
|
+
- For npm, bind and pin the provider: `npx --package @org/tool@<version> org-tool`; use `--no` when a missing dependency must fail.
|
|
224
|
+
- For Bun, use `bunx --package @org/tool@<version> org-tool` and `--no-install` when remote installation is not intended.
|
|
225
|
+
- Replace `yarn dlx`/`pnpm dlx` in repeatable or privileged workflows with a declared, locked dependency plus the runner's local `exec` command. When ephemeral execution is required, bind and pin the provider package explicitly.
|
|
226
|
+
- For Deno, pin the `npm:` package and binary subpath, retain a reviewed lockfile, use cache-only operation where appropriate, and grant only the permissions the command requires.
|
|
227
|
+
- Route private scopes to the intended registry and prevent public fallback.
|
|
228
|
+
- Pin package versions and lockfiles in privileged workflows.
|
|
229
|
+
- Replace bare `npx -y <name>` agent launchers with reviewed, publisher-qualified, version-pinned package specs.
|
|
230
|
+
|
|
231
|
+
## Summary
|
|
232
|
+
|
|
233
|
+
Treat package-runner confusion as an identity and execution-context bug. Prove the runner-specific transition, distinguish binary names from package names, verify registry and publisher state without equating absence with eligibility, and report only a complete execution path under the affected workflow's actual authority.
|