diff --git a/docs/docs/coverage/language/quantum.md b/docs/docs/coverage/language/quantum.md new file mode 100644 index 000000000000..dae4cb7dc1d6 --- /dev/null +++ b/docs/docs/coverage/language/quantum.md @@ -0,0 +1,96 @@ +# Quantum + +Trivy supports DataRobot Quantum packages - native system libraries packaged in Python wheel format. + +## Overview + +DataRobot uses an internal tool called "quantum-builders" that builds relocatable versions of system software (such as curl, krb5, openssl, libpng, etc.) and packages them into Python wheel format. These wheels are then installed into virtual environments using `pip`. + +Trivy detects these quantum packages and maps them to their upstream equivalents for CVE detection. + +## Support + +The following scanners are supported. + +| Package manager | SBOM | Vulnerability | License | +|-----------------|:----:|:-------------:|:-------:| +| Quantum | ✓ | ✓ | ✓ | + +The following table provides an outline of the features Trivy offers. + +| Package manager | File | Transitive dependencies | Dev dependencies | Position | +|-----------------|-----------------------|:-----------------------:|:----------------:|:--------:| +| Quantum | quantum.spec.json[^1] | - | - | ✓ | + +## Detection + +Trivy searches for `quantum.spec.json` files within `.dist-info` directories. These files are created by quantum-builders' `stamp_env()` function when packaging quantum wheels. + +### Quantum Spec Structure + +The `quantum.spec.json` file contains metadata about the quantum package: + +```json +{ + "name": "quantum-native-curl", + "library_name": "curl", + "version": "7.88.1", + "fullversion": "7.88.1.post5+dr", + "suffix": ".post5+dr", + "library_type": "native", + "license": "MIT", + "upstream": "https://curl.se/download/curl-7.88.1.tar.gz", + "platform": "linux-x86_64", + "python": "any", + "dependencies": ["quantum-native-openssl==1.1.1.post3+dr"], + "changelog": [] +} +``` + +### Package Name Extraction + +Trivy extracts the canonical package name from the `upstream` field: + +1. Parses the upstream URL (e.g., `https://curl.se/download/curl-7.88.1.tar.gz`) +2. Extracts the base filename and removes archive extensions +3. Removes version suffix to get the package name (e.g., `curl-7.88.1` → `curl`) +4. Falls back to the `library_name` field if URL parsing fails + +### Version Mapping + +Trivy uses the `version` field (upstream version without DataRobot suffix) rather than the `fullversion` field to match against vulnerability databases. + +For example: +- `version`: `7.88.1` (used for CVE matching) +- `fullversion`: `7.88.1.post5+dr` (internal DataRobot version) + +## Vulnerability Detection + +Quantum packages are checked against the Conan ecosystem in the vulnerability database, which covers C/C++ packages and their vulnerabilities from: + +- GitLab Advisory Database +- National Vulnerability Database (NVD) +- Other C/C++ security advisories + +## Example + +Given a container with quantum packages installed: + +```bash +$ trivy image my-image:latest +``` + +Trivy will: +1. Detect `.dist-info/quantum.spec.json` files +2. Parse metadata to extract library name and version +3. Create package entries (e.g., `curl@7.88.1`) +4. Check against vulnerability databases +5. Report any CVEs found + +## Limitations + +- Transitive dependencies are not analyzed (quantum packages list dependencies but Trivy treats each package independently) +- Only native C/C++ library quantum packages are supported +- Vulnerability data is limited to what's available in the Conan/GitLab Advisory Database + +[^1]: Located in `.dist-info` directories, e.g., `quantum_native_curl-7.88.1.post5+dr.dist-info/quantum.spec.json` diff --git a/pkg/dependency/parser/python/quantum/parse.go b/pkg/dependency/parser/python/quantum/parse.go new file mode 100644 index 000000000000..557ab1530d50 --- /dev/null +++ b/pkg/dependency/parser/python/quantum/parse.go @@ -0,0 +1,128 @@ +package quantum + +import ( + "encoding/json" + "net/url" + "path" + "strings" + + "golang.org/x/xerrors" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type Parser struct { + logger *log.Logger +} + +func NewParser() *Parser { + return &Parser{ + logger: log.WithPrefix("quantum"), + } +} + +// QuantumSpec represents the structure of quantum.spec.json +type QuantumSpec struct { + Name string `json:"name"` + LibraryName string `json:"library_name"` + Version string `json:"version"` + FullVersion string `json:"fullversion"` + LibraryType string `json:"library_type"` + License string `json:"license"` + Upstream string `json:"upstream"` + Platform string `json:"platform"` + Python string `json:"python"` + Dependencies []string `json:"dependencies"` +} + +// Parse parses quantum.spec.json files +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { + var spec QuantumSpec + if err := json.NewDecoder(r).Decode(&spec); err != nil { + return nil, nil, xerrors.Errorf("failed to decode quantum.spec.json: %w", err) + } + + // Extract the canonical package name from upstream URL + pkgName := p.extractPackageName(spec) + if pkgName == "" { + p.logger.Debug("Unable to determine package name from quantum spec", + log.String("quantum_name", spec.Name)) + return nil, nil, xerrors.New("unable to determine package name") + } + + // Use the upstream version (without DataRobot suffix) + version := spec.Version + if version == "" { + p.logger.Debug("Version is empty in quantum spec", log.String("name", spec.Name)) + return nil, nil, xerrors.New("version is empty") + } + + pkg := ftypes.Package{ + ID: pkgName + "@" + version, + Name: pkgName, + Version: version, + } + + // Add license if present + if spec.License != "" { + pkg.Licenses = []string{spec.License} + } + + return []ftypes.Package{pkg}, nil, nil +} + +// extractPackageName extracts the canonical package name from the quantum spec +func (p *Parser) extractPackageName(spec QuantumSpec) string { + // Try to extract from upstream URL first + if spec.Upstream != "" { + if name := p.extractFromUpstream(spec.Upstream); name != "" { + return name + } + } + + // Fall back to library_name if upstream parsing fails + if spec.LibraryName != "" { + return spec.LibraryName + } + + return "" +} + +// extractFromUpstream extracts the package name from an upstream URL +// For example: "https://curl.se/download/curl-7.88.1.tar.gz" -> "curl" +func (p *Parser) extractFromUpstream(upstream string) string { + u, err := url.Parse(upstream) + if err != nil { + p.logger.Debug("Failed to parse upstream URL", log.String("url", upstream), log.Err(err)) + return "" + } + + // Get the base name from the URL path + // e.g., "curl-7.88.1.tar.gz" + basename := path.Base(u.Path) + + // Remove common archive extensions + for _, ext := range []string{".tar.gz", ".tar.bz2", ".tar.xz", ".tgz", ".zip"} { + basename = strings.TrimSuffix(basename, ext) + } + + // Try to extract package name by removing version pattern + // Examples: + // "curl-7.88.1" -> "curl" + // "openssl-1.1.1" -> "openssl" + // "libpng-1.6.37" -> "libpng" + parts := strings.Split(basename, "-") + if len(parts) >= 2 { + // Check if the last part looks like a version (starts with a digit) + lastPart := parts[len(parts)-1] + if len(lastPart) > 0 && (lastPart[0] >= '0' && lastPart[0] <= '9') { + // Return everything except the last part (the version) + return strings.Join(parts[:len(parts)-1], "-") + } + } + + // If we can't determine from the pattern, return the whole basename + return basename +} diff --git a/pkg/dependency/parser/python/quantum/parse_test.go b/pkg/dependency/parser/python/quantum/parse_test.go new file mode 100644 index 000000000000..c1d04de0d75c --- /dev/null +++ b/pkg/dependency/parser/python/quantum/parse_test.go @@ -0,0 +1,128 @@ +package quantum + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestParser_Parse(t *testing.T) { + tests := []struct { + name string + file string + want []ftypes.Package + wantErr string + }{ + { + name: "curl quantum package", + file: "testdata/curl-quantum.spec.json", + want: []ftypes.Package{ + { + ID: "curl@7.88.1", + Name: "curl", + Version: "7.88.1", + Licenses: []string{"MIT"}, + }, + }, + }, + { + name: "openssl quantum package", + file: "testdata/openssl-quantum.spec.json", + want: []ftypes.Package{ + { + ID: "openssl@1.1.1", + Name: "openssl", + Version: "1.1.1", + Licenses: []string{"OpenSSL"}, + }, + }, + }, + { + name: "libpng quantum package", + file: "testdata/libpng-quantum.spec.json", + want: []ftypes.Package{ + { + ID: "libpng@1.6.37", + Name: "libpng", + Version: "1.6.37", + Licenses: []string{"libpng"}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + defer f.Close() + + p := NewParser() + got, _, err := p.Parse(f) + + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestParser_extractFromUpstream(t *testing.T) { + tests := []struct { + name string + upstream string + want string + }{ + { + name: "curl with tar.gz", + upstream: "https://curl.se/download/curl-7.88.1.tar.gz", + want: "curl", + }, + { + name: "openssl with tar.gz", + upstream: "https://www.openssl.org/source/openssl-1.1.1.tar.gz", + want: "openssl", + }, + { + name: "libpng with tar.xz", + upstream: "https://downloads.sourceforge.net/libpng/libpng-1.6.37.tar.xz", + want: "libpng", + }, + { + name: "krb5 with tar.gz", + upstream: "https://web.mit.edu/kerberos/dist/krb5/krb5-1.20.tar.gz", + want: "krb5", + }, + { + name: "package with zip", + upstream: "https://example.com/download/package-1.0.0.zip", + want: "package", + }, + { + name: "package with tgz", + upstream: "https://example.com/download/package-2.1.5.tgz", + want: "package", + }, + { + name: "no version in name", + upstream: "https://example.com/download/mypackage.tar.gz", + want: "mypackage", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := NewParser() + got := p.extractFromUpstream(tt.upstream) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/dependency/parser/python/quantum/testdata/curl-quantum.spec.json b/pkg/dependency/parser/python/quantum/testdata/curl-quantum.spec.json new file mode 100644 index 000000000000..4dc1cf5a8b5c --- /dev/null +++ b/pkg/dependency/parser/python/quantum/testdata/curl-quantum.spec.json @@ -0,0 +1,14 @@ +{ + "name": "quantum-native-curl", + "library_name": "curl", + "version": "7.88.1", + "fullversion": "7.88.1.post5+dr", + "suffix": ".post5+dr", + "library_type": "native", + "license": "MIT", + "upstream": "https://curl.se/download/curl-7.88.1.tar.gz", + "platform": "linux-x86_64", + "python": "any", + "dependencies": ["quantum-native-openssl==1.1.1.post3+dr"], + "changelog": [] +} diff --git a/pkg/dependency/parser/python/quantum/testdata/libpng-quantum.spec.json b/pkg/dependency/parser/python/quantum/testdata/libpng-quantum.spec.json new file mode 100644 index 000000000000..0048d49af22c --- /dev/null +++ b/pkg/dependency/parser/python/quantum/testdata/libpng-quantum.spec.json @@ -0,0 +1,14 @@ +{ + "name": "quantum-native-libpng", + "library_name": "libpng", + "version": "1.6.37", + "fullversion": "1.6.37.post1+dr", + "suffix": ".post1+dr", + "library_type": "native", + "license": "libpng", + "upstream": "https://downloads.sourceforge.net/libpng/libpng-1.6.37.tar.xz", + "platform": "linux-x86_64", + "python": "any", + "dependencies": [], + "changelog": [] +} diff --git a/pkg/dependency/parser/python/quantum/testdata/openssl-quantum.spec.json b/pkg/dependency/parser/python/quantum/testdata/openssl-quantum.spec.json new file mode 100644 index 000000000000..e75822e2820a --- /dev/null +++ b/pkg/dependency/parser/python/quantum/testdata/openssl-quantum.spec.json @@ -0,0 +1,14 @@ +{ + "name": "quantum-native-openssl", + "library_name": "openssl", + "version": "1.1.1", + "fullversion": "1.1.1.post3+dr", + "suffix": ".post3+dr", + "library_type": "native", + "license": "OpenSSL", + "upstream": "https://www.openssl.org/source/openssl-1.1.1.tar.gz", + "platform": "linux-x86_64", + "python": "any", + "dependencies": [], + "changelog": [] +} diff --git a/pkg/detector/library/driver.go b/pkg/detector/library/driver.go index 83c4a0ee4f45..9d50b44b7440 100644 --- a/pkg/detector/library/driver.go +++ b/pkg/detector/library/driver.go @@ -62,6 +62,11 @@ func NewDriver(libType ftypes.LangType) (Driver, bool) { // Only semver can be used for version ranges // https://docs.conan.io/en/latest/versioning/version_ranges.html comparer = compare.GenericComparer{} + case ftypes.Quantum: + // Quantum packages are native C/C++ libraries packaged as Python wheels + // Use Conan ecosystem for vulnerability detection as it covers C/C++ packages + ecosystem = vulnerability.Conan + comparer = compare.GenericComparer{} case ftypes.Swift: // Swift uses semver // https://www.swift.org/package-manager/#importing-dependencies diff --git a/pkg/fanal/analyzer/all/import.go b/pkg/fanal/analyzer/all/import.go index f4b2ff904eb1..8ebd02621c55 100644 --- a/pkg/fanal/analyzer/all/import.go +++ b/pkg/fanal/analyzer/all/import.go @@ -32,6 +32,7 @@ import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/python/pip" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/python/pipenv" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/python/poetry" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/python/quantum" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/python/uv" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/ruby/bundler" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/ruby/gemspec" diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index 70d96621c464..3d834e61714e 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -84,6 +84,7 @@ const ( TypePipenv Type = "pipenv" TypePoetry Type = "poetry" TypeUv Type = "uv" + TypeQuantumPkg Type = "quantum-pkg" // Go TypeGoBinary Type = "gobinary" @@ -204,6 +205,7 @@ var ( TypePipenv, TypePoetry, TypeUv, + TypeQuantumPkg, TypeGoBinary, TypeGoMod, TypeRustBinary, @@ -246,6 +248,7 @@ var ( TypeNodePkg, TypeCondaPkg, TypePythonPkg, + TypeQuantumPkg, TypeGoBinary, TypeJar, TypeRustBinary, diff --git a/pkg/fanal/analyzer/language/python/quantum/quantum.go b/pkg/fanal/analyzer/language/python/quantum/quantum.go new file mode 100644 index 000000000000..4d6679db6387 --- /dev/null +++ b/pkg/fanal/analyzer/language/python/quantum/quantum.go @@ -0,0 +1,92 @@ +package quantum + +import ( + "context" + "io" + "io/fs" + "os" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/python/quantum" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypeQuantumPkg, newQuantumAnalyzer) +} + +const version = 1 + +func newQuantumAnalyzer(opt analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &quantumAnalyzer{ + logger: log.WithPrefix("quantum"), + pkgParser: quantum.NewParser(), + }, nil +} + +type quantumAnalyzer struct { + logger *log.Logger + pkgParser language.Parser +} + +// PostAnalyze analyzes quantum.spec.json files within .dist-info directories +func (a quantumAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + var apps []types.Application + + required := func(path string, _ fs.DirEntry) bool { + // Match quantum.spec.json files in .dist-info directories + return strings.HasSuffix(path, ".dist-info/quantum.spec.json") + } + + err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, _ fs.DirEntry, r io.Reader) error { + rsa, ok := r.(xio.ReadSeekerAt) + if !ok { + return xerrors.New("invalid reader") + } + + app, err := a.parse(filePath, rsa, input.Options.FileChecksum) + if err != nil { + a.logger.Debug("Failed to parse quantum spec", + log.String("file", filePath), log.Err(err)) + // Continue processing other files even if one fails + return nil + } else if app == nil { + return nil + } + + apps = append(apps, *app) + return nil + }) + + if err != nil { + return nil, xerrors.Errorf("quantum package walk error: %w", err) + } + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func (a quantumAnalyzer) parse(filePath string, r xio.ReadSeekerAt, checksum bool) (*types.Application, error) { + return language.ParsePackage(types.Quantum, filePath, r, a.pkgParser, checksum) +} + +func (a quantumAnalyzer) Required(filePath string, _ os.FileInfo) bool { + // Match quantum.spec.json files within .dist-info directories + return strings.Contains(filePath, ".dist-info") && strings.HasSuffix(filePath, "quantum.spec.json") +} + +func (a quantumAnalyzer) Type() analyzer.Type { + return analyzer.TypeQuantumPkg +} + +func (a quantumAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/python/quantum/quantum_test.go b/pkg/fanal/analyzer/language/python/quantum/quantum_test.go new file mode 100644 index 000000000000..650c1d83a90c --- /dev/null +++ b/pkg/fanal/analyzer/language/python/quantum/quantum_test.go @@ -0,0 +1,122 @@ +package quantum + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_quantumAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + dir string + includeChecksum bool + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path with quantum packages", + dir: "testdata/happy", + includeChecksum: false, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Quantum, + FilePath: "quantum_native_curl-7.88.1.post5+dr.dist-info/quantum.spec.json", + Packages: types.Packages{ + { + ID: "curl@7.88.1", + Name: "curl", + Version: "7.88.1", + Licenses: []string{"MIT"}, + FilePath: "quantum_native_curl-7.88.1.post5+dr.dist-info/quantum.spec.json", + }, + }, + }, + { + Type: types.Quantum, + FilePath: "quantum_native_openssl-1.1.1.post3+dr.dist-info/quantum.spec.json", + Packages: types.Packages{ + { + ID: "openssl@1.1.1", + Name: "openssl", + Version: "1.1.1", + Licenses: []string{"OpenSSL"}, + FilePath: "quantum_native_openssl-1.1.1.post3+dr.dist-info/quantum.spec.json", + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newQuantumAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + got, err := a.PostAnalyze(t.Context(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + Options: analyzer.AnalysisOptions{ + FileChecksum: tt.includeChecksum, + }, + }) + + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + return + } + + require.NoError(t, err) + assert.ElementsMatch(t, tt.want.Applications, got.Applications) + }) + } +} + +func Test_quantumAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "quantum spec in dist-info", + filePath: "quantum_native_curl-7.88.1.dist-info/quantum.spec.json", + want: true, + }, + { + name: "quantum spec with complex path", + filePath: "site-packages/quantum_native_openssl-1.1.1.post3+dr.dist-info/quantum.spec.json", + want: true, + }, + { + name: "not a quantum spec file", + filePath: "quantum_native_curl-7.88.1.dist-info/METADATA", + want: false, + }, + { + name: "quantum.spec.json outside dist-info", + filePath: "random/quantum.spec.json", + want: false, + }, + { + name: "regular python package", + filePath: "python3.8/site-packages/distlib-0.3.1.dist-info/METADATA", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := quantumAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/python/quantum/testdata/happy/quantum_native_curl-7.88.1.post5+dr.dist-info/quantum.spec.json b/pkg/fanal/analyzer/language/python/quantum/testdata/happy/quantum_native_curl-7.88.1.post5+dr.dist-info/quantum.spec.json new file mode 100644 index 000000000000..4dc1cf5a8b5c --- /dev/null +++ b/pkg/fanal/analyzer/language/python/quantum/testdata/happy/quantum_native_curl-7.88.1.post5+dr.dist-info/quantum.spec.json @@ -0,0 +1,14 @@ +{ + "name": "quantum-native-curl", + "library_name": "curl", + "version": "7.88.1", + "fullversion": "7.88.1.post5+dr", + "suffix": ".post5+dr", + "library_type": "native", + "license": "MIT", + "upstream": "https://curl.se/download/curl-7.88.1.tar.gz", + "platform": "linux-x86_64", + "python": "any", + "dependencies": ["quantum-native-openssl==1.1.1.post3+dr"], + "changelog": [] +} diff --git a/pkg/fanal/analyzer/language/python/quantum/testdata/happy/quantum_native_openssl-1.1.1.post3+dr.dist-info/quantum.spec.json b/pkg/fanal/analyzer/language/python/quantum/testdata/happy/quantum_native_openssl-1.1.1.post3+dr.dist-info/quantum.spec.json new file mode 100644 index 000000000000..e75822e2820a --- /dev/null +++ b/pkg/fanal/analyzer/language/python/quantum/testdata/happy/quantum_native_openssl-1.1.1.post3+dr.dist-info/quantum.spec.json @@ -0,0 +1,14 @@ +{ + "name": "quantum-native-openssl", + "library_name": "openssl", + "version": "1.1.1", + "fullversion": "1.1.1.post3+dr", + "suffix": ".post3+dr", + "library_type": "native", + "license": "OpenSSL", + "upstream": "https://www.openssl.org/source/openssl-1.1.1.tar.gz", + "platform": "linux-x86_64", + "python": "any", + "dependencies": [], + "changelog": [] +} diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index 06f3984e948c..4267f94b88d8 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -96,6 +96,7 @@ const ( Hex LangType = "hex" Bitnami LangType = "bitnami" Julia LangType = "julia" + Quantum LangType = "quantum" K8sUpstream LangType = "kubernetes" EKS LangType = "eks" // Amazon Elastic Kubernetes Service