Skip to content
This repository was archived by the owner on Sep 11, 2026. It is now read-only.

Commit eb5fed8

Browse files
committed
vulnerability pretty printer
1 parent 587b5ea commit eb5fed8

5 files changed

Lines changed: 524 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# vulnerability logger
2+
3+
This development helper component implements a [reporter](https://github.com/smithy-security/smithy/blob/main/sdk/component/component.go). The purpose of this logger is to print vulnerabilities in a cli with the fields that current smithy cares about instead of printing the whole of OCSF.
4+
5+
To test this component, you can run any example workflow that uses it, e.g. bandit.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"log"
6+
"os"
7+
"time"
8+
9+
"github.com/go-errors/errors"
10+
11+
"github.com/smithy-security/smithy/sdk/component"
12+
13+
"github.com/smithy-security/private-components/components/reporters/vulnerability-logger/internal/reporter"
14+
)
15+
16+
func main() {
17+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
18+
defer cancel()
19+
20+
if err := Main(ctx); err != nil {
21+
log.Fatalf("unexpected error: %v", err)
22+
}
23+
}
24+
25+
func Main(ctx context.Context, opts ...component.RunnerOption) error {
26+
opts = append(opts, component.RunnerWithComponentName("vulnerability-logger"))
27+
cfg, err := reporter.NewConfig(reporter.WithStream(os.Stdout)) // Default to stdout if no stream is provided
28+
if err != nil {
29+
return errors.Errorf("could not create config: %w", err)
30+
}
31+
32+
rpt, err := reporter.NewVulnReporter(cfg)
33+
if err != nil {
34+
return errors.Errorf("could not create reporter: %w", err)
35+
}
36+
if err := component.RunReporter(
37+
ctx,
38+
rpt,
39+
opts...,
40+
); err != nil {
41+
return errors.Errorf("could not run reporter: %w", err)
42+
}
43+
44+
return nil
45+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name: vulnerability-logger
2+
description: "Logs what matters from findings in JSON format"
3+
type: reporter
4+
steps:
5+
- name: vulnerability-logger
6+
image: components/reporters/vulnerability-logger
7+
executable: /bin/app
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
package reporter
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"os"
9+
"time"
10+
11+
"github.com/go-errors/errors"
12+
"github.com/jonboulle/clockwork"
13+
"google.golang.org/protobuf/encoding/protojson"
14+
15+
vf "github.com/smithy-security/smithy/sdk/component/vulnerability-finding"
16+
ocsffindinginfo "github.com/smithy-security/smithy/sdk/gen/ocsf_ext/finding_info/v1"
17+
)
18+
19+
type (
20+
Config struct {
21+
stream io.Writer
22+
clock clockwork.Clock
23+
}
24+
25+
vulnReporter struct {
26+
cfg *Config
27+
}
28+
29+
vCode struct {
30+
Path string
31+
LineRange string
32+
Fix string
33+
Cwe string
34+
}
35+
vPkg struct {
36+
Purl string
37+
FoundLoc string
38+
Remediation string
39+
Cve string
40+
ExploitExists bool
41+
}
42+
vEnrichment struct {
43+
Provider string
44+
Value string
45+
EnrichmentName string
46+
Type string
47+
}
48+
vDatasource struct {
49+
TargetType string
50+
Uri string
51+
RepoURL string
52+
Reference string
53+
Purl string
54+
WebsiteUri string
55+
Tag string
56+
}
57+
smithyFinding struct {
58+
Confidence string
59+
CreatedAt time.Time
60+
Uid string
61+
Enrichments []*vEnrichment
62+
Title string
63+
Datasources []*vDatasource
64+
Description string
65+
Severity string
66+
Code []vCode
67+
Pkg []vPkg
68+
Vendor string
69+
ProductName string
70+
}
71+
)
72+
73+
const errCouldNotGetEnvVarStr = "could not get environment variable value %q: %w"
74+
75+
type ConfigOption func(*Config)
76+
77+
// WithStream sets the output stream for the reporter.
78+
func WithStream(w io.Writer) ConfigOption {
79+
return func(cfg *Config) {
80+
cfg.stream = w
81+
}
82+
}
83+
84+
// WithClock allows customising the underlying clock.
85+
func WithClock(clock clockwork.Clock) ConfigOption {
86+
return func(cfg *Config) {
87+
cfg.clock = clock
88+
}
89+
}
90+
91+
// NewConfig initializes the reporter's Config from env and applies functional options.
92+
func NewConfig(opts ...ConfigOption) (*Config, error) {
93+
var (
94+
cfg = &Config{}
95+
)
96+
97+
for _, opt := range opts {
98+
opt(cfg)
99+
}
100+
if cfg.stream == nil {
101+
cfg.stream = os.Stdout // Default to stdout if no stream is provided
102+
}
103+
return cfg, nil
104+
}
105+
106+
// NewVulnReporter returns a new json logger.
107+
func NewVulnReporter(cfg *Config) (vulnReporter, error) {
108+
if cfg == nil {
109+
return vulnReporter{}, errors.New("config is nil")
110+
}
111+
112+
return vulnReporter{
113+
cfg: cfg,
114+
}, nil
115+
}
116+
117+
// Report logs the findings in json format.
118+
func (j vulnReporter) Report(
119+
ctx context.Context,
120+
findings []*vf.VulnerabilityFinding,
121+
) error {
122+
123+
for _, finding := range findings {
124+
fields, err := j.getWhatWeCareAbout(ctx, finding)
125+
if err != nil {
126+
return errors.Errorf("could not extract what we care about: %w", err)
127+
}
128+
129+
b, err := json.Marshal(&fields)
130+
if err != nil {
131+
return errors.Errorf("could not json marshal cV: %w", err)
132+
}
133+
if _, err := io.WriteString(j.cfg.stream, string(b)); err != nil {
134+
return errors.Errorf("could not write json output: %w", err)
135+
}
136+
}
137+
138+
return nil
139+
}
140+
141+
func (j vulnReporter) getWhatWeCareAbout(_ context.Context, finding *vf.VulnerabilityFinding) (*smithyFinding, error) {
142+
cV := smithyFinding{}
143+
cV.Confidence = finding.Finding.GetConfidenceId().String()
144+
cV.Enrichments = make([]*vEnrichment, 0)
145+
for _, e := range finding.Finding.GetEnrichments() {
146+
cV.Enrichments = append(cV.Enrichments, &vEnrichment{
147+
Provider: e.GetProvider(),
148+
Value: e.GetValue(),
149+
EnrichmentName: e.GetName(),
150+
Type: e.GetType(),
151+
})
152+
}
153+
cV.Title = finding.Finding.GetFindingInfo().GetTitle()
154+
ds := finding.Finding.GetFindingInfo().GetDataSources()
155+
for _, datasource := range ds {
156+
datas := ocsffindinginfo.DataSource{}
157+
if err := protojson.Unmarshal([]byte(datasource), &datas); err != nil {
158+
return nil, errors.Errorf("could not json unmarshal finding: %w", err)
159+
}
160+
cV.Datasources = append(cV.Datasources, &vDatasource{
161+
TargetType: datas.GetTargetType().String(),
162+
Uri: datas.GetUri().GetPath(),
163+
RepoURL: datas.GetSourceCodeMetadata().GetRepositoryUrl(),
164+
Reference: datas.GetSourceCodeMetadata().GetReference(),
165+
Purl: datas.GetOciPackageMetadata().GetPackageUrl(),
166+
Tag: datas.GetOciPackageMetadata().GetTag(),
167+
WebsiteUri: datas.GetWebsiteMetadata().GetUrl(),
168+
})
169+
}
170+
cV.Description = finding.Finding.GetMessage()
171+
cV.Severity = finding.Finding.GetSeverityId().String()
172+
cV.Code = make([]vCode, 0)
173+
cV.Pkg = make([]vPkg, 0)
174+
cV.Uid = finding.Finding.FindingInfo.GetUid()
175+
if finding.Finding.FindingInfo.CreatedTime != nil {
176+
cV.CreatedAt = time.Unix(*finding.Finding.FindingInfo.CreatedTime, 0)
177+
}
178+
179+
for _, v := range finding.Finding.Vulnerabilities {
180+
for _, ac := range v.AffectedCode {
181+
var c vCode
182+
if ac.File != nil {
183+
c.Path = *ac.File.Path
184+
}
185+
if ac != nil && ac.StartLine != nil && ac.EndLine != nil {
186+
c.LineRange = fmt.Sprintf("%d-%d", *ac.StartLine, *ac.EndLine)
187+
}
188+
if ac.Remediation != nil {
189+
c.Fix = ac.Remediation.GetDesc()
190+
}
191+
if v.Cwe != nil {
192+
c.Cwe = v.Cwe.GetUid()
193+
}
194+
cV.Code = append(cV.Code, c)
195+
}
196+
for _, ap := range v.AffectedPackages {
197+
var p vPkg
198+
if ap.Purl != nil {
199+
p.Purl = *ap.Purl
200+
}
201+
if ap.Path != nil {
202+
p.FoundLoc = *ap.Path
203+
p.Remediation = ap.Remediation.GetDesc()
204+
}
205+
if v.Cve != nil {
206+
p.Cve = v.Cve.GetUid()
207+
}
208+
if v.IsExploitAvailable != nil {
209+
p.ExploitExists = *v.IsExploitAvailable
210+
}
211+
cV.Pkg = append(cV.Pkg, p)
212+
}
213+
cV.Vendor = v.GetVendorName()
214+
}
215+
216+
if finding.Finding.GetFindingInfo() != nil {
217+
cV.ProductName = finding.Finding.GetFindingInfo().GetProductUid()
218+
}
219+
220+
return &cV, nil
221+
}

0 commit comments

Comments
 (0)