@@ -26,15 +26,22 @@ type overDefenseCase struct {
2626}
2727
2828type scanOutput struct {
29- Score int `json:"score"`
30- Level string `json:"level"`
31- Blocked bool `json:"blocked"`
32- Reason string `json:"reason"`
33- Patterns []string `json:"patterns"`
34- Categories []string `json:"categories"`
35- BanListMatches []string `json:"ban_list_matches"`
36- OverDefenseRisk float64 `json:"over_defense_risk"`
37- Intent idpi.Intent `json:"intent,omitempty"`
29+ Score int `json:"score"`
30+ Level string `json:"level"`
31+ Blocked bool `json:"blocked"`
32+ Reason string `json:"reason"`
33+ Patterns []string `json:"patterns"`
34+ Categories []string `json:"categories"`
35+ BanListMatches []string `json:"ban_list_matches"`
36+ OverDefenseRisk float64 `json:"over_defense_risk"`
37+ IsOutputScan bool `json:"is_output_scan"`
38+ PIIFound bool `json:"pii_found"`
39+ PIITypes []string `json:"pii_types"`
40+ RedactedText string `json:"redacted_text"`
41+ RelevanceScore float64 `json:"relevance_score"`
42+ CodeDetected bool `json:"code_detected"`
43+ HarmfulCodePatterns []string `json:"harmful_code_patterns"`
44+ Intent idpi.Intent `json:"intent,omitempty"`
3845}
3946
4047var overDefenseDataset = []overDefenseCase {
@@ -84,6 +91,11 @@ func main() {
8491 log .Printf ("scan failed: %v" , err )
8592 os .Exit (2 )
8693 }
94+ case "scan-output" :
95+ if err := runScanOutput (os .Args [2 :]); err != nil {
96+ log .Printf ("scan-output failed: %v" , err )
97+ os .Exit (2 )
98+ }
8799 case "test-overdefense" :
88100 if ! runTestOverDefense (os .Stdout ) {
89101 os .Exit (1 )
@@ -310,6 +322,10 @@ func runScan(args []string) error {
310322 banCompetitors := fs .String ("ban-competitors" , "" , "comma-separated list of competitor names to ban" )
311323 customRegex := fs .String ("custom-regex" , "" , "comma-separated list of custom regex patterns to ban" )
312324 configFile := fs .String ("config-file" , "" , "path to JSON or YAML ban-list config file" )
325+ asOutput := fs .Bool ("as-output" , false , "run output scanning pipeline on input text" )
326+ originalPrompt := fs .String ("original-prompt" , "" , "original prompt text for output relevance comparison" )
327+ allowOutputCode := fs .Bool ("allow-output-code" , false , "allow code in output and only flag harmful code" )
328+ banOutputCode := fs .Bool ("ban-output-code" , false , "treat any code in output as suspicious" )
313329
314330 if err := fs .Parse (args ); err != nil {
315331 printUsage (os .Stderr )
@@ -322,6 +338,10 @@ func runScan(args []string) error {
322338 return err
323339 }
324340
341+ if * asOutput && (* url != "" || * domains != "" ) {
342+ log .Printf ("warning: --as-output ignores --url and --domains flags; use scan-output subcommand for output scanning" )
343+ }
344+
325345 shieldConfig := idpi.Config {
326346 Mode : idpi .ParseMode (* mode ),
327347 AllowedDomains : parseDomains (* domains ),
@@ -338,6 +358,8 @@ func runScan(args []string) error {
338358 BanCompetitors : parseCSVList (* banCompetitors ),
339359 CustomRegex : parseCSVList (* customRegex ),
340360 ConfigFile : strings .TrimSpace (* configFile ),
361+ AllowOutputCode : * allowOutputCode ,
362+ BanOutputCode : * banOutputCode ,
341363 }
342364 if err := applyProfileDefaults (* profile , & shieldConfig ); err != nil {
343365 return err
@@ -355,16 +377,88 @@ func runScan(args []string) error {
355377 }
356378
357379 result := shield .Assess (text , * url )
380+ if * asOutput {
381+ result = shield .AssessOutput (text , * originalPrompt )
382+ }
383+ output := scanOutput {
384+ Score : result .Score ,
385+ Level : result .Level ,
386+ Blocked : result .Blocked ,
387+ Reason : result .Reason ,
388+ Patterns : result .Patterns ,
389+ Categories : result .Categories ,
390+ BanListMatches : result .BanListMatches ,
391+ OverDefenseRisk : result .OverDefenseRisk ,
392+ IsOutputScan : result .IsOutputScan ,
393+ PIIFound : result .PIIFound ,
394+ PIITypes : result .PIITypes ,
395+ RedactedText : result .RedactedText ,
396+ RelevanceScore : result .RelevanceScore ,
397+ CodeDetected : result .CodeDetected ,
398+ HarmfulCodePatterns : result .HarmfulCodePatterns ,
399+ Intent : result .Intent ,
400+ }
401+
402+ enc := json .NewEncoder (os .Stdout )
403+ enc .SetIndent ("" , " " )
404+ if err := enc .Encode (output ); err != nil {
405+ return err
406+ }
407+
408+ if result .Blocked {
409+ os .Exit (1 )
410+ }
411+
412+ return nil
413+ }
414+
415+ func runScanOutput (args []string ) error {
416+ fs := flag .NewFlagSet ("scan-output" , flag .ContinueOnError )
417+ fs .SetOutput (io .Discard )
418+
419+ strict := fs .Bool ("strict" , false , "enable strict mode (block >= 40)" )
420+ originalPrompt := fs .String ("original-prompt" , "" , "original prompt text for output relevance comparison" )
421+ allowOutputCode := fs .Bool ("allow-output-code" , false , "allow code in output and only flag harmful code" )
422+ banOutputCode := fs .Bool ("ban-output-code" , false , "treat any code in output as suspicious" )
423+
424+ if err := fs .Parse (args ); err != nil {
425+ printUsage (os .Stderr )
426+ return err
427+ }
428+
429+ text , err := readInput (fs .Args ())
430+ if err != nil {
431+ return err
432+ }
433+
434+ shield , err := idpi .New (idpi.Config {
435+ Mode : idpi .ModeBalanced ,
436+ StrictMode : * strict ,
437+ AllowOutputCode : * allowOutputCode ,
438+ BanOutputCode : * banOutputCode ,
439+ })
440+ if err != nil {
441+ return err
442+ }
443+
444+ result := shield .AssessOutput (text , * originalPrompt )
358445 output := scanOutput {
359- Score : result .Score ,
360- Level : result .Level ,
361- Blocked : result .Blocked ,
362- Reason : result .Reason ,
363- Patterns : result .Patterns ,
364- Categories : result .Categories ,
365- BanListMatches : result .BanListMatches ,
366- OverDefenseRisk : result .OverDefenseRisk ,
367- Intent : result .Intent ,
446+ Score : result .Score ,
447+ Level : result .Level ,
448+ Blocked : result .Blocked ,
449+ Reason : result .Reason ,
450+ Patterns : result .Patterns ,
451+ Categories : result .Categories ,
452+ BanListMatches : result .BanListMatches ,
453+ OverDefenseRisk : result .OverDefenseRisk ,
454+ IsOutputScan : result .IsOutputScan ,
455+ PIIFound : result .PIIFound ,
456+ PIITypes : result .PIITypes ,
457+ RedactedText : result .RedactedText ,
458+ RelevanceScore : result .RelevanceScore ,
459+ CodeDetected : result .CodeDetected ,
460+ HarmfulCodePatterns : result .HarmfulCodePatterns ,
461+ Intent : result .Intent ,
368462 }
369463
370464 enc := json .NewEncoder (os .Stdout )
@@ -445,11 +539,13 @@ func printUsage(w io.Writer) {
445539 fmt .Fprintln (w )
446540 fmt .Fprintln (w , "Usage:" )
447541 fmt .Fprintln (w , " idpishield scan [file|-] --mode balanced --domains example.com,google.com" )
542+ fmt .Fprintln (w , " idpishield scan-output [file|-] --original-prompt \" user prompt\" " )
448543 fmt .Fprintln (w , " idpishield test-overdefense" )
449544 fmt .Fprintln (w , " idpishield mcp serve [--transport stdio|http] [flags]" )
450545 fmt .Fprintln (w )
451546 fmt .Fprintln (w , "Commands:" )
452547 fmt .Fprintln (w , " scan Assess input from file path or stdin and emit JSON risk result" )
548+ fmt .Fprintln (w , " scan-output Assess LLM response text and emit output-scan JSON risk result" )
453549 fmt .Fprintln (w , " test-overdefense Run built-in benign sentence suite to estimate over-defense rate" )
454550 fmt .Fprintln (w , " mcp Run MCP server (stdio by default) exposing tool: idpi_assess" )
455551 fmt .Fprintln (w )
@@ -471,6 +567,16 @@ func printUsage(w io.Writer) {
471567 fmt .Fprintln (w , " --ban-competitors comma-separated list of competitor names to ban" )
472568 fmt .Fprintln (w , " --custom-regex comma-separated list of regex patterns to ban" )
473569 fmt .Fprintln (w , " --config-file path to JSON/YAML ban-list configuration" )
570+ fmt .Fprintln (w , " --as-output run output scanning pipeline on input text" )
571+ fmt .Fprintln (w , " --original-prompt original prompt text used for output relevance comparison" )
572+ fmt .Fprintln (w , " --allow-output-code allow code in output and only flag harmful code" )
573+ fmt .Fprintln (w , " --ban-output-code treat any code in output as suspicious" )
574+ fmt .Fprintln (w )
575+ fmt .Fprintln (w , "scan-output flags:" )
576+ fmt .Fprintln (w , " --strict block at score >= 40 instead of >= 60" )
577+ fmt .Fprintln (w , " --original-prompt original prompt text used for output relevance comparison" )
578+ fmt .Fprintln (w , " --allow-output-code allow code in output and only flag harmful code" )
579+ fmt .Fprintln (w , " --ban-output-code treat any code in output as suspicious" )
474580}
475581
476582//nolint:errcheck // usage output — errors are not actionable
0 commit comments