Skip to content
Draft
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
58 changes: 58 additions & 0 deletions components/git/security.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PrepareSecurityRelease from '../../lib/prepare_security.js';
import UpdateSecurityRelease from '../../lib/update_security_release.js';
import SecurityBlog from '../../lib/security_blog.js';
import SecurityAnnouncement from '../../lib/security-announcement.js';
import ValidateReports from '../../lib/validate_reports.js';

export const command = 'security [options]';
export const describe = 'Manage an in-progress security release or start a new one.';
Expand Down Expand Up @@ -40,6 +41,52 @@ const securityOptions = {
describe: 'Request CVEs for a security release',
type: 'boolean'
},
'validate-reports': {
describe: 'Validate triaged HackerOne reports against the Node.js threat model',
type: 'boolean'
},
'validate-reports-format': {
choices: ['markdown', 'json'],
default: 'markdown',
describe: 'Output format for --validate-reports',
type: 'string'
},
'validate-reports-output': {
describe: 'Write --validate-reports output to a file instead of stdout',
type: 'string'
},
'validate-reports-limit': {
describe: 'Maximum number of triaged reports to validate',
type: 'number'
},
'validate-reports-confirm': {
default: true,
describe: 'Ask before continuing to the next report after each LLM assessment',
type: 'boolean'
},
'validate-reports-cache': {
default: true,
describe: 'Reuse cached LLM assessments for the same report, model, and prompt',
type: 'boolean'
},
llm: {
choices: ['codex', 'claude', 'copilot'],
describe: 'Ask an LLM CLI to assess each triaged report',
type: 'string'
},
'llm-model': {
describe: 'Override the LLM model used for command construction and cache identity',
type: 'string'
},
'llm-command': {
describe: 'Override the command used for --llm. The report prompt is sent on stdin.',
type: 'string'
},
'node-repo': {
default: process.cwd(),
describe: 'Node.js checkout path the LLM should use to read SECURITY.md and doc/',
type: 'string'
},
'post-release': {
describe: 'Create the post-release announcement to the given nodejs.org folder',
type: 'string'
Expand Down Expand Up @@ -82,6 +129,9 @@ export function builder(yargs) {
'git node security --request-cve',
'Request CVEs for a security release of Node.js based on' +
' the next-security-release/vulnerabilities.json'
).example(
'git node security --validate-reports',
'Validate triaged HackerOne reports against the Node.js threat model'
).example(
'git node security --post-release="../nodejs.org/"',
'Create the post-release announcement on the Nodejs.org repo'
Expand Down Expand Up @@ -119,6 +169,9 @@ export function handler(argv) {
if (argv['request-cve']) {
return requestCVEs(cli, argv);
}
if (argv['validate-reports']) {
return validateReports(cli, argv);
}
if (argv['post-release']) {
return createPostRelease(cli, argv);
}
Expand Down Expand Up @@ -157,6 +210,11 @@ async function requestCVEs(cli) {
return hackerOneCve.requestCVEs();
}

async function validateReports(cli, argv) {
const validator = new ValidateReports(cli, argv);
return validator.validate();
}

async function createPostRelease(cli, argv) {
const nodejsOrgFolder = argv['post-release'];
const blog = new SecurityBlog(cli);
Expand Down
130 changes: 130 additions & 0 deletions docs/git-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,136 @@ Example:
git node security --remove-report=12345
```

### `git node security --validate-reports`
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's missing the statement of fetching previous HackerOne reports and team assessment on those to use as baseline for assessment.


This command retrieves all triaged HackerOne reports for the Node.js program and
produces a local validation report for each one. It is intended to help the
security team review whether a report still looks valid under the Node.js threat
model and whether the current HackerOne severity/CVSS is consistent with the
available evidence.

The command uses the existing HackerOne credentials configured in `.ncurc`. It
does not modify HackerOne reports, labels, comments, or severity. The output is
only a local triage aid and still requires human review.

```sh
git node security --validate-reports
git node security --validate-reports --validate-reports-format=json
git node security --validate-reports --validate-reports-output=reports.md
git node security --validate-reports --llm=codex --node-repo=/path/to/node
git node security --validate-reports --llm=codex --llm-model=gpt-5.5
git node security --validate-reports --llm=codex --no-validate-reports-confirm
git node security --validate-reports --llm=codex --no-validate-reports-cache
git node security --validate-reports --llm=claude --node-repo=/path/to/node
git node security --validate-reports --llm=copilot --node-repo=/path/to/node
git node security --validate-reports --llm=copilot --llm-command="copilot -p"
```

By default, the command runs a heuristic pass only. The heuristic checks the
report title, vulnerability information, impact, description, comments, current
severity, CVSS vector, and weakness metadata for common Node.js security topics.
It can identify obvious mismatches, such as a CVSS vector whose calculated
rating does not match the HackerOne rating. Keyword matches are treated only as
topic hints, not as proof that a report is valid or invalid. This is deliberate:
HackerOne report text is reporter-controlled, so words like `request smuggling`
or `permission model` are not enough to make a threat-model decision. The
heuristic output is deliberately conservative and always leaves threat-model
validity as `needs-manual-review`.

Use `--llm=<provider>` to ask an LLM CLI to produce a structured assessment for
each report. Supported providers are `codex`, `claude`, and `copilot`.

When LLM mode is enabled, the command asks before assessing each report and shows
the report title, current severity, CVSS vector, and weakness. After each LLM
assessment, it prints a readable summary with:

- the report URL and title
- the provider and model/cache identity
- validity under the Node.js threat model
- whether the current severity is correct
- current severity/CVSS and suggested severity/CVSS
- a colored CVSS metric diff when the suggested vector differs
- CWE
- confidence from 0 to 100
- threat model/documentation references used by the model
- reasoning

Use `--no-validate-reports-confirm` for batch mode without the per-report
prompts. Use `--validate-reports-limit=<n>` to test the flow against a smaller
number of reports.

#### LLM prompt and reasoning

The LLM prompt is designed to keep the model anchored to the Node.js threat
model and local documentation instead of only reasoning from the report text.
For each report, the prompt instructs the model to:

- read `SECURITY.md` from the Node.js checkout supplied through `--node-repo`
- inspect relevant files under `doc/` for the affected API or subsystem
- apply the documented Node.js threat model, including treatment of application
code, caller-supplied API inputs, third-party modules, unsupported platforms,
and inspector/debugger access
- decide whether the report is valid under that threat model
- decide whether the current HackerOne severity/CVSS is correct
- use reports with the same CWE/weakness as precedent context when available
- avoid copying precedent blindly when `SECURITY.md` or `doc/` point to a
different result
- return only JSON matching the schema expected by the command

The report payload sent to the model contains the HackerOne report id, title,
URL, state, current severity, current CVSS vector, weakness metadata, reporter,
report body fields, comments, heuristic findings, and comparable reports with
the same weakness. Comparable reports include their title, URL, current
severity/CVSS, state, and any team summary available through HackerOne. This
helps the model account for previous team decisions while still checking the
current report against the threat model.

The model must return these fields:

- `validity`: `valid`, `invalid`, or `needs-more-info`
- `severity_correct`: boolean
- `suggested_severity`: `none`, `low`, `medium`, `high`, `critical`, or
`informational`
- `suggested_cvss`: a CVSS vector or `N/A`
- `cwe`: the best matching CWE
- `confidence`: a number from 0 to 100
- `reasoning`: a concise explanation
- `threat_model_references`: references to `SECURITY.md` and relevant `doc/`
material used for the decision

#### LLM commands and cache

The `--llm-command` option can override the default provider command. The prompt
is sent on stdin and the command must print a JSON object matching the expected
schema.

The model label is inferred from the local LLM CLI configuration when possible.
For example, Codex reads `~/.codex/config.toml` and includes
`model_reasoning_effort` in the cache label. Use `--llm-model` to override the
provider command model and the cache identity. If the model cannot be inferred,
the cache entry includes a comment explaining that `default` was used.

Successful LLM assessments are cached locally in
`.ncu-cache/security-report-validation` using the report, provider, model, and
prompt as the cache key. Use `--no-validate-reports-cache` to force a fresh LLM
assessment.

#### Options

| Option | Description |
| --- | --- |
| `--validate-reports-format=markdown\|json` | Select the final output format. Defaults to `markdown`. |
| `--validate-reports-output=<file>` | Write the final output to a file instead of stdout. |
| `--validate-reports-limit=<n>` | Validate at most `n` triaged reports. Useful for testing the flow. |
| `--validate-reports-confirm` | Ask before each LLM assessment and before continuing to the next report. Enabled by default. |
| `--no-validate-reports-confirm` | Disable interactive prompts for batch runs. |
| `--validate-reports-cache` | Reuse cached successful LLM assessments. Enabled by default. |
| `--no-validate-reports-cache` | Ignore existing LLM cache entries and do not reuse them. |
| `--llm=codex\|claude\|copilot` | Ask an LLM CLI to assess each report. |
| `--llm-model=<model>` | Override the provider model and cache identity. |
| `--llm-command=<command>` | Override the command used for LLM assessment. The prompt is sent on stdin. |
| `--node-repo=<path>` | Path to a Node.js checkout containing `SECURITY.md` and `doc/`. Defaults to the current directory. |

## `git node status`

Return status and information about the current git-node land session. Shows the following information:
Expand Down
Loading
Loading