diff --git a/go.mod b/go.mod index b53ed95db0..90a58c13ee 100644 --- a/go.mod +++ b/go.mod @@ -84,15 +84,20 @@ require github.com/getsentry/sentry-go/otel v0.44.1 require github.com/hashicorp/golang-lru/v2 v2.0.7 -require go.starlark.net v0.0.0-20260630144053-529d8e869a14 +require ( + github.com/github/smimesign v0.2.0 + go.starlark.net v0.0.0-20260630144053-529d8e869a14 +) require ( github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.32 // indirect github.com/go-openapi/runtime/server-middleware v0.30.0 // indirect - github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect + github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/modelcontextprotocol/go-sdk v1.7.0 // indirect github.com/oklog/ulid/v2 v2.1.1 // indirect + github.com/oleiade/reflections v1.1.0 // indirect github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/segmentio/encoding v0.5.4 // indirect github.com/theupdateframework/go-tuf v0.7.0 // indirect @@ -104,7 +109,7 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect - github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/ProtonMail/go-crypto v1.4.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.19.30 // indirect @@ -138,14 +143,14 @@ require ( github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/danieljoos/wincred v1.2.3 // indirect github.com/dgraph-io/ristretto v1.0.0 // indirect - github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect - github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect + github.com/digitorus/pkcs7 v0.0.0-20250730155240-ffadbf3f398c // indirect + github.com/digitorus/timestamp v0.0.0-20250524132541-c45532741eea // indirect github.com/docker/cli v29.6.0+incompatible // indirect - github.com/docker/docker-credential-helpers v0.9.3 // indirect + github.com/docker/docker-credential-helpers v0.9.5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect github.com/ebitengine/purego v0.10.0 // indirect - github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/extism/go-sdk v1.7.0 // indirect @@ -155,7 +160,7 @@ require ( github.com/getsentry/sentry-go v0.47.0 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/analysis v0.25.2 // indirect github.com/go-openapi/errors v0.22.8 // indirect github.com/go-openapi/jsonpointer v0.23.1 // indirect @@ -186,7 +191,7 @@ require ( github.com/golang/mock v1.7.0-rc.1 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/certificate-transparency-go v1.3.3 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/jsonschema-go v0.4.3 // indirect github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect @@ -199,11 +204,11 @@ require ( github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/kevinburke/ssh_config v1.6.0 // indirect github.com/klauspost/compress v1.18.7 // indirect github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/lucasb-eyer/go-colorful v1.4.0 // indirect - github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.21 // indirect @@ -243,7 +248,7 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761 // indirect github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect github.com/sergi/go-diff v1.4.0 // indirect @@ -256,8 +261,7 @@ require ( github.com/sigstore/sigstore-go v1.2.2 github.com/sigstore/timestamp-authority/v2 v2.1.2 // indirect github.com/sirupsen/logrus v1.9.4 // indirect - github.com/skeema/knownhosts v1.3.1 // indirect - github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/stretchr/objx v0.5.3 // indirect @@ -281,8 +285,8 @@ require ( github.com/yuin/gopher-lua v1.1.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.21.0 // indirect - go.opentelemetry.io/contrib/propagators/jaeger v1.21.1 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.40.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.40.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.15.1 // indirect go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect @@ -308,7 +312,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect k8s.io/apiextensions-apiserver v0.35.0 k8s.io/klog/v2 v2.140.0 // indirect - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect modernc.org/libc v1.72.1 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/go.sum b/go.sum index 429afcbd77..b991fbd4f3 100644 --- a/go.sum +++ b/go.sum @@ -49,8 +49,8 @@ github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= -github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= +github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= github.com/alicebob/miniredis/v2 v2.38.0 h1:nZAzCR+Lj+Vxk4ZXzm2NuKq2O33RXj1XxJ2e2uP9jiw= @@ -112,6 +112,7 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc= @@ -177,16 +178,16 @@ github.com/dgraph-io/ristretto v1.0.0/go.mod h1:jTi2FiYEhQ1NsMmA7DeBykizjOuY88Nh github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/digitorus/pkcs7 v0.0.0-20230713084857-e76b763bdc49/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= -github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 h1:ge14PCmCvPjpMQMIAH7uKg0lrtNSOdpYsRXlwk3QbaE= -github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= -github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 h1:lxmTCgmHE1GUYL7P0MlNa00M67axePTq+9nBSGddR8I= -github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y= +github.com/digitorus/pkcs7 v0.0.0-20250730155240-ffadbf3f398c h1:g349iS+CtAvba7i0Ee9EP1TlTZ9w+UncBY6HSmsFZa0= +github.com/digitorus/pkcs7 v0.0.0-20250730155240-ffadbf3f398c/go.mod h1:mCGGmWkOQvEuLdIRfPIpXViBfpWto4AhwtJlAvo62SQ= +github.com/digitorus/timestamp v0.0.0-20250524132541-c45532741eea h1:ALRwvjsSP53QmnN3Bcj0NpR8SsFLnskny/EIMebAk1c= +github.com/digitorus/timestamp v0.0.0-20250524132541-c45532741eea/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v29.6.0+incompatible h1:nw9himxMMZ7eIeherJNlKQq+acnlzGgHd+4uf10QRSc= github.com/docker/cli v29.6.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= -github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= +github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= +github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -199,8 +200,8 @@ github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= @@ -227,6 +228,8 @@ github.com/getsentry/sentry-go v0.47.0 h1:AnSMSyrYA5qZCIN/2xpgAAwv63sVULV+vBq37a github.com/getsentry/sentry-go v0.47.0/go.mod h1:h+b4VHpKnK7aUXB5wc+KDnPgp9ZtfliRD4eV85FbiSA= github.com/getsentry/sentry-go/otel v0.44.1 h1:RV2zUHEvGHJmCCpMaJ52tZZAlcbMgvtasQn/g3CcKKc= github.com/getsentry/sentry-go/otel v0.44.1/go.mod h1:CfzTxocQJ6JX4SLFvnBrGULBAARFAd1fHmbJCTQlOP4= +github.com/github/smimesign v0.2.0 h1:Hho4YcX5N1I9XNqhq0fNx0Sts8MhLonHd+HRXVGNjvk= +github.com/github/smimesign v0.2.0/go.mod h1:iZiiwNT4HbtGRVqCQu7uJPEZCuEE5sfSSttcnePkDl4= github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= @@ -260,8 +263,9 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-openapi/analysis v0.25.2 h1:I0vy4n3alz+DHTiN1PRhCb7QZxkK6g5YmswZKv2TKuw= github.com/go-openapi/analysis v0.25.2/go.mod h1:Uhs1t/2XR10EnwONYILGEzw8gcfGIG5Xk5K2AxnhqDo= github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I= @@ -369,9 +373,8 @@ github.com/google/cel-go v0.30.0 h1:ll54AkzKunWkBn9wSoiUXbFZXYZTkdJGNXTBXUoolGo= github.com/google/cel-go v0.30.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/certificate-transparency-go v1.3.3 h1:hq/rSxztSkXN2tx/3jQqF6Xc0O565UQPdHrOWvZwybo= github.com/google/certificate-transparency-go v1.3.3/go.mod h1:iR17ZgSaXRzSa5qvjFl8TnVD5h8ky2JMVio+dzoKMgA= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -481,8 +484,8 @@ github.com/jandelgado/gcov2lcov v1.0.5 h1:rkBt40h0CVK4oCb8Dps950gvfd1rYvQ8+cWa34 github.com/jandelgado/gcov2lcov v1.0.5/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss= 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/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= -github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= +github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7 h1:FWpSWRD8FbVkKQu8M1DM9jF5oXFLyE+XpisIYfdzbic= +github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7/go.mod h1:BMxO138bOokdgt4UaxZiEfypcSHX0t6SIFimVP1oRfk= github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= @@ -494,8 +497,8 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -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/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY= +github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= @@ -545,8 +548,8 @@ github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= +github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/luna-duclos/instrumentedsql v1.1.3/go.mod h1:9J1njvFds+zN7y85EDhN9XNQLANWwZt2ULeIC8yMNYs= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -581,8 +584,8 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -637,8 +640,8 @@ github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 h1:9bCMuD3Tc github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25/go.mod h1:eDjgYHYDJbPLBLsyZ6qRaugP0mX8vePOhZ5id1fdzJw= github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= -github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM= -github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60= +github.com/oleiade/reflections v1.1.0 h1:D+I/UsXQB4esMathlt0kkZRJZdUDmhv5zGi/HOwYTWo= +github.com/oleiade/reflections v1.1.0/go.mod h1:mCxx0QseeVCHs5Um5HhJeCKVC7AwS8kO67tky4rdisA= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= @@ -670,6 +673,7 @@ github.com/ory/jsonschema/v3 v3.0.8/go.mod h1:ZPzqjDkwd3QTnb2Z6PAS+OTvBE2x5i6m25 github.com/ory/x v0.0.665 h1:61vv0ObCDSX1vOQYbxBeqDiv4YiPmMT91lYxDaaKX08= github.com/ory/x v0.0.665/go.mod h1:7SCTki3N0De3ZpqlxhxU/94ZrOCfNEnXwVtd0xVt+L8= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pborman/getopt v0.0.0-20180811024354-2b5b3bfb099b/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc= github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= @@ -716,8 +720,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= -github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= +github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A= github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk= github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgmZlUv4= @@ -768,11 +772,9 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= -github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= -github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= @@ -905,10 +907,10 @@ go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0. go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1/go.mod h1:GnOaBaFQ2we3b9AGWJpsBa7v1S5RlQzlC3O7dRMxZhM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= -go.opentelemetry.io/contrib/propagators/b3 v1.21.0 h1:uGdgDPNzwQWRwCXJgw/7h29JaRqcq9B87Iv4hJDKAZw= -go.opentelemetry.io/contrib/propagators/b3 v1.21.0/go.mod h1:D9GQXvVGT2pzyTfp1QBOnD1rzKEWzKjjwu5q2mslCUI= -go.opentelemetry.io/contrib/propagators/jaeger v1.21.1 h1:f4beMGDKiVzg9IcX7/VuWVy+oGdjx3dNJ72YehmtY5k= -go.opentelemetry.io/contrib/propagators/jaeger v1.21.1/go.mod h1:U9jhkEl8d1LL+QXY7q3kneJWJugiN3kZJV2OWz3hkBY= +go.opentelemetry.io/contrib/propagators/b3 v1.40.0 h1:xariChe8OOVF3rNlfzGFgQc61npQmXhzZj/i82mxMfg= +go.opentelemetry.io/contrib/propagators/b3 v1.40.0/go.mod h1:72WvbdxbOfXaELEQfonFfOL6osvcVjI7uJEE8C2nkrs= +go.opentelemetry.io/contrib/propagators/jaeger v1.40.0 h1:aXl9uobjJs5vquMLt9ZkI/3zIuz8XQ3TqOKSWx0/xdU= +go.opentelemetry.io/contrib/propagators/jaeger v1.40.0/go.mod h1:ioMePqe6k6c/ovXSkmkMr1mbN5qRBGJxNTVop7/2XO0= go.opentelemetry.io/contrib/samplers/jaegerremote v0.15.1 h1:Qb+5A+JbIjXwO7l4HkRUhgIn4Bzz0GNS2q+qdmSx+0c= go.opentelemetry.io/contrib/samplers/jaegerremote v0.15.1/go.mod h1:G4vNCm7fRk0kjZ6pGNLo5SpLxAUvOfSrcaegnT8TPck= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= @@ -972,6 +974,7 @@ golang.ngrok.com/ngrok/v2 v2.1.4 h1:0JQZRqzVGBYluIi5MuhxNYx653qxpN7AiNwNJzoa9DQ= golang.ngrok.com/ngrok/v2 v2.1.4/go.mod h1:1bwK0+ZB4RJCJdqaXs2mvdsjeSk+x4YrrLn8IqOrIGo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1160,8 +1163,8 @@ k8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg= k8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= modernc.org/cc/v4 v4.28.1 h1:XpLbkYVQ24E8tX5u8+yWGvaxerxkR/S4zqxI8ZoSBuc= diff --git a/pkg/git/client.go b/pkg/git/client.go index cedd0fbe1a..b3240ab07f 100644 --- a/pkg/git/client.go +++ b/pkg/git/client.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "io" "log/slog" "path/filepath" "strings" @@ -16,6 +17,7 @@ import ( "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/cache" + "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/storage/filesystem" ) @@ -53,6 +55,10 @@ type HeadCommit struct { // whatever bytes the commit carries; callers must cryptographically // verify it before treating it as provenance. Signature string + // Payload is the encoded commit object without its signature — the + // exact bytes the signature signs. Verifiers check Signature over + // Payload. + Payload []byte } // DefaultGitClient implements Client using go-git @@ -277,8 +283,28 @@ func (*DefaultGitClient) HeadCommit(repoInfo *RepositoryInfo) (HeadCommit, error if err != nil { return HeadCommit{}, fmt.Errorf("failed to read HEAD commit: %w", err) } + payload, err := commitPayload(commit) + if err != nil { + return HeadCommit{}, fmt.Errorf("failed to encode HEAD commit payload: %w", err) + } return HeadCommit{ Hash: ref.Hash().String(), Signature: commit.PGPSignature, + Payload: payload, }, nil } + +// commitPayload returns the encoded commit object without its signature — +// the bytes a commit signature is computed over. +func commitPayload(commit *object.Commit) ([]byte, error) { + obj := &plumbing.MemoryObject{} + if err := commit.EncodeWithoutSignature(obj); err != nil { + return nil, err + } + r, err := obj.Reader() + if err != nil { + return nil, err + } + defer r.Close() //nolint:errcheck // in-memory reader + return io.ReadAll(r) +} diff --git a/pkg/git/client_test.go b/pkg/git/client_test.go index 729f5358db..ac8fb81742 100644 --- a/pkg/git/client_test.go +++ b/pkg/git/client_test.go @@ -140,6 +140,7 @@ func TestDefaultGitClient_HeadCommit_UnsignedCommit(t *testing.T) { assert.Len(t, head.Hash, 40, "commit hash should be 40 hex chars") assert.True(t, isAllHex(head.Hash), "commit hash should be all hex") assert.Empty(t, head.Signature, "an unsigned commit must yield an empty signature, not an error") + assert.Contains(t, string(head.Payload), "tree ", "the payload is present even for unsigned commits") } // isAllHex checks if s is a non-empty lowercase hex string. diff --git a/pkg/git/integration_test.go b/pkg/git/integration_test.go index 2bb24c5bab..7ab998f0a2 100644 --- a/pkg/git/integration_test.go +++ b/pkg/git/integration_test.go @@ -91,6 +91,10 @@ func TestDefaultGitClient_HeadCommit_SignedCommit(t *testing.T) { assert.Equal(t, armoredSig, head.Signature) assert.Equal(t, repoInfoHeadHash(t, repoInfo), head.Hash, "hash and signature must describe the same commit") + assert.NotContains(t, string(head.Payload), "gpgsig", + "the payload must be the commit without its signature header") + assert.Contains(t, string(head.Payload), "tree ", + "the payload must be the encoded commit object") } // TestDefaultGitClient_FullWorkflow exercises the complete clone → read → cleanup lifecycle diff --git a/pkg/skills/gitresolver/resolver.go b/pkg/skills/gitresolver/resolver.go index af167edb54..7cfdcb412d 100644 --- a/pkg/skills/gitresolver/resolver.go +++ b/pkg/skills/gitresolver/resolver.go @@ -50,6 +50,9 @@ type ResolveResult struct { // the install-time verifier checks it cryptographically before it is // trusted or recorded as provenance. CommitSignature string + // CommitPayload is the encoded commit object without its signature — + // the bytes CommitSignature signs. + CommitPayload []byte } // FileEntry represents a single file from the cloned repository. @@ -183,6 +186,7 @@ func (r *defaultResolver) Resolve(ctx context.Context, ref *GitReference) (*Reso Files: files, CommitHash: head.Hash, CommitSignature: head.Signature, + CommitPayload: head.Payload, }, nil } diff --git a/pkg/skills/gitresolver/resolver_test.go b/pkg/skills/gitresolver/resolver_test.go index 4381d0734c..a5018f4b40 100644 --- a/pkg/skills/gitresolver/resolver_test.go +++ b/pkg/skills/gitresolver/resolver_test.go @@ -357,6 +357,9 @@ func TestResolver_Resolve_SignedCommitSignaturePropagates(t *testing.T) { require.NotNil(t, result) assert.Equal(t, signedHash.String(), result.CommitHash) assert.Equal(t, armoredSig, result.CommitSignature) + assert.Contains(t, string(result.CommitPayload), "tree ", + "the signed payload must accompany the signature") + assert.NotContains(t, string(result.CommitPayload), "gpgsig") } func TestResolver_Resolve_MissingSkillMD(t *testing.T) { diff --git a/pkg/skills/lockfile/lockfile.go b/pkg/skills/lockfile/lockfile.go index dca6b24214..f1032ec678 100644 --- a/pkg/skills/lockfile/lockfile.go +++ b/pkg/skills/lockfile/lockfile.go @@ -99,6 +99,12 @@ type Provenance struct { RepositoryURI string `yaml:"repositoryUri,omitempty"` // SigstoreURL is the Sigstore instance the signature chains to. SigstoreURL string `yaml:"sigstoreUrl,omitempty"` + // Provisional marks provenance whose verification has a documented + // gap — currently git-commit signatures, verified for signature and + // certificate chain but without transparency-log proof of signing + // time. Visible in lock diffs so reduced assurance is never silent; + // removed when the gap closes. + Provisional bool `yaml:"provisional,omitempty"` } // Lockfile is the parsed contents of a project's toolhive.lock.yaml. diff --git a/pkg/skills/verifier/git.go b/pkg/skills/verifier/git.go new file mode 100644 index 0000000000..cb9c495071 --- /dev/null +++ b/pkg/skills/verifier/git.go @@ -0,0 +1,202 @@ +// SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package verifier + +import ( + "context" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "sync" + "time" + + cms "github.com/github/smimesign/ietf-cms" + "github.com/sigstore/sigstore-go/pkg/fulcio/certificate" + "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore-go/pkg/verify" + + coreverifier "github.com/stacklok/toolhive-core/container/verifier" + "github.com/stacklok/toolhive/pkg/skills/lockfile" +) + +// VerifyGit cryptographically verifies a gitsign commit signature: the CMS +// signature is checked over the commit payload and the signing certificate +// chain is verified against the Fulcio roots in toolhive-core's embedded +// trusted material — no network. A non-nil expected identity must match the +// certificate identity (post-hoc comparison: git signatures have no +// Sigstore bundle to bind a policy into); nil expected is trust on first +// use. +// +// The embedded Rekor transparency-log proof is NOT yet validated — signing +// time is checked against the certificate's own validity window, matching +// gitsign's certificate verifier. Rekor proof validation is a tracked +// follow-up (it requires reconstructing the proof from CMS unsigned +// attributes, which gitsign only exposes internally). +func (*Default) VerifyGit( + ctx context.Context, + payload, signature []byte, + expected *lockfile.Provenance, +) (*Result, error) { + if len(signature) == 0 { + return nil, fmt.Errorf("%w: commit is not signed", ErrUnsigned) + } + if len(payload) == 0 { + return nil, fmt.Errorf("%w: no commit payload to verify", ErrSignatureInvalid) + } + + roots, intermediates, err := fulcioPools() + if err != nil { + return nil, err + } + cert, err := verifyGitSignature(ctx, payload, signature, roots, intermediates) + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrSignatureInvalid, err) + } + + identity, err := gitIdentityFromCertificate(cert) + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrSignatureInvalid, err) + } + if expected != nil && !gitIdentityMatches(identity, expected) { + return nil, fmt.Errorf("%w: commit is signed by a different identity than %q", + ErrSignerMismatch, expected.SignerIdentity) + } + result := resultFromCore(identity, nil) + // No transparency-log proof is validated yet (see the doc comment), so + // git verification carries a documented assurance gap — marked so the + // lock file shows it rather than implying full verification. + result.Provisional = true + return result, nil +} + +// verifyGitSignature verifies the CMS (PKCS#7) signature gitsign attaches +// to commits: the armored signature is parsed, the message digest and +// signature are checked over the commit payload, and the signing +// certificate chain is verified against the given pools. It returns the +// certificate the signature was authenticated against. +// +// This mirrors gitsign's own CertVerifier (which wraps a fork of the same +// ietf-cms package) without importing the gitsign module, whose package +// graph compiles cosign and the cloud KMS SDKs into the binary. The +// verification time is anchored inside the signing certificate's own +// validity window — Fulcio certificates live for minutes, so "valid at +// time.Now" would reject every real signature; proving the actual signing +// time is the transparency log's job (tracked follow-up). +func verifyGitSignature( + _ context.Context, + payload, signature []byte, + roots, intermediates *x509.CertPool, +) (*x509.Certificate, error) { + // ietf-cms v0.2.0 mutates package-level state during BER decoding + // (protocol.encodeIndent), so concurrent ParseSignedData calls race. + // Verification is not hot-path — serialize instead of forking the + // library like gitsign did. + cmsMu.Lock() + defer cmsMu.Unlock() + + der := signature + if blk, _ := pem.Decode(signature); blk != nil { + der = blk.Bytes + } + sd, err := cms.ParseSignedData(der) + if err != nil { + return nil, fmt.Errorf("parsing CMS signature: %w", err) + } + certs, err := sd.GetCertificates() + if err != nil { + return nil, fmt.Errorf("reading signature certificates: %w", err) + } + leaf := leafCertificate(certs) + if leaf == nil { + return nil, errors.New("no signing certificate found in signature") + } + + chains, err := sd.VerifyDetached(payload, x509.VerifyOptions{ + Roots: roots, + Intermediates: intermediates, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + CurrentTime: leaf.NotBefore.Add(time.Minute), + }) + if err != nil { + return nil, fmt.Errorf("verifying signature: %w", err) + } + // Return the leaf of the first verified chain — the certificate the + // CMS verifier actually authenticated the signature against. + if len(chains) == 0 || len(chains[0]) == 0 || len(chains[0][0]) == 0 { + return nil, errors.New("no verified certificate chains returned") + } + return chains[0][0][0], nil +} + +// cmsMu serializes ietf-cms parsing/verification; see verifyGitSignature. +var cmsMu sync.Mutex + +// leafCertificate picks the end-entity certificate from a CMS certificate +// bag (the one that is not a CA), falling back to the first entry. +func leafCertificate(certs []*x509.Certificate) *x509.Certificate { + for _, c := range certs { + if !c.IsCA { + return c + } + } + if len(certs) > 0 { + return certs[0] + } + return nil +} + +// fulcioPools builds root and intermediate certificate pools from the +// Fulcio certificate authorities in the embedded trusted material. +func fulcioPools() (roots, intermediates *x509.CertPool, err error) { + tm, err := coreverifier.OfflineTrustedMaterial() + if err != nil { + return nil, nil, fmt.Errorf("loading trusted material: %w", err) + } + roots = x509.NewCertPool() + intermediates = x509.NewCertPool() + for _, ca := range tm.FulcioCertificateAuthorities() { + fulcioCA, ok := ca.(*root.FulcioCertificateAuthority) + if !ok || fulcioCA.Root == nil { + continue + } + roots.AddCert(fulcioCA.Root) + for _, intermediate := range fulcioCA.Intermediates { + intermediates.AddCert(intermediate) + } + } + return roots, intermediates, nil +} + +// gitIdentityFromCertificate extracts the signer identity from a gitsign +// certificate, reusing core's identity normalization (GitHub Actions +// workflow paths, issuer extraction) by summarizing the certificate the +// same way sigstore-go does for bundle verification results. +func gitIdentityFromCertificate(cert *x509.Certificate) (coreverifier.Identity, error) { + summary, err := certificate.SummarizeCertificate(cert) + if err != nil { + return coreverifier.Identity{}, fmt.Errorf("summarizing certificate: %w", err) + } + return coreverifier.IdentityFromResult(&verify.VerificationResult{ + Signature: &verify.SignatureVerificationResult{Certificate: &summary}, + }) +} + +// gitIdentityMatches compares the observed certificate identity against the +// lock file's expected provenance. Unlike the OCI path there is no Sigstore +// policy to bind the identity into, so the comparison is explicit; empty +// expected fields are not wildcards — SignerIdentity and CertIssuer are +// always recorded together at trust-on-first-use. +func gitIdentityMatches(observed coreverifier.Identity, expected *lockfile.Provenance) bool { + if observed.SignerIdentity != expected.SignerIdentity { + return false + } + if observed.CertIssuer != expected.CertIssuer { + return false + } + if expected.RepositoryURI != "" && observed.SourceRepositoryURI != expected.RepositoryURI { + return false + } + return true +} diff --git a/pkg/skills/verifier/git_test.go b/pkg/skills/verifier/git_test.go new file mode 100644 index 0000000000..b230ecb097 --- /dev/null +++ b/pkg/skills/verifier/git_test.go @@ -0,0 +1,275 @@ +// SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package verifier + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "testing" + "time" + + cms "github.com/github/smimesign/ietf-cms" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + coreverifier "github.com/stacklok/toolhive-core/container/verifier" + "github.com/stacklok/toolhive/pkg/skills/lockfile" +) + +// fulcioIssuerOID is the Fulcio extension carrying the OIDC issuer +// (1.3.6.1.4.1.57264.1.1, the raw-string form gitsign certificates use). +var fulcioIssuerOID = []int{1, 3, 6, 1, 4, 1, 57264, 1, 1} + +const testGitIssuer = "https://accounts.example.com" + +// testCA is a synthetic Fulcio-style CA for signing test certificates. +type testCA struct { + cert *x509.Certificate + key *ecdsa.PrivateKey +} + +func newTestCA(t *testing.T) *testCA { + t.Helper() + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "test-fulcio-root"}, + NotBefore: time.Now().Add(-48 * time.Hour), + NotAfter: time.Now().Add(time.Hour), + IsCA: true, + KeyUsage: x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key) + require.NoError(t, err) + cert, err := x509.ParseCertificate(der) + require.NoError(t, err) + return &testCA{cert: cert, key: key} +} + +// issueSigningCert issues a short-lived Fulcio-style code-signing +// certificate with the given email SAN and OIDC issuer extension. +func (ca *testCA) issueSigningCert(t *testing.T, email string) (*x509.Certificate, *ecdsa.PrivateKey) { + t.Helper() + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(2), + EmailAddresses: []string{email}, + // Fulcio certificates live for minutes. + NotBefore: time.Now().Add(-time.Minute), + NotAfter: time.Now().Add(9 * time.Minute), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + ExtraExtensions: []pkix.Extension{ + {Id: fulcioIssuerOID, Value: []byte(testGitIssuer)}, + }, + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, ca.cert, key.Public(), ca.key) + require.NoError(t, err) + cert, err := x509.ParseCertificate(der) + require.NoError(t, err) + return cert, key +} + +// issueExpiredSigningCert issues a signing certificate whose validity +// window lies entirely in the past. +func (ca *testCA) issueExpiredSigningCert(t *testing.T, email string) (*x509.Certificate, *ecdsa.PrivateKey) { + t.Helper() + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(3), + EmailAddresses: []string{email}, + NotBefore: time.Now().Add(-24 * time.Hour), + NotAfter: time.Now().Add(-23 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + ExtraExtensions: []pkix.Extension{ + {Id: fulcioIssuerOID, Value: []byte(testGitIssuer)}, + }, + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, ca.cert, key.Public(), ca.key) + require.NoError(t, err) + cert, err := x509.ParseCertificate(der) + require.NoError(t, err) + return cert, key +} + +func (ca *testCA) pool() *x509.CertPool { + pool := x509.NewCertPool() + pool.AddCert(ca.cert) + return pool +} + +// signCommitPayload produces the armored CMS detached signature gitsign +// attaches to commits. +func signCommitPayload(t *testing.T, payload []byte, cert *x509.Certificate, key *ecdsa.PrivateKey) []byte { + t.Helper() + der, err := cms.SignDetached(payload, []*x509.Certificate{cert}, key) + require.NoError(t, err) + return pem.EncodeToMemory(&pem.Block{Type: "SIGNED MESSAGE", Bytes: der}) +} + +const testCommitPayload = "tree 0123456789abcdef0123456789abcdef01234567\n" + + "author Test 1700000000 +0000\n" + + "committer Test 1700000000 +0000\n\n" + + "Signed test commit\n" + +func TestVerifyGitSignatureRoundTrip(t *testing.T) { + t.Parallel() + ca := newTestCA(t) + cert, key := ca.issueSigningCert(t, "dev@example.com") + payload := []byte(testCommitPayload) + sig := signCommitPayload(t, payload, cert, key) + + got, err := verifyGitSignature(t.Context(), payload, sig, ca.pool(), x509.NewCertPool()) + require.NoError(t, err, "a signature chaining to the trusted root must verify") + assert.Equal(t, cert.SerialNumber, got.SerialNumber, + "the authenticated certificate must be the signing leaf") + + // The identity extracted from the certificate carries the SAN and the + // Fulcio issuer extension. + identity, err := gitIdentityFromCertificate(got) + require.NoError(t, err) + assert.Equal(t, "dev@example.com", identity.SignerIdentity) + assert.Equal(t, testGitIssuer, identity.CertIssuer) +} + +func TestVerifyGitSignatureRejects(t *testing.T) { + t.Parallel() + ca := newTestCA(t) + cert, key := ca.issueSigningCert(t, "dev@example.com") + payload := []byte(testCommitPayload) + sig := signCommitPayload(t, payload, cert, key) + + t.Run("tampered payload", func(t *testing.T) { + t.Parallel() + tampered := []byte(testCommitPayload + "tampered\n") + _, err := verifyGitSignature(t.Context(), tampered, sig, ca.pool(), x509.NewCertPool()) + require.Error(t, err, "a signature must not verify over modified commit content") + }) + + t.Run("untrusted root", func(t *testing.T) { + t.Parallel() + otherCA := newTestCA(t) + _, err := verifyGitSignature(t.Context(), payload, sig, otherCA.pool(), x509.NewCertPool()) + require.Error(t, err, "a chain to an untrusted root must not verify") + }) + + t.Run("garbage signature", func(t *testing.T) { + t.Parallel() + _, err := verifyGitSignature(t.Context(), payload, []byte("not a signature"), ca.pool(), x509.NewCertPool()) + require.Error(t, err) + }) +} + +// TestVerifyGitSignatureAcceptsExpiredCertificate pins the current time +// anchoring: verification time is the certificate's own validity window, so +// a certificate that expired long ago still verifies. This is the +// documented assurance gap of the git path (no transparency-log proof of +// signing time — provenance is marked Provisional for it). +// +// TODO(rekor): flip this expectation to require.Error once Rekor +// inclusion-proof validation lands and bounds the replay window. +func TestVerifyGitSignatureAcceptsExpiredCertificate(t *testing.T) { + t.Parallel() + ca := newTestCA(t) + cert, key := ca.issueExpiredSigningCert(t, "dev@example.com") + payload := []byte(testCommitPayload) + sig := signCommitPayload(t, payload, cert, key) + + _, err := verifyGitSignature(t.Context(), payload, sig, ca.pool(), x509.NewCertPool()) + require.NoError(t, err, + "expired certificates currently verify (validity-window anchoring); see TODO(rekor)") +} + +func TestVerifyGitInputGuards(t *testing.T) { + t.Parallel() + d := NewDefault(nil) + + _, err := d.VerifyGit(t.Context(), []byte("payload"), nil, nil) + require.ErrorIs(t, err, ErrUnsigned, "an empty signature is the unsigned case") + + _, err = d.VerifyGit(t.Context(), nil, []byte("sig"), nil) + require.ErrorIs(t, err, ErrSignatureInvalid, "a signature without payload cannot verify") + + // A syntactically broken signature fails as invalid (the embedded + // Fulcio pools load fine; parsing fails first). + _, err = d.VerifyGit(t.Context(), []byte("payload"), []byte("garbage"), nil) + require.ErrorIs(t, err, ErrSignatureInvalid) +} + +func TestGitIdentityMatches(t *testing.T) { + t.Parallel() + base := coreverifier.Identity{SignerIdentity: "dev@example.com", CertIssuer: testGitIssuer} + + tests := []struct { + name string + expected *lockfile.Provenance + want bool + }{ + { + name: "exact match", + expected: &lockfile.Provenance{SignerIdentity: "dev@example.com", CertIssuer: testGitIssuer}, + want: true, + }, + { + name: "identity mismatch", + expected: &lockfile.Provenance{SignerIdentity: "other@example.com", CertIssuer: testGitIssuer}, + want: false, + }, + { + name: "issuer mismatch", + expected: &lockfile.Provenance{SignerIdentity: "dev@example.com", CertIssuer: "https://other"}, + want: false, + }, + { + name: "repository mismatch", + expected: &lockfile.Provenance{ + SignerIdentity: "dev@example.com", + CertIssuer: testGitIssuer, + RepositoryURI: "https://github.com/org/repo", + }, + want: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.want, gitIdentityMatches(base, tc.expected)) + }) + } +} + +func TestVerifyGitEnforcesExpectedIdentity(t *testing.T) { + t.Parallel() + ca := newTestCA(t) + cert, key := ca.issueSigningCert(t, "dev@example.com") + payload := []byte(testCommitPayload) + sig := signCommitPayload(t, payload, cert, key) + + // Exercise the identity path through the internal helpers with the + // test CA pools (VerifyGit itself pins the embedded Fulcio roots, + // which a synthetic CA cannot chain to). + got, err := verifyGitSignature(t.Context(), payload, sig, ca.pool(), x509.NewCertPool()) + require.NoError(t, err) + identity, err := gitIdentityFromCertificate(got) + require.NoError(t, err) + + assert.True(t, gitIdentityMatches(identity, &lockfile.Provenance{ + SignerIdentity: "dev@example.com", + CertIssuer: testGitIssuer, + })) + assert.False(t, gitIdentityMatches(identity, &lockfile.Provenance{ + SignerIdentity: "attacker@example.com", + CertIssuer: testGitIssuer, + })) +} diff --git a/pkg/skills/verifier/mocks/mock_verifier.go b/pkg/skills/verifier/mocks/mock_verifier.go index 7110a2430b..4e757592b4 100644 --- a/pkg/skills/verifier/mocks/mock_verifier.go +++ b/pkg/skills/verifier/mocks/mock_verifier.go @@ -71,6 +71,21 @@ func (mr *MockVerifierMockRecorder) VerifyBundleOffline(bundle, digest, expected return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyBundleOffline", reflect.TypeOf((*MockVerifier)(nil).VerifyBundleOffline), bundle, digest, expected) } +// VerifyGit mocks base method. +func (m *MockVerifier) VerifyGit(ctx context.Context, payload, signature []byte, expected *lockfile.Provenance) (*verifier.Result, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyGit", ctx, payload, signature, expected) + ret0, _ := ret[0].(*verifier.Result) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// VerifyGit indicates an expected call of VerifyGit. +func (mr *MockVerifierMockRecorder) VerifyGit(ctx, payload, signature, expected any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyGit", reflect.TypeOf((*MockVerifier)(nil).VerifyGit), ctx, payload, signature, expected) +} + // VerifyOCI mocks base method. func (m *MockVerifier) VerifyOCI(ctx context.Context, imageRef, digest string, expected *lockfile.Provenance) (*verifier.Result, error) { m.ctrl.T.Helper() diff --git a/pkg/skills/verifier/types.go b/pkg/skills/verifier/types.go index 6008f633b7..c146e5e15f 100644 --- a/pkg/skills/verifier/types.go +++ b/pkg/skills/verifier/types.go @@ -28,6 +28,9 @@ type Result struct { RepositoryURI string // SigstoreURL is the transparency log instance used for verification. SigstoreURL string + // Provisional marks a verification with a documented assurance gap + // (git signatures until Rekor proof validation lands). + Provisional bool // Bundle is the serialized Sigstore bundle for offline re-verification. Bundle []byte } @@ -44,6 +47,7 @@ func (r *Result) ToLockProvenance() *lockfile.Provenance { CertIssuer: r.CertIssuer, RepositoryURI: r.RepositoryURI, SigstoreURL: r.SigstoreURL, + Provisional: r.Provisional, } } diff --git a/pkg/skills/verifier/verifier.go b/pkg/skills/verifier/verifier.go index 05299ed180..22f0c8b2f9 100644 --- a/pkg/skills/verifier/verifier.go +++ b/pkg/skills/verifier/verifier.go @@ -41,6 +41,13 @@ type Verifier interface { // Key-signed bundles carry no certificate identity: trust is the key. VerifyOCIWithKey(ctx context.Context, imageRef, digest string, pubKeyPEM []byte) (*Result, error) + // VerifyGit cryptographically verifies a gitsign commit signature over + // the commit payload against the embedded Fulcio roots. A non-nil + // expected identity must match the certificate identity; nil expected + // is the trust-on-first-use case. Returns ErrUnsigned for an empty + // signature. + VerifyGit(ctx context.Context, payload, signature []byte, expected *lockfile.Provenance) (*Result, error) + // VerifyBundleOffline re-verifies a stored bundle against the artifact // digest ("sha256:") without network access, enforcing expected // like VerifyOCI. diff --git a/pkg/skills/verifier/verifier_test.go b/pkg/skills/verifier/verifier_test.go index e6e771ef55..e4ab8b9a22 100644 --- a/pkg/skills/verifier/verifier_test.go +++ b/pkg/skills/verifier/verifier_test.go @@ -187,6 +187,20 @@ func TestToLockProvenance(t *testing.T) { result: &Result{Signed: true, SigstoreURL: sigstorePublicGoodRekorURL}, want: nil, }, + { + name: "provisional result marks the lock provenance", + result: &Result{ + Signed: true, + SignerIdentity: "dev@example.com", + CertIssuer: "https://accounts.example.com", + Provisional: true, + }, + want: &lockfile.Provenance{ + SignerIdentity: "dev@example.com", + CertIssuer: "https://accounts.example.com", + Provisional: true, + }, + }, { name: "identity-bearing result maps all fields", result: &Result{