diff --git a/cmd/govulncheck/main_test.go b/cmd/govulncheck/main_test.go index 9c3b3018..418b3ba8 100644 --- a/cmd/govulncheck/main_test.go +++ b/cmd/govulncheck/main_test.go @@ -304,7 +304,7 @@ func runTestSuite(t *testing.T, dir string, govulndb string, update bool) { func isJSONMode(args []string) bool { for _, arg := range args { - if arg == "-json" { + if arg == "json" { return true } } diff --git a/cmd/govulncheck/testdata/testfiles/binary-call/binary_call_json.ct b/cmd/govulncheck/testdata/testfiles/binary-call/binary_call_json.ct index 837d8d48..3523b58a 100644 --- a/cmd/govulncheck/testdata/testfiles/binary-call/binary_call_json.ct +++ b/cmd/govulncheck/testdata/testfiles/binary-call/binary_call_json.ct @@ -1,6 +1,6 @@ ##### # Test basic binary scanning with json output -$ govulncheck -json -mode=binary ${vuln_binary} +$ govulncheck -format json -mode binary ${vuln_binary} { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/binary-call/binary_vendored_json.ct b/cmd/govulncheck/testdata/testfiles/binary-call/binary_vendored_json.ct index 23c70f0e..233939a0 100644 --- a/cmd/govulncheck/testdata/testfiles/binary-call/binary_vendored_json.ct +++ b/cmd/govulncheck/testdata/testfiles/binary-call/binary_vendored_json.ct @@ -1,6 +1,6 @@ ##### # Test basic binary scanning with json output -$ govulncheck -json -mode=binary ${vendored_binary} +$ govulncheck -format json -mode binary ${vendored_binary} { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/binary-module/binary_module_json.ct b/cmd/govulncheck/testdata/testfiles/binary-module/binary_module_json.ct index 7ca41912..ab425000 100644 --- a/cmd/govulncheck/testdata/testfiles/binary-module/binary_module_json.ct +++ b/cmd/govulncheck/testdata/testfiles/binary-module/binary_module_json.ct @@ -1,6 +1,6 @@ ##### # Test binary scanning at the module level with json output -$ govulncheck -json -mode=binary -scan=module ${vuln_binary} +$ govulncheck -format json -mode binary -scan module ${vuln_binary} { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/binary-package/binary_package_json.ct b/cmd/govulncheck/testdata/testfiles/binary-package/binary_package_json.ct index 66f23bd7..9e125956 100644 --- a/cmd/govulncheck/testdata/testfiles/binary-package/binary_package_json.ct +++ b/cmd/govulncheck/testdata/testfiles/binary-package/binary_package_json.ct @@ -1,6 +1,6 @@ ##### # Test binary scanning at the package level with json output -$ govulncheck -json -mode=binary -scan=package ${vuln_binary} +$ govulncheck -format json -mode binary -scan package ${vuln_binary} { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/failures/query_fail.ct b/cmd/govulncheck/testdata/testfiles/failures/query_fail.ct index cac82425..b643b415 100644 --- a/cmd/govulncheck/testdata/testfiles/failures/query_fail.ct +++ b/cmd/govulncheck/testdata/testfiles/failures/query_fail.ct @@ -1,4 +1,4 @@ ##### # Test of query mode with invalid input. -$ govulncheck -mode=query -json example.com/module@ --> FAIL 2 +$ govulncheck -mode=query -format json example.com/module@ --> FAIL 2 invalid query example.com/module@: must be of the form module@version diff --git a/cmd/govulncheck/testdata/testfiles/failures/usage_fail.ct b/cmd/govulncheck/testdata/testfiles/failures/usage_fail.ct index 00791660..918eeada 100644 --- a/cmd/govulncheck/testdata/testfiles/failures/usage_fail.ct +++ b/cmd/govulncheck/testdata/testfiles/failures/usage_fail.ct @@ -12,3 +12,18 @@ the -show flag is not supported for JSON output # Test of invalid input to -scan $ govulncheck -scan=invalid ./... --> FAIL 2 "invalid" is not a valid scan level + +##### +# Test of invalid -format value +$ govulncheck -format invalid ./... --> FAIL 2 +"invalid" is not a valid output format + +##### +# Test of trying to run -json with '-format text' flag +$ govulncheck -C ${moddir}/vuln -json -format text . --> FAIL 2 +the -json flag cannot be used with -format flag + +##### +# Test of explicit format use together with -json flag +$ govulncheck -C ${moddir}/vuln -format json -json . --> FAIL 2 +the -json flag cannot be used with -format flag diff --git a/cmd/govulncheck/testdata/testfiles/query/query_json.ct b/cmd/govulncheck/testdata/testfiles/query/query_json.ct index 5a27e865..857fda28 100644 --- a/cmd/govulncheck/testdata/testfiles/query/query_json.ct +++ b/cmd/govulncheck/testdata/testfiles/query/query_json.ct @@ -1,6 +1,6 @@ ##### # Test of query mode for a third party module. -$ govulncheck -mode=query -json github.com/tidwall/gjson@v1.6.5 +$ govulncheck -mode=query -format json github.com/tidwall/gjson@v1.6.5 { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/query/query_multi_json.ct b/cmd/govulncheck/testdata/testfiles/query/query_multi_json.ct index c0d4c8f7..8af0474b 100644 --- a/cmd/govulncheck/testdata/testfiles/query/query_multi_json.ct +++ b/cmd/govulncheck/testdata/testfiles/query/query_multi_json.ct @@ -1,6 +1,6 @@ ##### # Test of query mode with multiple inputs. -$ govulncheck -mode=query -json stdlib@go1.17 github.com/tidwall/gjson@v1.6.5 +$ govulncheck -mode=query -format json stdlib@go1.17 github.com/tidwall/gjson@v1.6.5 { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/source-call/source_call_json.ct b/cmd/govulncheck/testdata/testfiles/source-call/source_call_json.ct index 1e83eebb..b24e7513 100644 --- a/cmd/govulncheck/testdata/testfiles/source-call/source_call_json.ct +++ b/cmd/govulncheck/testdata/testfiles/source-call/source_call_json.ct @@ -1,6 +1,5 @@ ##### -# -$ govulncheck -C ${moddir}/vuln -json ./... +$ govulncheck -C ${moddir}/vuln -format json ./... { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/source-call/source_informational_text.ct b/cmd/govulncheck/testdata/testfiles/source-call/source_informational_text.ct index bab24106..2e1eeb43 100644 --- a/cmd/govulncheck/testdata/testfiles/source-call/source_informational_text.ct +++ b/cmd/govulncheck/testdata/testfiles/source-call/source_informational_text.ct @@ -1,5 +1,5 @@ ##### -# Test souce mode with no callstacks +# Test source mode with no callstacks $ govulncheck -C ${moddir}/informational -show=traces . Scanning your code and P packages across M dependent modules for known vulnerabilities... diff --git a/cmd/govulncheck/testdata/testfiles/source-call/source_multientry_json.ct b/cmd/govulncheck/testdata/testfiles/source-call/source_multientry_json.ct index b618b71f..599b4211 100644 --- a/cmd/govulncheck/testdata/testfiles/source-call/source_multientry_json.ct +++ b/cmd/govulncheck/testdata/testfiles/source-call/source_multientry_json.ct @@ -1,6 +1,6 @@ ##### # Test for multiple call stacks in source mode -$ govulncheck -json -C ${moddir}/multientry . +$ govulncheck -format json -C ${moddir}/multientry . { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/source-call/source_replace_json.ct b/cmd/govulncheck/testdata/testfiles/source-call/source_replace_json.ct index 9175288a..95705d1c 100644 --- a/cmd/govulncheck/testdata/testfiles/source-call/source_replace_json.ct +++ b/cmd/govulncheck/testdata/testfiles/source-call/source_replace_json.ct @@ -1,6 +1,6 @@ ##### # Test of source mode json on a module with a replace directive. -$ govulncheck -C ${moddir}/replace -json ./... +$ govulncheck -C ${moddir}/replace -format json ./... { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/source-call/source_vendored_json.ct b/cmd/govulncheck/testdata/testfiles/source-call/source_vendored_json.ct index 39b75910..f00922ae 100644 --- a/cmd/govulncheck/testdata/testfiles/source-call/source_vendored_json.ct +++ b/cmd/govulncheck/testdata/testfiles/source-call/source_vendored_json.ct @@ -1,6 +1,6 @@ ##### # -$ govulncheck -C ${moddir}/vendored -json ./... +$ govulncheck -C ${moddir}/vendored -format json ./... { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/source-module/source_module_json.ct b/cmd/govulncheck/testdata/testfiles/source-module/source_module_json.ct index 0a859967..3b728b53 100644 --- a/cmd/govulncheck/testdata/testfiles/source-module/source_module_json.ct +++ b/cmd/govulncheck/testdata/testfiles/source-module/source_module_json.ct @@ -1,6 +1,6 @@ ##### # Test that findings with callstacks or packages are not emitted in module mode -$ govulncheck -json -scan module -C ${moddir}/multientry +$ govulncheck -format json -scan module -C ${moddir}/multientry { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/source-package/source_package_json.ct b/cmd/govulncheck/testdata/testfiles/source-package/source_package_json.ct index 56679fe5..738eba98 100644 --- a/cmd/govulncheck/testdata/testfiles/source-package/source_package_json.ct +++ b/cmd/govulncheck/testdata/testfiles/source-package/source_package_json.ct @@ -1,6 +1,6 @@ ##### # Test that findings with callstacks are not emitted in package mode -$ govulncheck -json -scan package -C ${moddir}/multientry . +$ govulncheck -format json -scan package -C ${moddir}/multientry . { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/stdlib/query_stdlib_json.ct b/cmd/govulncheck/testdata/testfiles/stdlib/query_stdlib_json.ct index 8dd9b276..5ea84b86 100644 --- a/cmd/govulncheck/testdata/testfiles/stdlib/query_stdlib_json.ct +++ b/cmd/govulncheck/testdata/testfiles/stdlib/query_stdlib_json.ct @@ -1,6 +1,6 @@ ##### # Test of query mode with the standard library. -$ govulncheck -mode=query -json stdlib@go1.17 +$ govulncheck -mode=query -format json stdlib@go1.17 { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/stdlib/query_vstdlib_json.ct b/cmd/govulncheck/testdata/testfiles/stdlib/query_vstdlib_json.ct index bbd1e48d..4f3aa129 100644 --- a/cmd/govulncheck/testdata/testfiles/stdlib/query_vstdlib_json.ct +++ b/cmd/govulncheck/testdata/testfiles/stdlib/query_vstdlib_json.ct @@ -1,6 +1,6 @@ ##### # Test of query mode with the standard library (with a v prefix on the version). -$ govulncheck -mode=query -json stdlib@v1.17.0 +$ govulncheck -mode=query -format json stdlib@v1.17.0 { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/stdlib/source_stdlib_json.ct b/cmd/govulncheck/testdata/testfiles/stdlib/source_stdlib_json.ct index 2e5745d6..8586cc79 100644 --- a/cmd/govulncheck/testdata/testfiles/stdlib/source_stdlib_json.ct +++ b/cmd/govulncheck/testdata/testfiles/stdlib/source_stdlib_json.ct @@ -1,6 +1,6 @@ ##### # Test finding stdlib vulnerability in source mode with json output -$ govulncheck -C ${moddir}/stdlib -json . +$ govulncheck -C ${moddir}/stdlib -format json . { "config": { "protocol_version": "v1.0.0", diff --git a/cmd/govulncheck/testdata/testfiles/usage/format.ct b/cmd/govulncheck/testdata/testfiles/usage/format.ct new file mode 100644 index 00000000..2415087a --- /dev/null +++ b/cmd/govulncheck/testdata/testfiles/usage/format.ct @@ -0,0 +1,28 @@ +##### +# Test of explicit text format +$ govulncheck -C ${moddir}/informational -format text . +Scanning your code and P packages across M dependent modules for known vulnerabilities... + +=== Symbol Results === + +No vulnerabilities found. + +Your code is affected by 0 vulnerabilities. +This scan also found 1 vulnerability in packages you import and 1 vulnerability +in modules you require, but your code doesn't appear to call these +vulnerabilities. +Use '-show verbose' for more details. + +# Test of explicit json format +$ govulncheck -C ${moddir}/informational -format json +{ + "config": { + "protocol_version": "v1.0.0", + "scanner_name": "govulncheck", + "scanner_version": "v0.0.0-00000000000-20000101010101", + "db": "testdata/vulndb-v1", + "db_last_modified": "2023-04-03T15:57:51Z", + "go_version": "go1.18", + "scan_level": "symbol" + } +} diff --git a/cmd/govulncheck/testdata/testfiles/usage/usage.ct b/cmd/govulncheck/testdata/testfiles/usage/usage.ct index 030c31b6..968c4393 100644 --- a/cmd/govulncheck/testdata/testfiles/usage/usage.ct +++ b/cmd/govulncheck/testdata/testfiles/usage/usage.ct @@ -12,8 +12,11 @@ Usage: change to dir before running govulncheck -db url vulnerability database url (default "https://vuln.go.dev") + -format string + specify format output + The supported values are 'text' and 'json' (default 'text') -json - output JSON + output JSON (Go compatible legacy flag, see format flag) -mode string supports source or binary (default "source") -scan string diff --git a/internal/scan/flags.go b/internal/scan/flags.go index 3d361d5c..afed5466 100644 --- a/internal/scan/flags.go +++ b/internal/scan/flags.go @@ -20,11 +20,11 @@ type config struct { patterns []string mode string db string - json bool dir string - tags []string + tags buildutil.TagsFlag test bool - show []string + show showFlag + format string env []string } @@ -34,21 +34,25 @@ const ( modeConvert = "convert" // only intended for use by gopls modeQuery = "query" // only intended for use by gopls modeExtract = "extract" // currently, only binary extraction is supported + + formatUnset = "" + formatJSON = "json" + formatText = "text" ) func parseFlags(cfg *config, stderr io.Writer, args []string) error { - var tagsFlag buildutil.TagsFlag - var showFlag showFlag var version bool + var json bool flags := flag.NewFlagSet("", flag.ContinueOnError) flags.SetOutput(stderr) - flags.BoolVar(&cfg.json, "json", false, "output JSON") + flags.BoolVar(&json, "json", false, "output JSON (Go compatible legacy flag, see format flag)") flags.BoolVar(&cfg.test, "test", false, "analyze test files (only valid for source mode, default false)") flags.StringVar(&cfg.dir, "C", "", "change to `dir` before running govulncheck") flags.StringVar(&cfg.db, "db", "https://vuln.go.dev", "vulnerability database `url`") flags.StringVar(&cfg.mode, "mode", modeSource, "supports source or binary") - flags.Var(&tagsFlag, "tags", "comma-separated `list` of build tags") - flags.Var(&showFlag, "show", "enable display of additional information specified by the comma separated `list`\nThe supported values are 'traces','color', 'version', and 'verbose'") + flags.Var(&cfg.tags, "tags", "comma-separated `list` of build tags") + flags.Var(&cfg.show, "show", "enable display of additional information specified by the comma separated `list`\nThe supported values are 'traces','color', 'version', and 'verbose'") + flags.StringVar(&cfg.format, "format", formatUnset, "specify format output\nThe supported values are 'text' and 'json' (default 'text')") flags.BoolVar(&version, "version", false, "print the version information") scanLevel := flags.String("scan", "symbol", "set the scanning level desired, one of module, package or symbol") flags.Usage = func() { @@ -70,13 +74,11 @@ Usage: return err } cfg.patterns = flags.Args() - cfg.tags = tagsFlag - cfg.show = showFlag if version { cfg.show = append(cfg.show, "version") } cfg.ScanLevel = govulncheck.ScanLevel(*scanLevel) - if err := validateConfig(cfg); err != nil { + if err := validateConfig(cfg, json); err != nil { fmt.Fprintln(flags.Output(), err) return errUsage } @@ -97,13 +99,36 @@ var supportedLevels = map[string]bool{ govulncheck.ScanLevelSymbol: true, } -func validateConfig(cfg *config) error { +var supportedFormats = map[string]bool{ + formatJSON: true, + formatText: true, +} + +func validateConfig(cfg *config, json bool) error { + if json { + if len(cfg.show) > 0 { + return fmt.Errorf("the -show flag is not supported for JSON output") + } + if cfg.format != formatUnset { + return fmt.Errorf("the -json flag cannot be used with -format flag") + } + cfg.format = formatJSON + } else { + if cfg.format == formatUnset { + cfg.format = formatText + } + } + if _, ok := supportedModes[cfg.mode]; !ok { return fmt.Errorf("%q is not a valid mode", cfg.mode) } if _, ok := supportedLevels[string(cfg.ScanLevel)]; !ok { return fmt.Errorf("%q is not a valid scan level", cfg.ScanLevel) } + if _, ok := supportedFormats[cfg.format]; !ok { + return fmt.Errorf("%q is not a valid output format", cfg.format) + } + switch cfg.mode { case modeSource: if len(cfg.patterns) == 1 && isFile(cfg.patterns[0]) { @@ -135,8 +160,8 @@ func validateConfig(cfg *config) error { if len(cfg.patterns) != 1 { return fmt.Errorf("only 1 binary can be extracted at a time") } - if cfg.json { - return fmt.Errorf("the -json flag must be off in extract mode") + if cfg.format == formatJSON { + return fmt.Errorf("the json format must be off in extract mode") } if !isFile(cfg.patterns[0]) { return fmt.Errorf("%q is not a file (source extraction is not supported)", cfg.patterns[0]) @@ -161,8 +186,8 @@ func validateConfig(cfg *config) error { if len(cfg.tags) > 0 { return fmt.Errorf("the -tags flag is not supported in query mode") } - if !cfg.json { - return fmt.Errorf("the -json flag must be set in query mode") + if cfg.format != formatJSON { + return fmt.Errorf("the json format must be set in query mode") } for _, pattern := range cfg.patterns { // Parse the input here so that we can catch errors before @@ -172,9 +197,6 @@ func validateConfig(cfg *config) error { } } } - if cfg.json && len(cfg.show) > 0 { - return fmt.Errorf("the -show flag is not supported for JSON output") - } return nil } diff --git a/internal/scan/run.go b/internal/scan/run.go index a8fdbb10..30529dce 100644 --- a/internal/scan/run.go +++ b/internal/scan/run.go @@ -35,8 +35,8 @@ func RunGovulncheck(ctx context.Context, env []string, r io.Reader, stdout io.Wr prepareConfig(ctx, cfg, client) var handler govulncheck.Handler - switch { - case cfg.json: + switch cfg.format { + case formatJSON: handler = govulncheck.NewJSONHandler(stdout) default: th := NewTextHandler(stdout)