Skip to content

Commit d7cb201

Browse files
authored
Merge pull request #112 from kusaridev/pxp928-sarif-output
output SARIF format from the inspector results
2 parents fabfbc6 + 9866be4 commit d7cb201

File tree

7 files changed

+968
-12
lines changed

7 files changed

+968
-12
lines changed

api/docstore.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,34 @@ type WorkspaceInspectorResult struct {
3636
}
3737

3838
type Analysis struct {
39-
Proceed bool `docstore:"proceed" json:"proceed"`
40-
Results string `docstore:"results" json:"results"` // markdown content
39+
Proceed bool `docstore:"proceed" json:"proceed"`
40+
Results string `docstore:"results" json:"results"` // markdown content
41+
RawLLMAnalysis *SecurityAnalysis `docstore:"rawLLMAnalysis" json:"rawLLMAnalysis"`
4142
// Add other analysis fields as needed
4243
}
4344

45+
// Structured output types
46+
type CodeMitigationItem struct {
47+
LineNumber int `docstore:"line_number" json:"line_number"`
48+
Path string `docstore:"path" json:"path"`
49+
Content string `docstore:"content" json:"content"`
50+
Code string `docstore:"code" json:"code,omitempty"`
51+
}
52+
53+
type DependencyMitigationItem struct {
54+
Content string `docstore:"content" json:"content"`
55+
}
56+
57+
type SecurityAnalysis struct {
58+
Recommendation string `docstore:"recommendation" json:"recommendation"`
59+
Justification string `docstore:"justification" json:"justification"`
60+
RequiredCodeMitigations []CodeMitigationItem `docstore:"code_mitigations" json:"code_mitigations,omitempty"`
61+
RequiredDependencyMitigations []DependencyMitigationItem `docstore:"dependency_mitigations" json:"dependency_mitigations,omitempty"`
62+
ShouldProceed bool `docstore:"should_proceed" json:"should_proceed"`
63+
FailedAnalysis bool `docstore:"failed_analysis" json:"failed_analysis"`
64+
HealthScore int `docstore:"health_score" json:"health_score"` // 0-5 scale for full repo scans
65+
}
66+
4467
type Meta struct {
4568
Type string `docstore:"type" json:"type"` // pr, cli
4669
PR int `docstore:"pr" json:"pr"`

kusari/cmd/repo_scan.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,43 @@
44
package cmd
55

66
import (
7+
"fmt"
8+
79
"github.com/kusaridev/kusari-cli/pkg/repo"
810
"github.com/spf13/cobra"
911
"github.com/spf13/viper"
1012
)
1113

1214
var (
13-
platformUrl string
14-
wait bool
15+
platformUrl string
16+
wait bool
17+
outputFormat string
1518
)
1619

1720
func init() {
1821
scancmd.Flags().StringVarP(&platformUrl, "platform-url", "", "https://platform.api.us.kusari.cloud/", "platform url")
1922
scancmd.Flags().BoolVarP(&wait, "wait", "w", true, "wait for results")
23+
scancmd.Flags().StringVarP(&outputFormat, "output-format", "", "markdown", "output format (markdown or sarif)")
2024

2125
// Bind flags to viper
2226
mustBindPFlag("platform-url", scancmd.Flags().Lookup("platform-url"))
2327
mustBindPFlag("wait", scancmd.Flags().Lookup("wait"))
28+
mustBindPFlag("output-format", scancmd.Flags().Lookup("output-format"))
2429
}
2530

2631
func scan() *cobra.Command {
2732
scancmd.RunE = func(cmd *cobra.Command, args []string) error {
2833
cmd.SilenceUsage = true
2934

35+
// Validate output format
36+
if outputFormat != "markdown" && outputFormat != "sarif" {
37+
return fmt.Errorf("invalid output format: %s (must be 'markdown' or 'sarif')", outputFormat)
38+
}
39+
3040
dir := args[0]
3141
ref := args[1]
3242

33-
return repo.Scan(dir, ref, platformUrl, consoleUrl, verbose, wait)
43+
return repo.Scan(dir, ref, platformUrl, consoleUrl, verbose, wait, outputFormat)
3444
}
3545

3646
return scancmd
@@ -47,5 +57,6 @@ var scancmd = &cobra.Command{
4757
// Update from viper (this gets env vars + config + flags)
4858
platformUrl = viper.GetString("platform-url")
4959
wait = viper.GetBool("wait")
60+
outputFormat = viper.GetString("output-format")
5061
},
5162
}

pkg/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type Config struct {
2323
RefreshToken string `json:"refresh_token,omitempty"`
2424
TokenExpiry time.Time `json:"token_expiry,omitempty"`
2525
Verbose bool `json:"verbose,omitempty"`
26+
OutputFormat string `json:"output_format,omitempty"`
2627
}
2728

2829
// Manager handles configuration persistence

pkg/repo/scanner.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/charmbracelet/glamour"
2222
"github.com/kusaridev/kusari-cli/api"
2323
"github.com/kusaridev/kusari-cli/pkg/auth"
24+
"github.com/kusaridev/kusari-cli/pkg/sarif"
2425
urlBuilder "github.com/kusaridev/kusari-cli/pkg/url"
2526
)
2627

