Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • pschou/secfixes-tracker
  • alpine/security/secfixes-tracker
  • ncopa/secfixes-tracker
  • Gapd0909/secfixes-tracker
  • cookiengineer/secfixes-tracker
  • Ismaelkadir/secfixes-tracker
  • Dramaga11/secfixes-tracker
  • mcm/secfixes-tracker
  • fossdd/secfixes-tracker
  • dne/secfixes-tracker
10 results
Show changes
Commits on Source (8)
Showing
with 2251 additions and 7 deletions
application.toml
/secfixes-cli
test-default:
image: alpine:3.18
stage: test
stages:
- verify
python:test:
image: alpine:3.20
stage: verify
script:
- apk upgrade -U -a
- apk add python3 py3-flask py3-pytest py3-pytest-cov py3-pip py3-requests py3-yaml py3-dotenv py3-requests-mock
- pip install -r requirements.txt
- pip install --break-system-packages -r requirements.txt
- python3 -m pytest -v --cov=secfixes_tracker --cov-report term-missing --cov-report xml tests/
coverage: /^TOTAL.+?(\d+\%)$/
artifacts:
......@@ -13,6 +16,18 @@ test-default:
coverage_report:
coverage_format: cobertura
path: coverage.xml
tags:
- docker-alpine
- x86_64
tags: [docker-alpine, x86_64]
go:lint:
stage: verify
image: golangci/golangci-lint:v1.58.1
script:
- golangci-lint run
tags: [docker-alpine, x86_64]
go:build:
stage: verify
image: registry.alpinelinux.org/alpine/infra/docker/golang:3.20
script:
- redo
tags: [docker-alpine, x86_64]
# Options for analysis running
[run]
concurrency = 4
timeout = "10m"
# All available settings of specific linters
[linters-settings]
[linters-settings.misspell]
locale = "US"
[linters-settings.unused]
# Treat code as a program (not a library) and report unused exported identifiers
check-exported = false
[linters-settings.gocritic]
enabled-tags = ["diagnostic", "style", "performance"]
disabled-checks = ["importShadow", "appendAssign"]
[linters-settings.gocritic.settings]
hugeParam.sizeThreshold = 1000
rangeValCopy.sizeThreshold = 1000
[linters-settings.govet]
enable-all = true
disable = [
"shadow"
]
[[linters-settings.revive.rules]]
name = "var-naming"
arguments = [
[],
[],
[{skipPackageNameChecks = true}]
]
[[linters-settings.revive.rules]]
name = "unused-parameter"
disabled = true
[linters]
enable = [
"gocritic",
"asciicheck",
"dogsled",
"errorlint",
"exportloopref",
"goconst",
"gosimple",
"govet",
"ineffassign",
"misspell",
"nakedret",
"nolintlint",
"staticcheck",
"typecheck",
"unconvert",
"unused",
"whitespace",
"gci",
"gofumpt",
"wastedassign",
]
disable = [
"errcheck"
]
disable-all = false
fast = false
[issues]
fix = true
all: secfixes-cli
.PHONY: all
GO_FILES=$(shell find . -type f -name '*.go')
secfixes-cli: $(GO_FILES) go.mod go.sum
go build -o secfixes-cli ./importer/cmd/secfixes-cli
redo-ifchange secfixes-cli
db_path = "secfixes-tracker.sqlite"
[importers.vulnrich]
lookup_period = "48h"
[[rewriters]]
predicate = "target_sw == 'python'"
rewrite_rule = "product | lower() | replace('_', '-') | fmt('py3-%s')"
[[rewriters]]
predicate = "target_sw == 'ruby'"
rewrite_rule = "product | lower() | replace('_', '-') | fmt('ruby-%s')"
[[rewriters]]
predicate = "target_sw == 'wordpress'"
rewrite_rule = "product | lower() | replace('_', '-') | fmt('wordpress-%s')"
module gitlab.alpinelinux.org/alpine/security/secfixes-tracker
go 1.20
require (
github.com/BurntSushi/toml v1.3.2
github.com/expr-lang/expr v1.16.7
github.com/glebarez/sqlite v1.11.0
github.com/go-git/go-git/v5 v5.12.0
github.com/moznion/go-optional v0.11.0
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.9.0
gorm.io/gorm v1.25.10
)
require (
dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/tools v0.13.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
)
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/expr-lang/expr v1.16.7 h1:gCIiHt5ODA0xIaDbD0DPKyZpM9Drph3b3lolYAYq2Kw=
github.com/expr-lang/expr v1.16.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/moznion/go-optional v0.11.0 h1:5UcbqhXo0P34gcVlQ5IwYcqW6t8rCyxOfVWS+9zCpc8=
github.com/moznion/go-optional v0.11.0/go.mod h1:VLENS2WxeppZH4cTCQswYCznMRtFDXfaBjAKbmuz6s0=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
package main
import (
"time"
"github.com/spf13/cobra"
"gitlab.alpinelinux.org/alpine/security/secfixes-tracker/importer"
)
var nvdCmd = &cobra.Command{
Use: "import-nvd",
Short: "Import NVD secfixes",
RunE: runImportNVD,
}
func runImportNVD(cmd *cobra.Command, args []string) error {
err := importer.NVDFeed(
App().DB,
&importer.APIv2{},
App().Config,
7*24*time.Hour,
)
return err
}
func init() {
rootCmd.AddCommand(nvdCmd)
}
package main
import (
"fmt"
"log/slog"
"os"
"github.com/glebarez/sqlite"
"github.com/spf13/cobra"
"gitlab.alpinelinux.org/alpine/security/secfixes-tracker/secfixes"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
var rootCmd = &cobra.Command{
Short: "Import data into the secfixes tracker",
}
var _app app
type app struct {
DB *gorm.DB
Config secfixes.Config
}
func App() app {
return _app
}
func main() {
err := run()
if err != nil {
fmt.Printf("FATAL: %s\n", err)
os.Exit(1)
}
}
func run() error {
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
slog.SetDefault(logger)
config, err := secfixes.ParseConfigFromFile("config/application.toml")
if err != nil {
return fmt.Errorf("error reading 'application.toml': %w", err)
}
_app.Config = config
db, err := gorm.Open(sqlite.Open(config.DBPath), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
PrepareStmt: true,
SkipDefaultTransaction: true,
})
if err != nil {
return fmt.Errorf("could not open secfixes.db: %w", err)
}
_app.DB = db
err = rootCmd.Execute()
if err != nil {
return err
}
return nil
}
package main
import (
"github.com/spf13/cobra"
"gitlab.alpinelinux.org/alpine/security/secfixes-tracker/importer"
)
var vulnrichtmentCmd = &cobra.Command{
Use: "import-vulnrich",
Short: "Import vulnrichment secfixes",
RunE: runImportVulnrichment,
}
func runImportVulnrichment(cmd *cobra.Command, args []string) error {
tx := App().DB.Begin()
defer tx.Commit()
return importer.VulnrichtFeed(
tx, App().Config, App().Config.Importers.Vulnrich.LookupPeriod)
}
func init() {
rootCmd.AddCommand(vulnrichtmentCmd)
}
package importer
import (
"context"
"fmt"
"log/slog"
"time"
"github.com/moznion/go-optional"
"gitlab.alpinelinux.org/alpine/security/secfixes-tracker/secfixes"
"gorm.io/gorm"
)
func NVDFeed(
db *gorm.DB,
nvdApi *APIv2,
config secfixes.Config,
period time.Duration,
) error {
rewriters := make([]compiledRewriter, 0, len(config.Rewriters))
for i, rewriter := range config.Rewriters {
cr, err := NewCompiledRewriter(rewriter)
if err != nil {
return fmt.Errorf("could not parse rewrite rule %d, %w", i+1, err)
}
rewriters = append(rewriters, cr)
}
slog.Info("Importing NVD change feed", "period", period)
start := time.Date(2023, 7, 4, 0, 0, 0, 0, time.UTC)
end := time.Date(2023, 7, 12, 0, 0, 0, 0, time.UTC)
slog.Info("Requesting CVEs", "starttime", start, "endtime", end)
resp, err := nvdApi.GetCVEs(
PubStart(start),
PubEnd(end),
)
if err != nil {
return fmt.Errorf("could not get nvd change feed: %w", err)
}
slog.Info("Finished requesting CVEs", "total_results", resp.TotalResults, "results", len(resp.Vulnerabilities))
ctx := context.Background()
tx := db.Begin()
if tx.Error != nil {
return fmt.Errorf("could not start transaction: %w", err)
}
for _, item := range resp.Vulnerabilities {
err := ProcessNvdCveItem(ctx, tx, rewriters, item)
if err != nil {
slog.Error("could not process item", "item", item.CVE.ID, "err", err)
}
}
result := tx.Commit()
return result.Error
}
func ProcessNvdCveItem(
ctx context.Context,
db *gorm.DB,
rewriters []compiledRewriter,
item Vulnerability,
) error {
description := item.CVE.Descriptions.SelectLang("en")
if description.IsNone() {
return nil
}
slog.Info("Processing vulnerability", "cve", item.CVE.ID)
impact := item.CVE.Metrics.CvssMetricV31.SelectByType("Primary")
vuln := secfixes.Vulnerability{}
description.IfSome(func(v Description) {
vuln.Description = v.Value
})
impact.IfSome(func(v CvssMetricV31) {
var err error
vuln.Cvss3Score, err = v.CvssData.BaseScore.Float64()
if err != nil {
slog.Error(
"could not convert basescore to float64",
"cve", item.CVE.ID,
"err", err,
)
}
vuln.Cvss3Vector = v.CvssData.VectorString
})
result := db.
Where(secfixes.Vulnerability{CveID: item.CVE.ID}).
Assign(vuln).
FirstOrCreate(&vuln)
if result.Error != nil {
return fmt.Errorf("could not create or update cve %s: %w", item.CVE.ID, result.Error)
}
ProcessNvdCveConfigurations(
ctx,
db,
vuln,
rewriters,
item.CVE.Configurations)
return nil
}
func ProcessNvdCveConfigurations(
ctx context.Context,
db *gorm.DB,
vuln secfixes.Vulnerability,
rewriters []compiledRewriter,
configurations []Configuration,
) {
for _, configuration := range configurations {
for _, node := range configuration.Nodes {
for _, match := range node.CPEMatch {
cpe := match.Criteria
for _, rewriter := range rewriters {
cpe = rewriter.Rewrite(cpe)
}
sourcePkgname := cpe.Product
sourceVersion := optional.Some(cpe.Version)
if cpe.Version == "*" {
sourceVersion = optional.None[string]()
}
maxVersion := match.VersionEndIncluding.Or(
match.VersionEndExcluding.Or(
sourceVersion,
),
)
minVersion := match.VersionStartIncluding.Or(
match.VersionStartExcluding,
)
minVersionOp := "=="
maxVersionOp := "=="
if match.UsesVersionRanges() {
minVersionOp = ">="
if match.VersionStartExcluding.IsSome() {
minVersionOp = ">"
}
maxVersionOp = "<="
if match.VersionEndExcluding.IsSome() {
maxVersionOp = "<"
}
}
pkg := secfixes.Package{PackageName: sourcePkgname}
result := db.
Where(pkg).
Assign(pkg).
FirstOrCreate(&pkg)
if result.Error != nil {
slog.Error(
"could not create or update package",
"package", pkg.PackageName,
"cve", vuln.CveID,
"err", result.Error,
)
continue
}
cpeMatch := secfixes.CPEMatch{
VulnID: vuln.VulnID,
PackageID: pkg.PackageID,
MinimumVersion: minVersion.UnwrapAsPtr(),
MaximumVersion: maxVersion.UnwrapAsPtr(),
MinimumVersionOP: minVersionOp,
MaximumVersionOP: maxVersionOp,
Vulnerable: match.Vulnerable,
CpeUri: cpe.Uri,
}
result = db.
Where(secfixes.CPEMatch{
VulnID: vuln.VulnID,
PackageID: pkg.PackageID,
}).
Assign(cpeMatch).
FirstOrCreate(&cpeMatch)
if result.Error != nil {
slog.Error(
"could not create or update cpematch",
"package", pkg.PackageName,
"cve", vuln.CveID,
"match_criteria_id", match.MatchCriteriaId,
"err", result.Error,
)
}
}
}
}
}
package importer
import (
"encoding/json"
"fmt"
"io"
"log/slog"
"strings"
"time"
o "github.com/moznion/go-optional"
"gitlab.alpinelinux.org/alpine/security/secfixes-tracker/importer/vulnrich"
"gitlab.alpinelinux.org/alpine/security/secfixes-tracker/secfixes"
"gorm.io/gorm"
)
func VulnrichtFeed(
db *gorm.DB,
config secfixes.Config,
period time.Duration,
) error {
slog.Info("Fetching vulnrichment repository")
repo, err := vulnrich.GetRepo("https://github.com/cisagov/vulnrichment.git", "vulnrichment.git")
if err != nil {
return err
}
err = vulnrich.UpdateRepo(repo)
if err != nil {
return fmt.Errorf("could not update vulnrichment repo: %w", err)
}
cveFiles, err := vulnrich.GetCVEFiles(repo)
if err != nil {
return fmt.Errorf("could not get files from repo: %w", err)
}
records := vulnrich.Records{}
for _, entryReader := range cveFiles {
entry := vulnrich.Record{}
data, err := io.ReadAll(entryReader.Content)
if err != nil {
return err
}
entryReader.Content.Close()
err = json.Unmarshal(data, &entry)
if err != nil {
slog.Error("could not unmarshal vulnrich record", "filename", entryReader.Name, "err", err)
continue
}
records = append(records, entry)
}
for _, record := range records.Filter(func(r vulnrich.Record) bool {
container := OptionalFirst(r.Containers.Adp).TakeOr(r.Containers.Cna)
return container.ProviderMetadata.DateUpdated.After(time.Now().Add(-period))
}) {
container := OptionalFirst(record.Containers.Adp).TakeOr(record.Containers.Cna)
slog.Info("CVE record", "cve", record.CveMetadata.CveID, "updated", container.ProviderMetadata.DateUpdated)
err := ProcessVulnrichRecord(db, config, record)
if err != nil {
slog.Error("could not process vulnricht record", "cve", record.CveMetadata.CveID, "err", err)
}
}
return nil
}
func ProcessVulnrichRecord(
db *gorm.DB,
config secfixes.Config,
record vulnrich.Record,
) error {
rewriters := make([]compiledRewriter, 0, len(config.Rewriters))
for i, rewriter := range config.Rewriters {
cr, err := NewCompiledRewriter(rewriter)
if err != nil {
return fmt.Errorf("could not parse rewrite rule %d, %w", i+1, err)
}
rewriters = append(rewriters, cr)
}
cna := record.Containers.Cna
adp := OptionalFirst(record.Containers.Adp)
cveDescription := o.None[vulnrich.CveDescriptions]()
if record.CveMetadata.State == vulnrich.CveStateREJECTED {
cveDescription = o.Some(record.Containers.Cna.RejectedReasons)
}
cveDescription = cveDescription.
Or(OptionalNonEmpty(cna.Descriptions)).
Or(o.FlatMap(adp, func(v vulnrich.CveContainer) o.Option[vulnrich.CveDescriptions] {
return OptionalNonEmpty(v.Descriptions)
}))
description := o.
FlatMap(cveDescription, func(v vulnrich.CveDescriptions) o.Option[string] {
cveDescription := v.ForLang("en").Or(
v.ForLang("en-US"),
)
return o.FlatMap(cveDescription, func(v vulnrich.CveDescription) o.Option[string] {
return o.Some(v.Value)
})
}).
Or(cna.Title)
if description.IsNone() {
slog.Warn("No descriptions found", "cve", record.CveMetadata.CveID)
return nil
}
var metric o.Option[vulnrich.Metric]
// Default to metrics from CNA
metric = OptionalFirst(cna.Metrics)
// If metrics from ADP are available and no metrics from CNA are available,
// use that.
metric = metric.Or(
o.FlatMap(
OptionalFirst(record.Containers.Adp),
func(v vulnrich.CveContainer) o.Option[vulnrich.Metric] {
return OptionalFirst(v.Metrics)
},
),
)
// Find the highest available cvss version metric on the found metric
cvssMetric := o.FlatMap(
metric,
func(v vulnrich.Metric) o.Option[vulnrich.CvssMetric] {
return o.None[vulnrich.CvssMetric]().
Or(v.CvssV40).
Or(v.CvssV31).
Or(v.CvssV30).
Or(v.CvssV20)
},
)
vuln := secfixes.Vulnerability{}
description.IfSome(func(v string) {
vuln.Description = v
})
cvssMetric.IfSome(func(v vulnrich.CvssMetric) {
vuln.Cvss3Score, _ = v.BaseScore.Float64()
vuln.Cvss3Vector = v.VectorString
})
result := db.
Where(secfixes.Vulnerability{CveID: record.CveMetadata.CveID}).
Assign(vuln).
FirstOrCreate(&vuln)
if result.Error != nil {
return fmt.Errorf("could not create or update cve %s: %w", record.CveMetadata.CveID, result.Error)
}
cpeMatchIDsByPackage := map[int][]int{}
for _, affected := range cna.Affected {
cpeMatchIDsByPackageForAffected, err := ProcessCveAffected(db, config, rewriters, cna, vuln, affected)
if err != nil {
slog.Error("could not process cna.Affected", "err", err)
continue
}
for packageID, matchIDs := range cpeMatchIDsByPackageForAffected {
cpeMatchIDsByPackage[packageID] = append(cpeMatchIDsByPackage[packageID], matchIDs...)
}
}
adp.IfSome(func(adp vulnrich.CveContainer) {
for _, affected := range adp.Affected {
cpeMatchIDsByPackageForAffected, err := ProcessCveAffected(db, config, rewriters, adp, vuln, affected)
if err != nil {
slog.Error("could not process adp.Affected", "err", err)
continue
}
for packageID, matchIDs := range cpeMatchIDsByPackageForAffected {
cpeMatchIDsByPackage[packageID] = append(cpeMatchIDsByPackage[packageID], matchIDs...)
}
}
})
refs := OptionalNonEmpty(cna.References).
Or(o.Map(adp, func(v vulnrich.CveContainer) []vulnrich.Reference { return v.References })).
TakeOr([]vulnrich.Reference{})
for _, ref := range refs {
err := ProcessCveRef(db, config, vuln, ref)
if err != nil {
slog.Error("could not process ref", "cve", vuln.CveID, "url", ref.URL, "err", err)
}
}
packageIDs := []int{}
for packageID, cpeMatchIDs := range cpeMatchIDsByPackage {
packageIDs = append(packageIDs, packageID)
result := db.Exec(
`DELETE FROM cpe_match where vuln_id = ? and package_id = ? and cpe_match_id NOT IN ?`,
vuln.VulnID,
packageID,
cpeMatchIDs,
)
if result.Error != nil {
slog.Error(
"could not clean up matches for package",
"vuln_id", vuln.VulnID,
"cve", vuln.CveID,
"package_id", packageID,
"err", result.Error,
)
continue
}
slog.Debug(
"cleaning up left over matches for package",
"vuln_id", vuln.VulnID,
"cve", vuln.CveID,
"package_id", packageID,
"deleted", result.RowsAffected,
)
}
slog.Debug("cleaning up orphaned CPE matches", "cve", vuln.CveID)
if len(packageIDs) > 0 {
result = db.Exec(
`DELETE FROM cpe_match where vuln_id = ? AND package_id not in ?`,
vuln.VulnID,
packageIDs,
)
} else {
result = db.Exec(
`DELETE FROM cpe_match where vuln_id = ?`,
vuln.VulnID,
)
}
if result.Error != nil {
slog.Error("could not delete orphaned cpe matches", "err", result.Error)
return nil
}
slog.Debug("finished", "deleted", result.RowsAffected)
return nil
}
func ProcessCveAffected(
db *gorm.DB,
config secfixes.Config,
rewriters []compiledRewriter,
cveContainer vulnrich.CveContainer,
vuln secfixes.Vulnerability,
affected vulnrich.Affected,
) (cpeMatchIDsByPackage map[int][]int, err error) {
cpe := o.FlatMap(
OptionalFirst(affected.CPEs), func(v string) o.Option[CPE23Uri] {
cpe, _ := NewCPEUri(v)
return o.Some(cpe)
}).
TakeOr(CPE23Uri{Vendor: affected.Vendor, Product: affected.Product})
for _, rewriter := range rewriters {
cpe = rewriter.Rewrite(cpe)
}
cpeMatchIDsByPackage = map[int][]int{}
for _, version := range affected.Versions {
cpeMatchId, err := ProcessCveAffectedVersion(db, config, rewriters, cveContainer, vuln, affected, version, cpe)
if err != nil {
return nil, err
}
cpeMatchId.IfSome(func(v secfixes.CPEMatch) {
cpeMatchIDsByPackage[v.PackageID] = append(cpeMatchIDsByPackage[v.PackageID], v.CpeMatchID)
})
}
return cpeMatchIDsByPackage, nil
}
func ProcessCveAffectedVersion(
db *gorm.DB,
config secfixes.Config,
rewriters []compiledRewriter,
cveContainer vulnrich.CveContainer,
vuln secfixes.Vulnerability,
affected vulnrich.Affected,
version vulnrich.Version,
cpe CPE23Uri,
) (cpeMatchId o.Option[secfixes.CPEMatch], err error) {
sourcePkgname := cpe.Product
minVersion := strings.TrimSpace(version.Version.String())
maxVersion := o.None[string]()
minVersionOp := "=="
maxVersionOp := "=="
vulnerable := o.MapOr(
version.Status.Or(affected.DefaultStatus),
false,
func(v vulnrich.AffectedStatus) bool {
return v == vulnrich.AffectedStatusAffected
})
var validVersion bool
switch {
case cveContainer.ProviderMetadata.ShortName == "GitHub_M" && version.LessThan.IsNone() && version.LessThanOrEqual.IsNone():
validVersion = processVersionGithub(version, &minVersion, &maxVersion, &minVersionOp, &maxVersionOp)
default:
validVersion = processVersionStandard(version, &minVersion, &maxVersion, &minVersionOp, &maxVersionOp)
}
if !validVersion {
return o.None[secfixes.CPEMatch](), nil
}
pkg := secfixes.Package{PackageName: sourcePkgname}
result := db.
Where(pkg).
Assign(pkg).
FirstOrCreate(&pkg)
if result.Error != nil {
slog.Error(
"could not create or update package",
"package", pkg.PackageName,
"cve", vuln.CveID,
"err", result.Error,
)
return o.None[secfixes.CPEMatch](), nil
}
cpeMatch := secfixes.CPEMatch{
VulnID: vuln.VulnID,
PackageID: pkg.PackageID,
Vulnerable: vulnerable,
MinimumVersion: &minVersion,
MaximumVersion: maxVersion.UnwrapAsPtr(),
MinimumVersionOP: minVersionOp,
MaximumVersionOP: maxVersionOp,
}
slog.Debug(
"cpematch",
"cve", vuln.CveID,
"package", pkg.PackageName,
"vulnerable", vulnerable,
"minimumVersion", minVersion,
"minVersionOp", minVersionOp,
"maxVersion", maxVersion.TakeOr(""),
"maxVersionOp", maxVersionOp,
)
result = db.
Where(secfixes.CPEMatch{
VulnID: vuln.VulnID,
PackageID: pkg.PackageID,
CpeUri: cpe.Uri,
MinimumVersion: &minVersion,
MaximumVersion: maxVersion.UnwrapAsPtr(),
MinimumVersionOP: minVersionOp,
MaximumVersionOP: maxVersionOp,
}).
Assign(cpeMatch).
FirstOrCreate(&cpeMatch)
if result.Error != nil {
slog.Error(
"coiuld not create or update cpematch",
"package", pkg.PackageName,
"cve", vuln.CveID,
"err", result.Error,
)
return o.None[secfixes.CPEMatch](), nil
}
return o.Some(cpeMatch), nil
}
func processVersionStandard(
version vulnrich.Version,
minVersion *string,
maxVersion *o.Option[string],
minVersionOp, maxVersionOp *string,
) (valid bool) {
version.LessThan.IfSome(func(v string) {
*minVersionOp = ">="
*maxVersionOp = "<"
*maxVersion = o.Some(v)
})
version.LessThanOrEqual.IfSome(func(v string) {
*minVersionOp = ">="
*maxVersionOp = "<="
*maxVersion = o.Some(v)
})
if (*maxVersionOp)[0] == '<' && (*minVersion == "-" || *minVersion == "*") {
*minVersion = "0"
}
if (*maxVersionOp)[0] == '<' && *minVersion == maxVersion.TakeOr("") {
*minVersion = "0"
}
if *minVersionOp == "==" && *maxVersionOp == "==" {
*maxVersion = o.Some(*minVersion)
}
if *minVersion == "-" || *minVersion == "*" {
// Cannot say anything useful about this version
return false
}
return true
}
func processVersionGithub(
version vulnrich.Version,
minVersion *string,
maxVersion *o.Option[string],
minVersionOp, maxVersionOp *string,
) (valid bool) {
firstLimit, secondLimit, hasLowerBound := strings.Cut(version.Version.String(), ", ")
firstLimitOp, firstLimitVer, _ := strings.Cut(firstLimit, " ")
if hasLowerBound {
secondLimitOp, secondLimitVer, _ := strings.Cut(secondLimit, " ")
*minVersion = firstLimitVer
*minVersionOp = firstLimitOp
*maxVersion = o.Some(secondLimitVer)
*maxVersionOp = secondLimitOp
} else {
*minVersion = "0"
*minVersionOp = ">="
*maxVersion = o.Some(firstLimitVer)
*maxVersionOp = firstLimitOp
}
return true
}
func ProcessCveRef(
db *gorm.DB,
config secfixes.Config,
vuln secfixes.Vulnerability,
ref vulnrich.Reference,
) error {
tag := OptionalFirst(ref.Tags).
TakeOr("")
tag = strings.TrimPrefix(tag, "x_refsource_")
vulnRef := secfixes.VulnerabilityReference{
VulnID: vuln.VulnID,
RefUri: ref.URL,
RefType: tag,
}
db.
Where(secfixes.VulnerabilityReference{
VulnID: vuln.VulnID,
RefUri: ref.URL,
}).
Assign(vulnRef).
FirstOrCreate(&vulnRef)
return db.Error
}
package importer
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/moznion/go-optional"
)
const NVDEndpoint = "https://services.nvd.nist.gov/rest/json/%s/2.0"
type NVDCVEResponse struct {
Format string `json:"format"`
Version string `json:"version"`
Timestamp string `json:"timestamp"`
Vulnerabilities []Vulnerability `json:"vulnerabilities"`
ResultsPerPage int `json:"resultsPerPage"`
StartIndex int `json:"startIndex"`
TotalResults int `json:"totalResults"`
}
type Vulnerability struct {
CVE CVE `json:"cve"`
}
type CVE struct {
ID string `json:"id"`
SourceIdentifier string `json:"sourceIdentifier"`
Published string `json:"published"`
VulnStatus string `json:"vulnStatus"`
Descriptions Descriptions `json:"descriptions"`
Metrics Metric `json:"metrics"`
Configurations []Configuration `json:"configurations"`
Weaknesses []Weakness `json:"weakness"`
References []Reference `json:"references"`
}
type Descriptions []Description
func (d Descriptions) SelectLang(lang string) optional.Option[Description] {
for _, description := range d {
if description.Lang == lang {
return optional.Some(description)
}
}
return optional.None[Description]()
}
type Description struct {
Lang string `json:"lang"`
Value string `json:"value"`
}
type CvssMetricsV31 []CvssMetricV31
func (c CvssMetricsV31) SelectByType(typ string) optional.Option[CvssMetricV31] {
for _, metric := range c {
if metric.Type == typ {
return optional.Some(metric)
}
}
return optional.None[CvssMetricV31]()
}
type Metric struct {
CvssMetricV31 CvssMetricsV31 `json:"cvssMetricV31"`
}
type CvssMetricV31 struct {
Source string `json:"source"`
Type string `json:"type"`
CvssData CvssData `json:"cvssData"`
}
type CvssData struct {
Version string `json:"version"`
VectorString string `json:"vectorString"`
AttackVector string `json:"attackVector"`
AttackComplexity string `json:"attackComplexity"`
PrivilegeRequired string `json:"privilegeRequired"`
UserInteraction string `json:"userInteraction"`
Scope string `json:"scope"`
ConfidentialityImpact string `json:"confidentialityImpact"`
IntegrityImpact string `json:"integrityImpact"`
AvailabilityImpact string `json:"availabilityImpact"`
BaseScore json.Number `json:"baseScore"`
BaseSeverity string `json:"baseSeverity"`
}
type Configuration struct {
Operator string `json:"operator"`
Nodes []Node `json:"nodes"`
}
type Node struct {
Operator string `json:"operator"`
CPEMatch []CPEMatch `json:"cpeMatch"`
Negate bool `json:"negate"`
}
type CPE23Uri struct {
Uri string
Part string
Vendor string
Product string
Version string
Update string
Edition string
Language string
SwEdition string
TargetSw string
TargetHw string
Other string
}
var _ json.Unmarshaler = (*CPE23Uri)(nil)
func (c *CPE23Uri) fromUri(uri string) error {
c.Uri = uri
if !strings.HasPrefix(uri, "cpe:2.3:") {
return fmt.Errorf("invalid format, must start with 'cpe:2.3:', received: '%s;", uri)
}
parts := strings.Split(uri, ":")
if len(parts) < 13 {
return fmt.Errorf("invalid format, must have 13 components, found %d components", len(parts))
}
c.Part = unquote(parts[2])
c.Vendor = unquote(parts[3])
c.Product = unquote(parts[4])
c.Version = unquote(parts[5])
c.Update = unquote(parts[6])
c.Edition = unquote(parts[7])
c.Language = unquote(parts[8])
c.SwEdition = unquote(parts[9])
c.TargetSw = unquote(parts[10])
c.TargetHw = unquote(parts[11])
c.Other = unquote(parts[12])
return nil
}
func NewCPEUri(uri string) (c CPE23Uri, err error) {
err = c.fromUri(uri)
return c, err
}
func (c *CPE23Uri) UnmarshalJSON(data []byte) error {
var uri string
err := json.Unmarshal(data, &uri)
if err != nil {
return err
}
if c == nil {
c = &CPE23Uri{}
}
if err := c.fromUri(uri); err != nil {
return fmt.Errorf("cpe23uri: could not unmarshal data: %w", err)
}
return nil
}
type CPEMatch struct {
Criteria CPE23Uri `json:"criteria"`
MatchCriteriaId string `json:"matchCriteriaId"`
VersionStartExcluding optional.Option[string] `json:"versionStartExcluding"`
VersionStartIncluding optional.Option[string] `json:"versionStartIncluding"`
VersionEndExcluding optional.Option[string] `json:"versionEndExcluding"`
VersionEndIncluding optional.Option[string] `json:"versionEndIncluding"`
Vulnerable bool `json:"vulnerable"`
}
func (c CPEMatch) UsesVersionRanges() bool {
if c.VersionStartExcluding.IsSome() {
return true
}
if c.VersionStartIncluding.IsSome() {
return true
}
if c.VersionEndExcluding.IsSome() {
return true
}
if c.VersionEndIncluding.IsSome() {
return true
}
return false
}
type Weakness struct {
Source string `json:"source"`
Type string `json:"type"`
Descriptions Descriptions `json:"descriptions"`
}
type Reference struct {
URL string `json:"url"`
Source string `json:"source"`
Tags []string `jons:"tags"`
}
type APIv2 struct {
Endpoint string
once sync.Once
}
func (a *APIv2) init() {
a.once.Do(func() {
if a.Endpoint == "" {
a.Endpoint = NVDEndpoint
}
})
}
type RequestOptionsFunc func(url.Values) error
func NoRejected() RequestOptionsFunc {
return func(q url.Values) error {
q.Set("noRejected", "")
return nil
}
}
func StartIndex(index int) RequestOptionsFunc {
return func(q url.Values) error {
q.Set("startIndex", strconv.Itoa(index))
return nil
}
}
func ResultsPerPage(nr int) RequestOptionsFunc {
return func(q url.Values) error {
q.Set("resultsPerPage", strconv.Itoa(nr))
return nil
}
}
func PubStart(date time.Time) RequestOptionsFunc {
return func(q url.Values) error {
q.Set("pubStartDate", date.Format(time.RFC3339))
if q.Get("pubEndDate") == "" {
q.Set("pubEndDate", date.Add(24*time.Hour).Format(time.RFC3339))
}
return nil
}
}
func PubEnd(date time.Time) RequestOptionsFunc {
return func(q url.Values) error {
q.Set("pubEndDate", date.Format(time.RFC3339))
if q.Get("pubStartDate") == "" {
q.Set("pubStartDate", date.Add(-24*time.Hour).Format(time.RFC3339))
}
return nil
}
}
func buildUrl(endpoint, api string, options []RequestOptionsFunc) (string, error) {
apiUrl, err := url.Parse(fmt.Sprintf(endpoint, api))
if err != nil {
return "", fmt.Errorf("failed to parse endpoint: %w", err)
}
query := url.Values{}
for _, option := range options {
err = option(query)
if err != nil {
return "", fmt.Errorf("failed to apply option: %w", err)
}
}
apiUrl.RawQuery = query.Encode()
return apiUrl.String(), nil
}
func (a *APIv2) GetCVEs(options ...RequestOptionsFunc) (*NVDCVEResponse, error) {
a.init()
requestUrl, err := buildUrl(a.Endpoint, "cves", options)
if err != nil {
return nil, fmt.Errorf("failed to build url: %w", err)
}
resp, err := http.Get(requestUrl)
if err != nil {
return nil, fmt.Errorf("failure in HTTP request: %w", err)
}
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
nvdResp := &NVDCVEResponse{}
err = decoder.Decode(nvdResp)
return nvdResp, err
}
func unquote(v string) string {
var unquoted strings.Builder
for _, r := range v {
if r == '\\' {
continue
}
unquoted.WriteRune(r)
}
return unquoted.String()
}
package importer
import (
"encoding/json"
"net/url"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBuildUrlReturnsValidUrl(t *testing.T) {
require := require.New(t)
result, err := buildUrl("https://example.com/api/%s/", "test", []RequestOptionsFunc{})
require.NoError(err, "unexpected error")
require.NotEmpty(result)
parsedUrl, err := url.Parse(result)
require.NoError(err)
require.Equal("example.com", parsedUrl.Host)
require.Equal("/api/test/", parsedUrl.Path)
require.Equal("https", parsedUrl.Scheme)
}
func TestBuildUrlNoRejected(t *testing.T) {
require := require.New(t)
result, err := buildUrl("https://example.com/api/%s/", "test", []RequestOptionsFunc{NoRejected()})
require.NoError(err)
url, err := url.Parse(result)
require.NoError(err)
query := url.Query()
require.Contains(query, "noRejected", "Query parementer noRejected should be present")
}
func TestBuildUrlPubStart(t *testing.T) {
require := require.New(t)
result, err := buildUrl(
"https://example.com/api/%s/",
"test",
[]RequestOptionsFunc{PubStart(time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC))},
)
require.NoError(err)
url, err := url.Parse(result)
require.NoError(err)
query := url.Query()
pubStart := query.Get("pubStartDate")
require.Equal("2023-08-01T00:00:00Z", pubStart)
}
func TestBuildUrlPubStartSetsPubEndIfMissing(t *testing.T) {
require := require.New(t)
result, err := buildUrl(
"https://example.com/api/%s/",
"test",
[]RequestOptionsFunc{PubStart(time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC))},
)
require.NoError(err)
url, err := url.Parse(result)
require.NoError(err)
query := url.Query()
pubEnd := query.Get("pubEndDate")
require.Equal("2023-08-02T00:00:00Z", pubEnd)
}
func TestBuildUrlPubStartDoesNotOverwriteExistingPubEnd(t *testing.T) {
require := require.New(t)
result, err := buildUrl(
"https://example.com/api/%s/",
"test",
[]RequestOptionsFunc{
PubEnd(time.Date(2023, 8, 7, 0, 0, 0, 0, time.UTC)),
PubStart(time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC)),
},
)
require.NoError(err)
url, err := url.Parse(result)
require.NoError(err)
query := url.Query()
pubEnd := query.Get("pubEndDate")
require.Equal("2023-08-07T00:00:00Z", pubEnd)
}
func TestBuildUrlPubEnd(t *testing.T) {
require := require.New(t)
result, err := buildUrl(
"https://example.com/api/%s/",
"test",
[]RequestOptionsFunc{PubEnd(time.Date(2023, 8, 2, 0, 0, 0, 0, time.UTC))},
)
require.NoError(err)
url, err := url.Parse(result)
require.NoError(err)
query := url.Query()
pubStart := query.Get("pubEndDate")
require.Equal("2023-08-02T00:00:00Z", pubStart)
}
func TestBuildUrlPubEndSetsPubEndIfMissing(t *testing.T) {
require := require.New(t)
result, err := buildUrl(
"https://example.com/api/%s/",
"test",
[]RequestOptionsFunc{PubEnd(time.Date(2023, 8, 2, 0, 0, 0, 0, time.UTC))},
)
require.NoError(err)
url, err := url.Parse(result)
require.NoError(err)
query := url.Query()
pubEnd := query.Get("pubStartDate")
require.Equal("2023-08-01T00:00:00Z", pubEnd)
}
func TestBuildUrlPubEndDoesNotOverwriteExistingPubEnd(t *testing.T) {
require := require.New(t)
result, err := buildUrl(
"https://example.com/api/%s/",
"test",
[]RequestOptionsFunc{
PubStart(time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC)),
PubEnd(time.Date(2023, 8, 5, 0, 0, 0, 0, time.UTC)),
},
)
require.NoError(err)
url, err := url.Parse(result)
require.NoError(err)
query := url.Query()
pubEnd := query.Get("pubStartDate")
require.Equal("2023-08-01T00:00:00Z", pubEnd)
}
func TestBuildUrlStartIndex(t *testing.T) {
require := require.New(t)
result, err := buildUrl("https://example.com/api/%s/", "test", []RequestOptionsFunc{StartIndex(1)})
require.NoError(err)
url, err := url.Parse(result)
require.NoError(err)
query := url.Query()
startIndex := query.Get("startIndex")
require.Equal("1", startIndex)
}
func TestBuildUrlResultsPerPage(t *testing.T) {
require := require.New(t)
result, err := buildUrl("https://example.com/api/%s/", "test", []RequestOptionsFunc{ResultsPerPage(200)})
require.NoError(err)
url, err := url.Parse(result)
require.NoError(err)
query := url.Query()
resultsPerPage := query.Get("resultsPerPage")
require.Equal("200", resultsPerPage)
}
func TestCPEUri(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
uri := "cpe:2.3:a:b:c:d:e:f:g:h:i:j:k"
cpeuri, err := NewCPEUri(uri)
require.NoError(err)
assert.Equal("a", cpeuri.Part, "part")
assert.Equal("b", cpeuri.Vendor, "vendor")
assert.Equal("c", cpeuri.Product, "product")
assert.Equal("d", cpeuri.Version, "version")
assert.Equal("e", cpeuri.Update, "update")
assert.Equal("f", cpeuri.Edition, "edition")
assert.Equal("g", cpeuri.Language, "language")
assert.Equal("h", cpeuri.SwEdition, "sw_edition")
assert.Equal("i", cpeuri.TargetSw, "target_sw")
assert.Equal("j", cpeuri.TargetHw, "target_hw")
assert.Equal("k", cpeuri.Other, "other")
}
func TestCPEUriUnmarshalJSON(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
cpeuri := CPE23Uri{}
data := []byte(`"cpe:2.3:a:b:c:d:e:f:g:h:i:j:k"`)
err := json.Unmarshal(data, &cpeuri)
require.NoError(err)
assert.Equal("a", cpeuri.Part, "part")
assert.Equal("b", cpeuri.Vendor, "vendor")
assert.Equal("c", cpeuri.Product, "product")
assert.Equal("d", cpeuri.Version, "version")
assert.Equal("e", cpeuri.Update, "update")
assert.Equal("f", cpeuri.Edition, "edition")
assert.Equal("g", cpeuri.Language, "language")
assert.Equal("h", cpeuri.SwEdition, "sw_edition")
assert.Equal("i", cpeuri.TargetSw, "target_sw")
assert.Equal("j", cpeuri.TargetHw, "target_hw")
assert.Equal("k", cpeuri.Other, "other")
}
package importer
import "github.com/moznion/go-optional"
// OptionalFirst returns an option.Some with the first element of a slice if
// available, otherwise an optional.None.
func OptionalFirst[S ~[]E, E any](s S) optional.Option[E] {
if len(s) > 0 {
return optional.Some(s[0])
} else {
return optional.None[E]()
}
}
// OptionalNonEmpty returns optional.Some if the provided slice contains any
// elements, otherwise optional.None
func OptionalNonEmpty[S ~[]E, E any](s S) optional.Option[S] {
if len(s) > 0 {
return optional.Some(s)
} else {
return optional.None[S]()
}
}
package importer
import (
"fmt"
"reflect"
"github.com/expr-lang/expr"
"github.com/expr-lang/expr/vm"
"gitlab.alpinelinux.org/alpine/security/secfixes-tracker/secfixes"
)
type RewriterEnv struct {
Vendor string `expr:"vendor"`
Product string `expr:"product"`
TargetSW string `expr:"target_sw"`
Version string `expr:"version"`
Cpe CPE23Uri `expr:"cpe"`
}
type compiledRewriter struct {
Predicate *vm.Program
RewriteRule *vm.Program
Field string
}
func NewCompiledRewriter(r secfixes.Rewriter) (cr compiledRewriter, err error) {
genericOpts := []expr.Option{
expr.Env(RewriterEnv{}),
expr.Function(
"fmt",
exprFmt,
new(func(string, string) string),
new(func([]any, string) string),
),
}
if r.Field != "" {
cr.Field = r.Field
} else {
cr.Field = "product"
}
predicateOpts := append(genericOpts,
expr.AsBool(),
)
cr.Predicate, err = expr.Compile(r.Predicate, predicateOpts...)
if err != nil {
return cr, fmt.Errorf("error compiling predicate: %w", err)
}
rewriterOpts := append(genericOpts,
expr.AsKind(reflect.String),
)
cr.RewriteRule, err = expr.Compile(r.RewriteRule, rewriterOpts...)
if err != nil {
return cr, fmt.Errorf("error compiling rewrite rule: %w", err)
}
return cr, err
}
func (c compiledRewriter) Rewrite(cpe CPE23Uri) CPE23Uri {
env := RewriterEnv{
Vendor: cpe.Vendor,
Product: cpe.Product,
TargetSW: cpe.TargetSw,
Version: cpe.Version,
Cpe: cpe,
}
predicate, _ := expr.Run(c.Predicate, env)
if !predicate.(bool) {
return cpe
}
result, _ := expr.Run(c.RewriteRule, env)
resultStr := result.(string)
switch c.Field {
case "product":
cpe.Product = resultStr
case "vendor":
cpe.Vendor = resultStr
case "version":
cpe.Version = resultStr
case "target_sw":
cpe.TargetSw = resultStr
}
return cpe
}
// exprFmt is an implementation of sprintf for expr. It takes the thing to be
// formatted as the first argument to make it possible to use with pipes. The
// first argument can either be a string, or a list of any value.
func exprFmt(params ...any) (any, error) {
switch arg1 := params[0].(type) {
case string:
return fmt.Sprintf(params[1].(string), arg1), nil
case []any:
return fmt.Sprintf(params[1].(string), arg1...), nil
default:
return "", fmt.Errorf("unsupported type for argument 1: %T", arg1)
}
}
package vulnrich
import (
"encoding/json"
"fmt"
"strconv"
"github.com/moznion/go-optional"
)
//go:generate go run github.com/abice/go-enum@v0.6.0 --marshal
type Stringable string
var _ json.Unmarshaler = (*Stringable)(nil)
func (s *Stringable) UnmarshalJSON(b []byte) error {
var value string
err := json.Unmarshal(b, &value)
if err == nil {
*s = Stringable(value)
return nil
}
var valueInt int
err = json.Unmarshal(b, &valueInt)
if err != nil {
return fmt.Errorf("stringable: cannot interpret version as string or int: %w", err)
}
*s = Stringable(strconv.Itoa(valueInt))
return nil
}
func (s Stringable) String() string {
return string(s)
}
type RejectedReasons struct {
Lang string `json:"lang"`
Value string `json:"value"`
}
type ProviderMetadata struct {
DateUpdated DateTime `json:"dateUpdated"`
OrgID string `json:"orgId"`
ShortName string `json:"shortName"`
}
// ENUM(affected, unaffected, unknown)
type AffectedStatus string
type Version struct {
LessThan optional.Option[string] `json:"lessThan"`
LessThanOrEqual optional.Option[string] `json:"lessThanOrEqual"`
Status optional.Option[AffectedStatus] `json:"status"`
Version Stringable `json:"version"`
VersionType optional.Option[string] `json:"versionType"`
}
type Affected struct {
CPEs []string `json:"cpes"`
Product string `json:"product"`
Vendor string `json:"vendor"`
DefaultStatus optional.Option[AffectedStatus] `json:"defaultStatus"`
Versions []Version `json:"versions"`
}
type CveDescription struct {
Lang string `json:"lang"`
Value string `json:"value"`
}
type CveDescriptions []CveDescription
func (c CveDescriptions) ForLang(lang string) optional.Option[CveDescription] {
for _, descr := range c {
if descr.Lang == lang {
return optional.Some(descr)
}
}
return optional.None[CveDescription]()
}
type ProblemTypeDescription struct {
Description string `json:"description"`
Lang string `json:"lang"`
Type string `json:"type"`
CweID string `json:"cwiId"`
}
type ProblemType struct {
Descriptions []ProblemTypeDescription `json:"descriptions"`
}
type Reference struct {
URL string `json:"url"`
Name string `json:"name,omitempty"`
Tags []string `json:"tags"`
}
type CVEDataMeta struct {
Assigner string `json:"ASSIGNER"`
ID string `json:"ID"`
State string `json:"STATE"`
}
type DescriptionData struct {
Lang string `json:"lang"`
Value string `json:"value"`
}
type Description struct {
DescriptionData []DescriptionData `json:"description_data"`
}
type ProblemDescription struct {
Lang string `json:"lang"`
Value string `json:"value"`
}
type ProblemtypeData struct {
Description []ProblemDescription `json:"description"`
}
type Problemtype struct {
ProblemtypeData []ProblemtypeData `json:"problemtype_data"`
}
type Options struct {
Exploitation string `json:"Exploitation,omitempty"`
Automatable string `json:"Automatable,omitempty"`
TechnicalImpact string `json:"Technical Impact,omitempty"`
}
type Content struct {
Timestamp Timestamp `json:"timestamp"`
ID string `json:"id"`
Role string `json:"role"`
Version string `json:"version"`
Options []Options `json:"options"`
}
// ENUM(NONE, LOW, MEDIUM, HIGH, CRITICAL)
type CvssSeverity string
type CvssMetric struct {
Version string `json:"version"`
VectorString string `json:"vectorString"`
BaseScore json.Number `json:"baseScore"`
BaseSeverity CvssSeverity `json:"baseSeverity"`
}
type Metric struct {
Format string `json:"format"`
CvssV20 optional.Option[CvssMetric] `json:"cvssV2_0"`
CvssV30 optional.Option[CvssMetric] `json:"cvssV3_0"`
CvssV31 optional.Option[CvssMetric] `json:"cvssV3_1"`
CvssV40 optional.Option[CvssMetric] `json:"cvssV4_0"`
Other optional.Option[json.RawMessage] `json:"other"`
}
type CveContainer struct {
ProviderMetadata ProviderMetadata `json:"providerMetadata"`
DateAssigned DateTime `json:"dateAssigned"`
DatePublic DateTime `json:"datePublic"`
Title optional.Option[string] `json:"title"`
Descriptions CveDescriptions `json:"descriptions"`
Affected []Affected `json:"affected"`
ProblemTypes []ProblemType `json:"problemTypes"`
References []Reference `json:"references"`
Metrics []Metric `json:"metrics"`
RejectedReasons CveDescriptions `json:"rejectedReasons"`
}
type Containers struct {
Cna CveContainer `json:"cna"`
Adp []CveContainer `json:"adp"`
}
// ENUM(PUBLISHED, REJECTED)
type CveState string
type CveMetadata struct {
DateUpdated DateTime `json:"dateUpdated"`
DateReserved DateTime `json:"dateReserved"`
DatePublished DateTime `json:"datePublished"`
DateRejected DateTime `json:"dateRejected"`
CveID string `json:"cveId"`
AssignerOrgID string `json:"assignerOrgId"`
AssignerShortName string `json:"assignerShortName"`
RequesterUserID string `json:"requesterUserId"`
State CveState `json:"state"`
Serial int `json:"serial"`
}
type Record struct {
CveMetadata CveMetadata `json:"cveMetadata"`
DataType string `json:"dataType"`
DataVersion string `json:"dataVersion"`
Containers Containers `json:"containers"`
}
type Records []Record
func (r Records) Filter(predicate func(Record) bool) (result Records) {
for _, record := range r {
if predicate(record) {
result = append(result, record)
}
}
return
}
// Code generated by go-enum DO NOT EDIT.
// Version:
// Revision:
// Build Date:
// Built By:
package vulnrich
import (
"errors"
"fmt"
)
const (
// AffectedStatusAffected is a AffectedStatus of type affected.
AffectedStatusAffected AffectedStatus = "affected"
// AffectedStatusUnaffected is a AffectedStatus of type unaffected.
AffectedStatusUnaffected AffectedStatus = "unaffected"
// AffectedStatusUnknown is a AffectedStatus of type unknown.
AffectedStatusUnknown AffectedStatus = "unknown"
)
var ErrInvalidAffectedStatus = errors.New("not a valid AffectedStatus")
// String implements the Stringer interface.
func (x AffectedStatus) String() string {
return string(x)
}
// IsValid provides a quick way to determine if the typed value is
// part of the allowed enumerated values
func (x AffectedStatus) IsValid() bool {
_, err := ParseAffectedStatus(string(x))
return err == nil
}
var _AffectedStatusValue = map[string]AffectedStatus{
"affected": AffectedStatusAffected,
"unaffected": AffectedStatusUnaffected,
"unknown": AffectedStatusUnknown,
}
// ParseAffectedStatus attempts to convert a string to a AffectedStatus.
func ParseAffectedStatus(name string) (AffectedStatus, error) {
if x, ok := _AffectedStatusValue[name]; ok {
return x, nil
}
return AffectedStatus(""), fmt.Errorf("%s is %w", name, ErrInvalidAffectedStatus)
}
// MarshalText implements the text marshaller method.
func (x AffectedStatus) MarshalText() ([]byte, error) {
return []byte(string(x)), nil
}
// UnmarshalText implements the text unmarshaller method.
func (x *AffectedStatus) UnmarshalText(text []byte) error {
tmp, err := ParseAffectedStatus(string(text))
if err != nil {
return err
}
*x = tmp
return nil
}
const (
// CveStatePUBLISHED is a CveState of type PUBLISHED.
CveStatePUBLISHED CveState = "PUBLISHED"
// CveStateREJECTED is a CveState of type REJECTED.
CveStateREJECTED CveState = "REJECTED"
)
var ErrInvalidCveState = errors.New("not a valid CveState")
// String implements the Stringer interface.
func (x CveState) String() string {
return string(x)
}
// IsValid provides a quick way to determine if the typed value is
// part of the allowed enumerated values
func (x CveState) IsValid() bool {
_, err := ParseCveState(string(x))
return err == nil
}
var _CveStateValue = map[string]CveState{
"PUBLISHED": CveStatePUBLISHED,
"REJECTED": CveStateREJECTED,
}
// ParseCveState attempts to convert a string to a CveState.
func ParseCveState(name string) (CveState, error) {
if x, ok := _CveStateValue[name]; ok {
return x, nil
}
return CveState(""), fmt.Errorf("%s is %w", name, ErrInvalidCveState)
}
// MarshalText implements the text marshaller method.
func (x CveState) MarshalText() ([]byte, error) {
return []byte(string(x)), nil
}
// UnmarshalText implements the text unmarshaller method.
func (x *CveState) UnmarshalText(text []byte) error {
tmp, err := ParseCveState(string(text))
if err != nil {
return err
}
*x = tmp
return nil
}
const (
// CvssSeverityNONE is a CvssSeverity of type NONE.
CvssSeverityNONE CvssSeverity = "NONE"
// CvssSeverityLOW is a CvssSeverity of type LOW.
CvssSeverityLOW CvssSeverity = "LOW"
// CvssSeverityMEDIUM is a CvssSeverity of type MEDIUM.
CvssSeverityMEDIUM CvssSeverity = "MEDIUM"
// CvssSeverityHIGH is a CvssSeverity of type HIGH.
CvssSeverityHIGH CvssSeverity = "HIGH"
// CvssSeverityCRITICAL is a CvssSeverity of type CRITICAL.
CvssSeverityCRITICAL CvssSeverity = "CRITICAL"
)
var ErrInvalidCvssSeverity = errors.New("not a valid CvssSeverity")
// String implements the Stringer interface.
func (x CvssSeverity) String() string {
return string(x)
}
// IsValid provides a quick way to determine if the typed value is
// part of the allowed enumerated values
func (x CvssSeverity) IsValid() bool {
_, err := ParseCvssSeverity(string(x))
return err == nil
}
var _CvssSeverityValue = map[string]CvssSeverity{
"NONE": CvssSeverityNONE,
"LOW": CvssSeverityLOW,
"MEDIUM": CvssSeverityMEDIUM,
"HIGH": CvssSeverityHIGH,
"CRITICAL": CvssSeverityCRITICAL,
}
// ParseCvssSeverity attempts to convert a string to a CvssSeverity.
func ParseCvssSeverity(name string) (CvssSeverity, error) {
if x, ok := _CvssSeverityValue[name]; ok {
return x, nil
}
return CvssSeverity(""), fmt.Errorf("%s is %w", name, ErrInvalidCvssSeverity)
}
// MarshalText implements the text marshaller method.
func (x CvssSeverity) MarshalText() ([]byte, error) {
return []byte(string(x)), nil
}
// UnmarshalText implements the text unmarshaller method.
func (x *CvssSeverity) UnmarshalText(text []byte) error {
tmp, err := ParseCvssSeverity(string(text))
if err != nil {
return err
}
*x = tmp
return nil
}
package vulnrich
import (
"encoding/json"
"time"
)
func UnmarshalJsonTimeFormat(b []byte, formats ...string) (t time.Time, err error) {
var timeUnmarshalled string
err = json.Unmarshal(b, &timeUnmarshalled)
if err != nil {
return time.Time{}, err
}
var parsed time.Time
for _, format := range formats {
parsed, err = time.Parse(format, timeUnmarshalled)
if err == nil {
break
}
}
if err != nil {
return time.Time{}, err
}
return parsed, nil
}
type DateTime struct {
time.Time
}
var _ json.Unmarshaler = (*DateTime)(nil)
func (t *DateTime) UnmarshalJSON(b []byte) error {
formats := []string{
"2006-01-02T15:04:05",
"2006-01-02T15:04:05Z07:00",
}
tm, err := UnmarshalJsonTimeFormat(b, formats...)
if err != nil {
return err
}
*t = DateTime{tm}
return nil
}
type Timestamp struct {
time.Time
}
var _ json.Unmarshaler = (*Timestamp)(nil)
func (t *Timestamp) UnmarshalJSON(b []byte) error {
tm, err := UnmarshalJsonTimeFormat(b, "2006-01-02T15:04:05.999999Z07:00")
if err != nil {
return err
}
*t = Timestamp{tm}
return nil
}