Skip to content

Commit 43795fd

Browse files
authored
Merge branch 'main' into fix/kwok-registry-image-mirror
2 parents b7cbaf3 + 8068cd4 commit 43795fd

11 files changed

Lines changed: 139 additions & 24 deletions

File tree

.github/workflows/renovate.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ jobs:
105105
# Keep this digest in lockstep with RENOVATE_VALIDATOR_IMAGE in
106106
# the Makefile.
107107
- name: Run Renovate
108-
uses: renovatebot/github-action@b50d2ba2bd928235abdcc14d06dfafc217f1c565 # v46.1.18
108+
uses: renovatebot/github-action@22e0a16091fc706b04affe6ae53d5e3358ac4023 # v46.1.19
109109
with:
110110
renovate-version: '43@sha256:00185c0d63462acec8331cc9a94dcd74a763f2765fca0edcc3ff568af1dc8104'
111111
env:

.openvex.json

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"@id": "https://github.com/NVIDIA/aicr/.openvex.json",
44
"author": "NVIDIA AICR maintainers",
55
"role": "document creator",
6-
"timestamp": "2026-07-09T00:00:00Z",
7-
"version": 6,
6+
"timestamp": "2026-07-13T00:00:00Z",
7+
"version": 7,
88
"tooling": "manual; aiperf-bench statements verified against aiperf v0.7.0 source; aicr statement reachability verified by source inspection (CGO-free ko build, no libssl linkage); aicr-gate statements suppress CVEs in the embedded upstream kyverno/chainsaw binary (affected packages identified via vuln.go.dev), justified by chainsaw's ephemeral cluster-internal readiness-gate usage",
99
"statements": [
1010
{
@@ -306,6 +306,29 @@
306306
"justification": "vulnerable_code_not_in_execute_path",
307307
"impact_statement": "The attack requires parsing a crafted (attacker-controlled) XML document through xml.parsers.expat or xml.etree.ElementTree to trigger hash flooding. AICR invokes aiperf-bench exclusively as `aiperf profile <text-LLM> --url <endpoint>` (validators/performance/inference_perf_constraint.go). That subcommand communicates with the inference endpoint via JSON over HTTP only; all data transport uses aiohttp + orjson/msgspec. aiperf v0.7.0's declared dependencies contain no XML parsing libraries \u2014 the runtime stack (aiohttp, msgspec, orjson, numpy, transformers, uvicorn, fastapi) is entirely JSON/binary-based. While xml.parsers.expat is present in the Python 3.13 stdlib image, no attacker-controlled XML document can enter the aiperf profile execution path, making the hash-flooding trigger unreachable."
308308
},
309+
{
310+
"vulnerability": {
311+
"name": "CVE-2026-15308",
312+
"description": "cpython CPU-exhaustion DoS in html.parser.HTMLParser incremental feed() via repeated unterminated markup declarations (PSF-2026-33, CWE-407, High CVSS 7.5/8.7). Fix merged to the cpython 3.13 branch on 2026-07-04 (PR #153040) but as of 2026-07-13 no released CPython contains it — v3.13.14 (2026-06-10), v3.14.6, and v3.15.0b3 all predate the fix, so no base-image bump can remediate yet."
313+
},
314+
"products": [
315+
{
316+
"@id": "pkg:oci/aicr-aiperf-bench",
317+
"identifiers": {
318+
"purl": "pkg:oci/aicr-aiperf-bench"
319+
}
320+
},
321+
{
322+
"@id": "pkg:oci/aiperf-bench",
323+
"identifiers": {
324+
"purl": "pkg:oci/aiperf-bench"
325+
}
326+
}
327+
],
328+
"status": "not_affected",
329+
"justification": "vulnerable_code_not_in_execute_path",
330+
"impact_statement": "The trigger requires incrementally feeding attacker-controlled HTML containing repeated unterminated markup declarations into html.parser.HTMLParser.feed(). aiperf v0.7.0 source contains zero references to html.parser, HTMLParser, or any html stdlib import (verified with `grep -rn -E 'html\\.parser|HTMLParser|^(import|from) html'` against the PyPI sdist — no hits). AICR invokes aiperf-bench exclusively as `aiperf profile <text-LLM> --url <endpoint>` (validators/performance/inference_perf_constraint.go); the workload exchanges only JSON over HTTP via aiohttp + msgspec/orjson and never parses HTML documents from the inference endpoint or any other source. No attacker-controlled markup can reach the vulnerable parser, so the quadratic-complexity code path is unreachable."
331+
},
309332
{
310333
"vulnerability": {
311334
"name": "CVE-2026-45447",

.settings.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ testing_tools:
6464
# renovate: datasource=github-releases depName=tilt-dev/tilt depType=testing_tools
6565
tilt: '0.37.5'
6666
# renovate: datasource=github-releases depName=helm/helm depType=testing_tools
67-
helm: 'v4.2.2'
67+
helm: 'v4.2.3'
6868
# renovate: datasource=github-releases depName=databus23/helm-diff depType=testing_tools
6969
helm_diff: 'v3.15.10'
7070
# renovate: datasource=github-releases depName=helmfile/helmfile depType=testing_tools
@@ -91,14 +91,14 @@ testing_tools:
9191
yq: 'v4.53.3'
9292
# AWS CLI used by the KMS e2e to provision keys against the MiniStack emulator.
9393
# renovate: datasource=pypi depName=awscli depType=testing_tools
94-
awscli: '1.45.41'
94+
awscli: '1.45.45'
9595
# MiniStack: MIT-licensed, token-free AWS emulator (https://ministack.org) used
9696
# by the KMS e2e in place of LocalStack, whose latest/stable images now refuse
9797
# to boot without a paid license token. Pinned (never :latest) for reproducibility.
9898
# Run with USE_SSL=1: sigstore's awskms signer hardcodes https://, so the e2e
9999
# serves TLS with a mkcert cert (see mkcert below).
100100
# renovate: datasource=docker depName=ministackorg/ministack depType=testing_tools
101-
ministack_image: 'ministackorg/ministack:1.3.72'
101+
ministack_image: 'ministackorg/ministack:1.4.1'
102102
# mkcert issues a trusted localhost cert for MiniStack's TLS and installs its CA
103103
# into the system trust store, so the Go AWS SDK accepts the awskms:// endpoint.
104104
# renovate: datasource=github-releases depName=FiloSottile/mkcert depType=testing_tools
@@ -131,7 +131,7 @@ testing_tools:
131131
# kustomize-controller and helm-controller are the only controllers the
132132
# bundle consumes (OCIRepository -> Kustomization -> HelmRelease).
133133
# renovate: datasource=github-releases depName=fluxcd/flux2 depType=testing_tools
134-
flux_version: 'v2.9.0'
134+
flux_version: 'v2.9.1'
135135
# In-cluster Git server for the KWOK flux-git deployer lane (issue #963).
136136
# Rootless variant so the admin-user bootstrap can `kubectl exec gitea
137137
# admin user create` directly without su gymnastics.
@@ -143,7 +143,7 @@ testing_tools:
143143
# renovate: datasource=github-releases depName=hauler-dev/hauler depType=testing_tools
144144
hauler: 'v2.0.1'
145145
# renovate: datasource=github-releases depName=zarf-dev/zarf depType=testing_tools
146-
zarf: 'v0.80.0'
146+
zarf: 'v0.81.0'
147147
# Quality Thresholds
148148
quality:
149149
coverage_threshold: '75'

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ require (
3535
k8s.io/apimachinery v0.36.2
3636
k8s.io/client-go v0.36.2
3737
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3
38-
oras.land/oras-go/v2 v2.6.1
38+
oras.land/oras-go/v2 v2.6.2
3939
sigs.k8s.io/controller-runtime v0.24.1
4040
sigs.k8s.io/kustomize/api v0.21.1
4141
sigs.k8s.io/kustomize/kyaml v0.21.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,8 +603,8 @@ k8s.io/streaming v0.36.2 h1:NSKthPPg9UFSKsRauVJUVGH2Dvn8fhKmY4qrMkw/p98=
603603
k8s.io/streaming v0.36.2/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s=
604604
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE=
605605
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM=
606-
oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs=
607-
oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk=
606+
oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo=
607+
oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4=
608608
sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4=
609609
sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw=
610610
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=

vendor/modules.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1694,7 +1694,7 @@ k8s.io/utils/internal/third_party/forked/golang/net
16941694
k8s.io/utils/net
16951695
k8s.io/utils/ptr
16961696
k8s.io/utils/trace
1697-
# oras.land/oras-go/v2 v2.6.1
1697+
# oras.land/oras-go/v2 v2.6.2
16981698
## explicit; go 1.25.0
16991699
oras.land/oras-go/v2
17001700
oras.land/oras-go/v2/content

vendor/oras.land/oras-go/v2/content/file/utils.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,15 @@ func extractTarDirectory(dirPath, dirName string, r io.Reader, buf []byte, prese
175175
}
176176
filePath := filepath.Join(dirPath, filePathRel)
177177

178+
// resolveRelToBase only performs lexical and per-component Lstat checks,
179+
// which a chain of previously-extracted symlinks can bypass. Re-verify
180+
// containment with symlinks fully resolved before mutating the
181+
// filesystem, matching the check on the pushFile path.
182+
// (GHSA-m37j-52j7-pjw7)
183+
if err := checkSymlinkEscape(dirPath, filePath); err != nil {
184+
return err
185+
}
186+
178187
// Create content
179188
switch header.Typeflag {
180189
case tar.TypeReg:
@@ -188,6 +197,11 @@ func extractTarDirectory(dirPath, dirName string, r io.Reader, buf []byte, prese
188197
// This is a known limitation and will not be addressed.
189198
var target string
190199
if target, err = ensureLinkPath(dirPath, dirName, filePath, header.Linkname); err == nil {
200+
if !filepath.IsAbs(target) {
201+
// link(2) resolves relative paths against the process CWD, not
202+
// the link file's directory. Resolve explicitly to prevent escape.
203+
target = filepath.Join(filepath.Dir(filePath), target)
204+
}
191205
err = os.Link(target, filePath)
192206
}
193207
case tar.TypeSymlink:
@@ -276,6 +290,16 @@ func ensureLinkPath(baseAbs, baseRel, link, target string) (string, error) {
276290

277291
// writeFile writes content to the file specified by the `path` parameter.
278292
func writeFile(path string, r io.Reader, perm os.FileMode, buf []byte) (err error) {
293+
// os.OpenFile follows a terminal symlink, so a regular-file entry whose
294+
// path was already created as a symlink by an earlier archive entry would
295+
// be written through that link, landing outside the extraction root
296+
// (GHSA-m37j-52j7-pjw7). Remove any such symlink first so the content is
297+
// written to a regular file at path itself.
298+
if fi, err := os.Lstat(path); err == nil && fi.Mode()&os.ModeSymlink != 0 {
299+
if err := os.Remove(path); err != nil {
300+
return err
301+
}
302+
}
279303
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
280304
if err != nil {
281305
return err

vendor/oras.land/oras-go/v2/content/reader.go

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ limitations under the License.
1616
package content
1717

1818
import (
19+
"bytes"
1920
"errors"
2021
"fmt"
2122
"io"
@@ -24,11 +25,15 @@ import (
2425
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
2526
)
2627

27-
// maxDescriptorSize is the upper-bound for descriptor sizes accepted by
28-
// ReadAll. Descriptors sourced from attacker-supplied OCI layouts can carry
29-
// arbitrarily large Size values; without this cap, make([]byte, desc.Size)
30-
// triggers a runtime panic before any allocation occurs.
31-
const maxDescriptorSize = 32 * 1024 * 1024 // 32 MiB
28+
// maxInitialBufferSize bounds the buffer that ReadAll pre-allocates from
29+
// desc.Size before any content is read. desc.Size is attacker-controllable: a
30+
// crafted OCI layout index.json can declare an arbitrarily large Size (e.g.
31+
// 2^62), and make([]byte, desc.Size) on such a value triggers a runtime panic
32+
// ("makeslice: len out of range") before any allocation occurs. ReadAll caps
33+
// the initial allocation at this value and grows the buffer as it reads, so the
34+
// declared size is never trusted for allocation while legitimately large
35+
// content (e.g. plugin or chart layers) is still read in full.
36+
const maxInitialBufferSize = 32 * 1024 * 1024 // 32 MiB
3237

3338
var (
3439
// ErrInvalidDescriptorSize is returned by ReadAll() when
@@ -125,22 +130,32 @@ func NewVerifyReader(r io.Reader, desc ocispec.Descriptor) *VerifyReader {
125130
// The read content is verified against the size and the digest
126131
// using a VerifyReader.
127132
func ReadAll(r io.Reader, desc ocispec.Descriptor) ([]byte, error) {
128-
if desc.Size < 0 || desc.Size > maxDescriptorSize {
133+
if desc.Size < 0 {
129134
return nil, ErrInvalidDescriptorSize
130135
}
131-
buf := make([]byte, desc.Size)
132136

133137
vr := NewVerifyReader(r, desc)
134-
if n, err := io.ReadFull(vr, buf); err != nil {
138+
139+
// Do not pre-allocate desc.Size directly: it is attacker-controllable and a
140+
// forged value (e.g. 2^62) would panic make(). Cap the initial allocation
141+
// and let the buffer grow as content is read. The VerifyReader enforces the
142+
// declared size and digest, so a size that does not match the actual content
143+
// still fails verification rather than over-allocating.
144+
initialCap := desc.Size
145+
if initialCap > maxInitialBufferSize {
146+
initialCap = maxInitialBufferSize
147+
}
148+
buf := bytes.NewBuffer(make([]byte, 0, initialCap))
149+
if _, err := buf.ReadFrom(vr); err != nil {
135150
if errors.Is(err, io.ErrUnexpectedEOF) {
136-
return nil, fmt.Errorf("read failed: expected content size of %d, got %d, for digest %s: %w", desc.Size, n, desc.Digest.String(), err)
151+
return nil, fmt.Errorf("read failed: expected content size of %d, got %d, for digest %s: %w", desc.Size, buf.Len(), desc.Digest.String(), err)
137152
}
138153
return nil, fmt.Errorf("read failed: %w", err)
139154
}
140155
if err := vr.Verify(); err != nil {
141156
return nil, err
142157
}
143-
return buf, nil
158+
return buf.Bytes(), nil
144159
}
145160

146161
// ensureEOF ensures the read operation ends with an EOF and no

vendor/oras.land/oras-go/v2/errdef/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ var (
2626
ErrMissingReference = errors.New("missing reference")
2727
ErrNotFound = errors.New("not found")
2828
ErrSizeExceedsLimit = errors.New("size exceeds limit")
29+
ErrTooManyPages = errors.New("too many pages")
2930
ErrUnsupported = errors.New("unsupported")
3031
ErrUnsupportedVersion = errors.New("unsupported version")
3132
)

vendor/oras.land/oras-go/v2/registry/remote/repository.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,18 @@ type Repository struct {
127127
// Reference: https://github.com/oras-project/oras-go/issues/841
128128
ReferrerListPageSize int
129129

130+
// TagListMaxPages limits the total number of pages fetched during tag
131+
// listing, bounding server-driven pagination so a malicious or misbehaving
132+
// registry cannot force unbounded requests.
133+
// If zero, tag listing is unlimited.
134+
TagListMaxPages int
135+
136+
// ReferrerListMaxPages limits the total number of pages fetched during
137+
// referrer listing, bounding server-driven pagination so a malicious or
138+
// misbehaving registry cannot force unbounded requests.
139+
// If zero, referrer listing is unlimited.
140+
ReferrerListMaxPages int
141+
130142
// MaxMetadataBytes specifies a limit on how many response bytes are allowed
131143
// in the server's response to the metadata APIs, such as catalog list, tag
132144
// list, and referrers list.
@@ -205,6 +217,8 @@ func (r *Repository) clone() *Repository {
205217
ManifestMediaTypes: slices.Clone(r.ManifestMediaTypes),
206218
TagListPageSize: r.TagListPageSize,
207219
ReferrerListPageSize: r.ReferrerListPageSize,
220+
TagListMaxPages: r.TagListMaxPages,
221+
ReferrerListMaxPages: r.ReferrerListMaxPages,
208222
MaxMetadataBytes: r.MaxMetadataBytes,
209223
SkipReferrersGC: r.SkipReferrersGC,
210224
HandleWarning: r.HandleWarning,
@@ -400,7 +414,10 @@ func (r *Repository) Tags(ctx context.Context, last string, fn func(tags []strin
400414
ctx = auth.AppendRepositoryScope(ctx, r.Reference, auth.ActionPull)
401415
url := buildRepositoryTagListURL(r.PlainHTTP, r.Reference)
402416
var err error
403-
for err == nil {
417+
for page := 0; err == nil; page++ {
418+
if r.TagListMaxPages > 0 && page >= r.TagListMaxPages {
419+
return fmt.Errorf("tag listing exceeded %d pages: %w", r.TagListMaxPages, errdef.ErrTooManyPages)
420+
}
404421
url, err = r.tags(ctx, last, fn, url)
405422
// clear `last` for subsequent pages
406423
last = ""
@@ -512,7 +529,10 @@ func (r *Repository) referrersByAPI(ctx context.Context, desc ocispec.Descriptor
512529

513530
url := buildReferrersURL(r.PlainHTTP, ref, artifactType)
514531
var err error
515-
for err == nil {
532+
for page := 0; err == nil; page++ {
533+
if r.ReferrerListMaxPages > 0 && page >= r.ReferrerListMaxPages {
534+
return fmt.Errorf("referrer listing exceeded %d pages: %w", r.ReferrerListMaxPages, errdef.ErrTooManyPages)
535+
}
516536
url, err = r.referrersPageByAPI(ctx, artifactType, fn, url)
517537
}
518538
if err == errNoLink {

0 commit comments

Comments
 (0)