@@ -16,6 +16,7 @@ const H1_TRIAGED_REPORTS_URL =
1616 'https://api.hackerone.com/v1/reports?filter[program][]=nodejs&filter[state][]=triaged' ;
1717const CACHE_FOLDER = '.ncu-cache/security-report-validation' ;
1818const MANUAL_REVIEW_VALIDITY = 'needs-manual-review' ;
19+ const MANUAL_LLM_PROVIDER = 'none' ;
1920
2021const CVSS_WEIGHTS = {
2122 AV : { N : 0.85 , A : 0.62 , L : 0.55 , P : 0.2 } ,
@@ -519,14 +520,19 @@ function reportToMarkdown(result) {
519520 ` - References: ${
520521 result . llm . assessment . threat_model_references . join ( ', ' ) } `
521522 ] . join ( '\n' )
522- : result . llm ?. error
523+ : result . llm ?. promptPrinted
523524 ? [
524525 ` - Provider: ${ result . llm . provider } ` ,
525- ` - Error: ${ result . llm . error } `
526+ ' - Prompt printed to terminal for manual LLM assessment'
526527 ] . join ( '\n' )
527- : result . llm ?. skipped
528- ? ` - Provider: ${ result . llm . provider } \n - Skipped by user`
529- : ' - Not requested' ;
528+ : result . llm ?. error
529+ ? [
530+ ` - Provider: ${ result . llm . provider } ` ,
531+ ` - Error: ${ result . llm . error } `
532+ ] . join ( '\n' )
533+ : result . llm ?. skipped
534+ ? ` - Provider: ${ result . llm . provider } \n - Skipped by user`
535+ : ' - Not requested' ;
530536
531537 return [
532538 `### ${ result . id } : ${ result . title } ` ,
@@ -777,6 +783,10 @@ function inferLLMModel(provider, explicitModel) {
777783}
778784
779785function buildProviderCommand ( provider , nodeRepo , commandOverride , model ) {
786+ if ( provider === MANUAL_LLM_PROVIDER ) {
787+ return ;
788+ }
789+
780790 if ( commandOverride ) {
781791 return {
782792 command : commandOverride ,
@@ -845,6 +855,18 @@ function buildProviderCommand(provider, nodeRepo, commandOverride, model) {
845855 }
846856}
847857
858+ function printLLMPrompt ( result , prompt , cli ) {
859+ cli . separator ( `H1 ${ result . id } LLM Prompt` ) ;
860+ cli . log ( [
861+ 'Copy this prompt into any LLM tool. The response should be JSON matching ' +
862+ 'the schema included in the prompt.' ,
863+ '' ,
864+ '----- BEGIN LLM PROMPT -----' ,
865+ prompt . trimEnd ( ) ,
866+ '----- END LLM PROMPT -----'
867+ ] . join ( '\n' ) ) ;
868+ }
869+
848870function cacheDir ( ) {
849871 return path . join ( process . cwd ( ) , CACHE_FOLDER ) ;
850872}
@@ -879,11 +901,11 @@ function writeCachedAssessment(key, assessment, metadata = {}) {
879901 fs . writeFileSync ( file , JSON . stringify ( cache , null , 2 ) + '\n' ) ;
880902}
881903
882- // This is the actual prompt sent to Codex, Claude, Copilot, or --llm-command.
883- // The command receives it on stdin and must return JSON matching
884- // LLM_OUTPUT_SCHEMA. Keep this prompt explicit about SECURITY.md and doc/
885- // because the model should make a threat-model decision from Node.js sources,
886- // not only from reporter-controlled HackerOne text.
904+ // This is the actual prompt printed for manual LLM use or sent to Codex,
905+ // Claude, Copilot, or --llm- command. Commands receive it on stdin and must
906+ // return JSON matching LLM_OUTPUT_SCHEMA. Keep this prompt explicit about
907+ // SECURITY.md and doc/ because the model should make a threat-model decision
908+ // from Node.js sources, not only from reporter-controlled HackerOne text.
887909function buildLLMPrompt ( report , heuristic , nodeRepo , allReports ) {
888910 const payload = getReportPromptPayload ( report , heuristic , allReports ) ;
889911 return `You are assessing a private HackerOne report for Node.js core.
@@ -987,6 +1009,16 @@ async function assessOneReportWithLLM({
9871009 }
9881010
9891011 const prompt = buildLLMPrompt ( report , result , nodeRepo , reports ) ;
1012+
1013+ if ( provider === MANUAL_LLM_PROVIDER ) {
1014+ printLLMPrompt ( result , prompt , cli ) ;
1015+ result . llm = {
1016+ provider,
1017+ promptPrinted : true
1018+ } ;
1019+ return promptAfterManualLLMPrompt ( argv , cli ) ;
1020+ }
1021+
9901022 const key = cacheKey ( {
9911023 provider,
9921024 model,
@@ -1056,7 +1088,7 @@ async function assessOneReportWithLLM({
10561088
10571089async function assessReportsWithLLM ( reports , results , argv , cli ) {
10581090 const nodeRepo = path . resolve ( argv [ 'node-repo' ] ?? process . cwd ( ) ) ;
1059- const provider = argv . llm ;
1091+ const provider = argv . llm ?? MANUAL_LLM_PROVIDER ;
10601092 const explicitModel = argv [ 'llm-model' ] ;
10611093 const modelInfo = inferLLMModel ( provider , explicitModel ) ;
10621094 const model = modelInfo . model ;
@@ -1086,7 +1118,7 @@ async function assessReportsWithLLM(reports, results, argv, cli) {
10861118 if ( ! shouldContinue ) break ;
10871119 }
10881120 } finally {
1089- commandConfig . cleanup ?. ( ) ;
1121+ commandConfig ? .cleanup ?. ( ) ;
10901122 }
10911123}
10921124
@@ -1105,7 +1137,11 @@ async function promptBeforeLLMAssessment(result, argv, cli, index, total) {
11051137 `${ result . weakness . name || '' } ` . trim ( )
11061138 ] . join ( '\n' ) ) ;
11071139
1108- return cli . prompt ( `Assess H1 report ${ result . id } : ${ result . title } ?` , {
1140+ const action = ( argv . llm ?? MANUAL_LLM_PROVIDER ) === MANUAL_LLM_PROVIDER
1141+ ? 'Print LLM prompt for'
1142+ : 'Assess' ;
1143+
1144+ return cli . prompt ( `${ action } H1 report ${ result . id } : ${ result . title } ?` , {
11091145 defaultAnswer : true
11101146 } ) ;
11111147}
@@ -1123,6 +1159,16 @@ async function promptAfterLLMAssessment(result, argv, cli) {
11231159 } ) ;
11241160}
11251161
1162+ async function promptAfterManualLLMPrompt ( argv , cli ) {
1163+ if ( ! argv [ 'validate-reports-confirm' ] ) {
1164+ return true ;
1165+ }
1166+
1167+ return cli . prompt ( 'Continue to the next report?' , {
1168+ defaultAnswer : true
1169+ } ) ;
1170+ }
1171+
11261172export default class ValidateReports {
11271173 constructor ( cli , argv = { } ) {
11281174 this . cli = cli ;
@@ -1144,9 +1190,7 @@ export default class ValidateReports {
11441190 this . cli . stopSpinner ( `Fetched ${ reports . length } triaged HackerOne reports` ) ;
11451191
11461192 const results = reports . map ( assessReport ) ;
1147- if ( this . argv . llm ) {
1148- await assessReportsWithLLM ( reports , results , this . argv , this . cli ) ;
1149- }
1193+ await assessReportsWithLLM ( reports , results , this . argv , this . cli ) ;
11501194
11511195 const format = this . argv [ 'validate-reports-format' ] ?? 'markdown' ;
11521196 const output = format === 'json'
0 commit comments