@@ -39,12 +40,13 @@ var (
3940
workingDir string
4041
)
4142

42-
func Scan(dir string, rev string, platformUrl string, consoleUrl string, verbose bool, wait bool) error {
43-
return scan(dir, rev, platformUrl, consoleUrl, verbose, wait, false, nil)
43+
func Scan(dir string, rev string, platformUrl string, consoleUrl string, verbose bool, wait bool, outputFormat string) error {
44+
return scan(dir, rev, platformUrl, consoleUrl, verbose, wait, false, outputFormat, nil)
4445
}
4546

4647
func RiskCheck(dir string, platformUrl string, consoleUrl string, verbose bool, wait bool) error {
47-
return scan(dir, "", platformUrl, consoleUrl, verbose, wait, true, nil)
48+
// default to outputformat "markdown" for now for risk check as it will link to console
49+
return scan(dir, "", platformUrl, consoleUrl, verbose, wait, true, "markdown", nil)
4850
}
4951

5052
// scanMock facilitates use of mock values for testing
@@ -55,13 +57,14 @@ type scanMock struct {
5557
token string
5658
}
5759

58-
func scan(dir string, rev string, platformUrl string, consoleUrl string, verbose bool, wait bool, full bool,
60+
func scan(dir string, rev string, platformUrl string, consoleUrl string, verbose bool, wait bool, full bool, outputFormat string,
5961
mock *scanMock) error {
6062
if verbose {
6163
fmt.Fprintf(os.Stderr, " dir: %s\n", dir)
6264
fmt.Fprintf(os.Stderr, " rev: %s\n", rev)
6365
fmt.Fprintf(os.Stderr, " platformUrl: %s\n", platformUrl)
6466
fmt.Fprintf(os.Stderr, " consoleUrl: %s\n", consoleUrl)
67+
fmt.Fprintf(os.Stderr, " outputFormat: %s\n", outputFormat)
6568
}
6669

6770
// Check to see if the directory has a .git directory. If it does not, it is not the root of
@@ -190,7 +193,7 @@ func scan(dir string, rev string, platformUrl string, consoleUrl string, verbose
190193

191194
// Wait for results if the user wants, or exit immediately
192195
if wait {
193-
return queryForResult(platformUrl, epoch, accessToken, consoleFullUrl, workspace)
196+
return queryForResult(platformUrl, epoch, accessToken, consoleFullUrl, workspace, outputFormat)
194197
}
195198
return nil
196199
}
@@ -199,7 +202,7 @@ func cleanupWorkingDirectory(tempDir string) {
199202
_ = os.RemoveAll(tempDir)
200203
}
201204

202-
func queryForResult(platformUrl string, epoch *string, accessToken string, consoleFullUrl *string, workspace string) error {
205+
func queryForResult(platformUrl string, epoch *string, accessToken string, consoleFullUrl *string, workspace, outputFormat string) error {
203206
maxAttempts := 750
204207
attempt := 0
205208
sleepDuration := time.Second
@@ -262,6 +265,19 @@ func queryForResult(platformUrl string, epoch *string, accessToken string, conso
262265
s.FinalMSG = "✓ Analysis complete!\n"
263266
s.Stop()
264267

268+
// Check output format
269+
if outputFormat == "sarif" {
270+
// Output sarif format
271+
sarifOutput, err := sarif.ConvertToSARIF(results[0].Analysis.RawLLMAnalysis)
272+
if err != nil {
273+
return fmt.Errorf("failed to convert to SARIF: %w", err)
274+
}
275+
276+
fmt.Fprintf(os.Stderr, "You can also view your results here: %s\n", *consoleFullUrl)
277+
fmt.Print(sarifOutput) // stdout
278+
return nil
279+
}
280+
265281
// Clean and format results for stdout
266282
rawContent := results[0].Analysis.Results
267283
cleanedContent := removeImageLines(rawContent)

pkg/repo/scanner_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func TestScan_ArchiveFormat(t *testing.T) {
6565
}
6666

6767
// Run the scan with dependencies injection
68-
err := scan(testDir, "HEAD", "https://platform.example.com", "https://console.example.com", false, false, full, mock)
68+
err := scan(testDir, "HEAD", "https://platform.example.com", "https://console.example.com", false, false, full, "markdown", mock)
6969
require.NoError(t, err)
7070

7171
// Verify upload was called

0 commit comments

Comments
 (0)