Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/runx-cli/src/official_skills.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ pub(crate) const OFFICIAL_SKILLS: &[OfficialSkillLockEntry] = &[
version: "sha-c2d071df7f50",
digest: "08cefe802c15e5be7d32ae9a363a6c42168e86f7fab92890e5ce5c994af367c9",
},
OfficialSkillLockEntry {
skill_id: "runx/dependency-advisory-graph",
version: "sha-4bd3c2511486",
digest: "5e7cc5d157ef0383d9574812242dc9bb41321caa5be645abd5bfcb2c2b4a2bf1",
},
OfficialSkillLockEntry {
skill_id: "runx/dependency-cve-audit",
version: "sha-6db720882ba0",
Expand Down
7 changes: 7 additions & 0 deletions packages/cli/src/official-skills.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@
"catalog_visibility": "public",
"catalog_role": "context"
},
{
"skill_id": "runx/dependency-advisory-graph",
"version": "sha-4bd3c2511486",
"digest": "5e7cc5d157ef0383d9574812242dc9bb41321caa5be645abd5bfcb2c2b4a2bf1",
"catalog_visibility": "public",
"catalog_role": "canonical"
},
{
"skill_id": "runx/dependency-cve-audit",
"version": "sha-6db720882ba0",
Expand Down
208 changes: 208 additions & 0 deletions skills/dependency-advisory-graph/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
---
name: dependency-advisory-graph
description: Audit locked npm dependencies against OSV advisories and emit exact-version advisory graph evidence.
source:
type: cli-tool
command: node
args:
- run.mjs
runx:
tags:
- security
- dependencies
- osv
links:
source: https://github.com/RYDE-PLAY/runx/tree/ryde-play/dependency-advisory-graph/skills/dependency-advisory-graph
advisory_source: https://osv.dev
---

## What this skill does

This skill audits a Node.js project's committed `package-lock.json` for known
vulnerabilities in npm dependencies. It extracts exact installed package
versions, queries the OSV API for each exact `{ ecosystem, package, version }`
tuple, and emits a machine-checkable evidence packet plus a concise Markdown
report.

The skill is read-only. It does not install dependencies, execute target code,
modify repositories, open issues, submit advisories, or claim that a package is
safe when OSV returns no matching record. A zero-finding result means only that
OSV did not return advisories for the exact versions scanned under the selected
scope.

## When to use this skill

Use this skill when an agent needs a reproducible dependency vulnerability
snapshot for a public Node.js project, release candidate, benchmark fixture, or
security review packet. It is appropriate when the lockfile is public or has
been explicitly cleared for disclosure to OSV, and when the review needs exact
installed-version evidence rather than semver range guesses.

The skill is especially useful before triage, upgrade planning, advisory
drafting, or reviewer handoff because it preserves the lockfile SHA-256, the
scan policy, the OSV query tuple for every finding, and references for each
advisory.

## When not to use this skill

Do not use this skill for private lockfiles unless the package names, versions,
and lockfile URL/path are approved for disclosure to the advisory source. Do not
use it as a full application security review, source-code audit, exploitability
assessment, SBOM generator, package installer, or automated remediation tool.

Do not use the output as proof that a project is vulnerability-free. The result
depends on OSV coverage at run time, the selected dependency scope, and the
contents of the supplied lockfile.

## Procedure

1. Read `package_lock_path` or `package_lock_url`.
2. Record byte length and SHA-256 for the lockfile input.
3. Extract exact installed package versions from the lockfile.
4. Limit the scan to the declared `scan_scope` and `include_dev` policy.
5. Query OSV only with exact npm package names and exact installed versions.
6. Keep only vulnerabilities returned by OSV for that exact version query.
7. Emit `dependency.advisory.graph.result.v1` with findings, query evidence, and validation.
8. Write `evidence.json` and `report.md` when `output_dir` is provided.

## Edge cases and stop conditions

Stop with an error when neither `package_lock_path` nor `package_lock_url` is
provided, when the lockfile cannot be read, when the URL is not HTTPS, when the
lockfile is not valid JSON, or when it does not contain a `packages` object.

For local paths and output paths, the resolved path must stay inside the skill
directory. This prevents the runner from reading or writing unrelated workspace
files.

If an OSV request fails, stop instead of returning a partial success packet. If
`scan_scope` is `direct`, skip direct dependencies whose installed package entry
is missing from `packages`; do not invent versions from semver declarations.

The output is evidence for dependency triage, not an authorization to publish a
security advisory or mutate a repository. Any later issue filing, advisory
publication, or remediation PR needs its own gate.

## Output schema

The primary output is `dependency_advisory_graph_result`, with schema
`dependency.advisory.graph.result.v1`:

```json
{
"schema": "dependency.advisory.graph.result.v1",
"data": {
"target": {
"name": "string | null",
"repo": "string | null",
"ref": "string | null"
},
"source": {
"kind": "file | url",
"ref": "string",
"bytes": 0,
"sha256": "hex"
},
"scanner": {
"name": "dependency-advisory-graph",
"version": "0.1.0",
"advisory_source": "OSV.dev v1 query API",
"advisory_endpoint": "https://api.osv.dev/v1/query"
},
"policy": {
"ecosystem": "npm",
"scan_scope": "direct | all",
"include_dev": false,
"target_code_executed": false,
"target_packages_installed": false,
"finding_rule": "string"
},
"summary": {
"dependencies_scanned": 0,
"packages_with_findings": 0,
"findings": 0
},
"dependencies": [],
"findings": [],
"typed_findings": [
{
"package": "string",
"installed_version": "string",
"advisory_id": "string",
"evidence_url": "string",
"advisory_source": "https://api.osv.dev/v1/query",
"retrieved_at": "ISO-8601 timestamp",
"severity": "string",
"fix_version": "string | null",
"confidence": "high"
}
],
"advisory_graph": {
"schema": "dependency.advisory.graph.v1",
"nodes": [],
"edges": []
},
"validation": {
"valid": true,
"every_finding_has_exact_version": true,
"every_finding_has_reference": true,
"every_finding_has_advisory_id": true,
"zero_false_hit_control": "string"
}
}
}
```

When `output_dir` is provided, the runner also writes `evidence.json` and
`report.md` inside that directory and returns their relative paths in
`data.artifacts`.

## Worked example

The harness scans OWASP NodeGoat at commit
`c5cb68a7084e4ae7dcc60e6a98768720a81841e8` using the committed
`package-lock.json`:

```bash
runx skill "$PWD" \
--input target_name="OWASP NodeGoat" \
--input target_repo=https://github.com/OWASP/NodeGoat \
--input target_ref=c5cb68a7084e4ae7dcc60e6a98768720a81841e8 \
--input package_lock_url=https://raw.githubusercontent.com/OWASP/NodeGoat/c5cb68a7084e4ae7dcc60e6a98768720a81841e8/package-lock.json \
--input scan_scope=direct \
--input include_dev=false \
--input output_dir=artifacts/nodegoat \
--json
```

Expected evidence shape:

- `source.sha256` records the fetched lockfile hash.
- `summary.dependencies_scanned` is the number of direct production npm
dependencies found in the lockfile.
- Each finding contains the exact installed version, OSV advisory id, aliases,
fixed versions when listed, affected ranges, and references.
- `typed_findings` exposes the review-critical fields required by the bounty:
package, installed_version, advisory_id, evidence_url, advisory_source,
retrieved_at, severity, fix_version, and confidence.
- `advisory_graph` connects exact `pkg:<name>@<version>` nodes to
`adv:<advisory_id>` nodes with `affected_by_exact_version` edges.
- `validation.valid` is true only when every finding includes an exact version,
advisory id, and at least one reference.

## Inputs

- `target_name`: human-readable project name.
- `target_repo`: source repository URL.
- `target_ref`: immutable commit or release reference.
- `package_lock_path`: local path to a `package-lock.json`.
- `package_lock_url`: public URL for a `package-lock.json`.
- `scan_scope`: `direct` or `all`; defaults to `direct`.
- `include_dev`: include dev dependencies when true; defaults to false.
- `output_dir`: optional directory for `evidence.json` and `report.md`.

## Outputs

- `dependency_advisory_graph_result`: complete packet.
- `evidence_json`: same evidence as machine-checkable JSON.
- `report_md`: concise report with exact version findings and references.
139 changes: 139 additions & 0 deletions skills/dependency-advisory-graph/X.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
skill: dependency-advisory-graph
version: "0.1.0"

catalog:
kind: skill
audience: public
visibility: public
role: canonical

runx:
mutating: false
idempotency:
key: lockfile_sha256
scopes:
- dependencies.read
- advisories.osv.query
policy:
data_classification: public_dependency_metadata
network:
allowed:
- https://api.osv.dev/v1/query
- https://raw.githubusercontent.com/
forbidden:
- private repositories
- package installation
- source code execution
verifier_notes:
- Every finding is produced from an exact package name and exact installed version query.
- The dogfood fixture pins the target repository to an immutable commit URL.
artifacts:
emits:
- dependency_advisory_graph_result
- typed_findings
- advisory_graph
- evidence_json
- report_md
wrap_as: dependency_advisory_graph_packet

harness:
cases:
- name: nodegoat-direct-production
runner: default
inputs:
target_name: OWASP NodeGoat
target_repo: https://github.com/OWASP/NodeGoat
target_ref: c5cb68a7084e4ae7dcc60e6a98768720a81841e8
package_lock_url: https://raw.githubusercontent.com/OWASP/NodeGoat/c5cb68a7084e4ae7dcc60e6a98768720a81841e8/package-lock.json
scan_scope: direct
include_dev: false
output_dir: artifacts/nodegoat
expect:
status: sealed
- name: clean-lockfile-no-findings
runner: default
inputs:
target_name: Clean local fixture
target_repo: local-fixture
target_ref: clean-package-lock-v1
package_lock_url: https://raw.githubusercontent.com/RYDE-PLAY/runx/ryde-play/dependency-advisory-graph/skills/dependency-advisory-graph/fixtures/empty-package-lock.json
scan_scope: direct
include_dev: false
output_dir: artifacts/clean
expect:
status: sealed
- name: missing-lockfile-stop
runner: default
inputs:
target_name: Missing lockfile fixture
target_repo: local-fixture
target_ref: missing-lockfile-v1
scan_scope: direct
include_dev: false
expect:
status: failure

runners:
default:
default: true
type: cli-tool
command: /usr/bin/env
args:
- node
- run.mjs
scopes:
- dependencies.read
- advisories.osv.query
policy:
reads:
- public npm package lockfiles
calls:
- OSV exact-version query API
writes:
- evidence.json
- report.md
disallows:
- installing target packages
- executing target project code
- using private repository contents
inputs:
target_name:
type: string
required: true
description: Human-readable project name.
target_repo:
type: string
required: true
description: Public source repository URL.
target_ref:
type: string
required: false
description: Immutable commit, tag, or release reference.
package_lock_path:
type: string
required: false
description: Local package-lock.json path inside the skill directory.
package_lock_url:
type: string
required: false
description: Public package-lock.json URL.
scan_scope:
type: string
required: false
default: direct
description: direct or all dependencies.
include_dev:
type: boolean
required: false
default: false
description: Include development dependencies.
output_dir:
type: string
required: false
description: Directory inside the skill directory where artifacts should be written.
outputs:
dependency_advisory_graph_result: object
typed_findings: array
advisory_graph: object
evidence_json: object
report_md: string
9 changes: 9 additions & 0 deletions skills/dependency-advisory-graph/fixtures/clean.inputs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"target_name": "Clean local fixture",
"target_repo": "local-fixture",
"target_ref": "clean-package-lock-v1",
"package_lock_path": "fixtures/empty-package-lock.json",
"scan_scope": "direct",
"include_dev": false,
"output_dir": "artifacts/clean"
}
12 changes: 12 additions & 0 deletions skills/dependency-advisory-graph/fixtures/empty-package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading