From 0ba9fdd8b09d9f7191fd44a32b7f6d6935f2ebbd Mon Sep 17 00:00:00 2001 From: Suneha Bose Date: Mon, 12 Oct 2026 10:55:11 -0700 Subject: [PATCH 1/4] chore: release v0.17.16 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f6ab8cb0..a52217f1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## [0.17.16] - 2026-10-12 + +- Security: validate and encode template variables to prevent instruction injection in generated artifacts ([GHSA-xqmq-h7rh-g4cq](https://github.com/Azure/draft/security/advisories/GHSA-xqmq-h7rh-g4cq)) + ## [0.17.14] - 2026-01-08 - Dependabot updates for dependencies and actions From b2a64826df529fabcebc3866ed1a07a2d3d25cb0 Mon Sep 17 00:00:00 2001 From: David Gamero Date: Mon, 12 Oct 2026 14:09:57 -0400 Subject: [PATCH 2/4] fix: accept registry ports in container image names --- .../workflows/integration-per-language.yml | 6 +++--- cmd/create_test.go | 2 +- pkg/config/validators/validators.go | 21 ++++++++++++++++++- pkg/config/validators/validators_test.go | 17 +++++++++++++++ .../azure-kubernetes-service-helm.yml | 2 +- .../azure-kubernetes-service-kustomize.yml | 2 +- .../workflows_github_helm_test.go | 2 +- .../workflows_github_kustomize_test.go | 2 +- 8 files changed, 45 insertions(+), 9 deletions(-) diff --git a/.github/workflows/integration-per-language.yml b/.github/workflows/integration-per-language.yml index 0a58d8e74..bbd787e4d 100644 --- a/.github/workflows/integration-per-language.yml +++ b/.github/workflows/integration-per-language.yml @@ -49,7 +49,7 @@ jobs: --variable BUILDERVERSION=1.11 \ --variable SERVICEPORT=8080 \ --variable NAMESPACE=testNamespace \ - --variable IMAGENAME=testImage \ + --variable IMAGENAME=testimage \ --variable IMAGETAG=latest \ --variable ENTRYPOINT=myapp.py - name: Validate JSON @@ -221,7 +221,7 @@ jobs: --variable BUILDERVERSION=1.11 \ --variable SERVICEPORT=8080 \ --variable NAMESPACE=testNamespace \ - --variable IMAGENAME=testImage \ + --variable IMAGENAME=testimage \ --variable IMAGETAG=latest \ --variable ENTRYPOINT=myapp.py - name: Validate JSON @@ -385,7 +385,7 @@ jobs: --variable BUILDERVERSION=1.11 \ --variable SERVICEPORT=80 \ --variable NAMESPACE=testNamespace \ - --variable IMAGENAME=testImage \ + --variable IMAGENAME=testimage \ --variable IMAGETAG=latest \ --variable ENTRYPOINT=myapp.py - name: Validate JSON diff --git a/cmd/create_test.go b/cmd/create_test.go index 4ea50928f..dd02d0006 100644 --- a/cmd/create_test.go +++ b/cmd/create_test.go @@ -34,7 +34,7 @@ func TestRun(t *testing.T) { {Name: "DOCKERFILENAME", Value: "Dockerfile"}, }, } - flagVariablesMap = map[string]string{"PORT": "8080", "APPNAME": "testing-create-command", "VERSION": "1.18", "SERVICEPORT": "8080", "NAMESPACE": "test-namespace", "IMAGENAME": "testImage", "IMAGETAG": "latest", "DOCKERFILENAME": "test.Dockerfile"} + flagVariablesMap = map[string]string{"PORT": "8080", "APPNAME": "testing-create-command", "VERSION": "1.18", "SERVICEPORT": "8080", "NAMESPACE": "test-namespace", "IMAGENAME": "host.minikube.internal:5001/testapp", "IMAGETAG": "latest", "DOCKERFILENAME": "test.Dockerfile"} mockCC := createCmd{ deployType: deployType, dest: testDir, diff --git a/pkg/config/validators/validators.go b/pkg/config/validators/validators.go index e56499651..cee767385 100644 --- a/pkg/config/validators/validators.go +++ b/pkg/config/validators/validators.go @@ -22,10 +22,18 @@ const ( maxImageTagLength = 128 // maxPort is the highest valid TCP/UDP port. maxPort = 65535 + + // Docker reference name grammar (namePat), without tags or digests: + // https://github.com/distribution/reference/blob/main/regexp.go + imageDomainComponent = `(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])` + imageHost = `(?:` + imageDomainComponent + `(?:\.` + imageDomainComponent + `)*|\[(?:[a-fA-F0-9:]+)\])` + imagePathComponent = `[a-z0-9]+(?:(?:[._]|__|[-]+)[a-z0-9]+)*` + imageName = `(?:` + imageHost + `(?::[0-9]+)?/)?` + imagePathComponent + `(?:/` + imagePathComponent + `)*` ) var ( ciNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._/-]*$`) + containerImageNamePattern = regexp.MustCompile(`^` + imageName + `$`) helmChartOverridesPattern = regexp.MustCompile(`^[A-Za-z0-9_.-]+=[A-Za-z0-9_.:/+-]+(?:,[A-Za-z0-9_.-]+=[A-Za-z0-9_.:/+-]+)*$`) ) @@ -50,8 +58,10 @@ func safeNameRunes(r rune) bool { func GetValidator(variableKind string) func(string) error { switch variableKind { - case "azureContainerRegistry", "azureManagedCluster", "azureResourceGroup", "containerImageName": + case "azureContainerRegistry", "azureManagedCluster", "azureResourceGroup": return ciNameValidator + case "containerImageName": + return containerImageNameValidator case "containerImageVersion": return containerImageVersionValidator case "dirPath": @@ -90,6 +100,15 @@ func ciNameValidator(input string) error { return nil } +// Image tags are supplied separately by containerImageVersion. Anchor Docker's +// name grammar so a valid substring cannot conceal trailing shell syntax. +func containerImageNameValidator(input string) error { + if !containerImageNamePattern.MatchString(input) { + return fmt.Errorf("invalid container image name %q: expected a repository name with an optional registry and port", input) + } + return nil +} + func kubernetesDNSLabelValidator(input string) error { if errs := validation.IsDNS1123Label(input); len(errs) > 0 { return fmt.Errorf("invalid Kubernetes name %q: %s", input, strings.Join(errs, "; ")) diff --git a/pkg/config/validators/validators_test.go b/pkg/config/validators/validators_test.go index f97b657c0..fd12f4dd7 100644 --- a/pkg/config/validators/validators_test.go +++ b/pkg/config/validators/validators_test.go @@ -203,6 +203,23 @@ func TestCINameValidatorsRejectShellSyntax(t *testing.T) { assert.Nil(t, GetValidator("kubernetesNamespace")("team-a")) } +func TestContainerImageNameValidator(t *testing.T) { + for _, input := range []string{ + "testapp", "team/my_app", "mcr.microsoft.com/team/app", + "host.minikube.internal:5001/testapp", "localhost:5000/team/app", + "127.0.0.1:5000/app", + } { + assert.NoError(t, GetValidator("containerImageName")(input), "expected %q to be valid", input) + } + for _, input := range []string{ + "", "app:latest", "app@sha256:abcd", "https://registry/app", "UPPERCASE", + "registry:port/app", "app;command", "app$(command)", "app --option value", + "app\nRUN command", `app"`, "app#comment", + } { + assert.Error(t, GetValidator("containerImageName")(input), "expected %q to be rejected", input) + } +} + func TestKubernetesResourceNameValidator(t *testing.T) { for _, input := range []string{"app", "my-app", "app2"} { assert.Nil(t, GetValidator("kubernetesResourceName")(input), "expected %q to be valid", input) diff --git a/pkg/fixtures/workflows/github/helm/.github/workflows/azure-kubernetes-service-helm.yml b/pkg/fixtures/workflows/github/helm/.github/workflows/azure-kubernetes-service-helm.yml index 724e63549..12bae8dfa 100644 --- a/pkg/fixtures/workflows/github/helm/.github/workflows/azure-kubernetes-service-helm.yml +++ b/pkg/fixtures/workflows/github/helm/.github/workflows/azure-kubernetes-service-helm.yml @@ -44,7 +44,7 @@ on: env: ACR_RESOURCE_GROUP: testAcrRG AZURE_CONTAINER_REGISTRY: testAcr - CONTAINER_NAME: testContainer + CONTAINER_NAME: testcontainer CLUSTER_NAME: testCluster CLUSTER_RESOURCE_GROUP: testClusterRG CLUSTER_RESOURCE_TYPE: Microsoft.ContainerService/managedClusters diff --git a/pkg/fixtures/workflows/github/kustomize/.github/workflows/azure-kubernetes-service-kustomize.yml b/pkg/fixtures/workflows/github/kustomize/.github/workflows/azure-kubernetes-service-kustomize.yml index 48c873c62..9fbd35cb1 100644 --- a/pkg/fixtures/workflows/github/kustomize/.github/workflows/azure-kubernetes-service-kustomize.yml +++ b/pkg/fixtures/workflows/github/kustomize/.github/workflows/azure-kubernetes-service-kustomize.yml @@ -45,7 +45,7 @@ on: env: ACR_RESOURCE_GROUP: testAcrRG AZURE_CONTAINER_REGISTRY: testAcr - CONTAINER_NAME: testContainer + CONTAINER_NAME: testcontainer CLUSTER_NAME: testCluster CLUSTER_RESOURCE_GROUP: testClusterRG CLUSTER_RESOURCE_TYPE: Microsoft.ContainerService/managedClusters diff --git a/pkg/handlers/templatetests/workflows_github_helm_test.go b/pkg/handlers/templatetests/workflows_github_helm_test.go index 52bf5b4cd..48682b9c0 100644 --- a/pkg/handlers/templatetests/workflows_github_helm_test.go +++ b/pkg/handlers/templatetests/workflows_github_helm_test.go @@ -20,7 +20,7 @@ func TestGitHubWorkflowHelmTemplates(t *testing.T) { "BRANCHNAME": "testBranch", "ACRRESOURCEGROUP": "testAcrRG", "AZURECONTAINERREGISTRY": "testAcr", - "CONTAINERNAME": "testContainer", + "CONTAINERNAME": "testcontainer", "CLUSTERRESOURCEGROUP": "testClusterRG", "CLUSTERRESOURCETYPE": "Microsoft.ContainerService/managedClusters", "CLUSTERNAME": "testCluster", diff --git a/pkg/handlers/templatetests/workflows_github_kustomize_test.go b/pkg/handlers/templatetests/workflows_github_kustomize_test.go index efbf72310..359a4541a 100644 --- a/pkg/handlers/templatetests/workflows_github_kustomize_test.go +++ b/pkg/handlers/templatetests/workflows_github_kustomize_test.go @@ -20,7 +20,7 @@ func TestGitHubWorkflowKustomizeTemplates(t *testing.T) { "BRANCHNAME": "testBranch", "ACRRESOURCEGROUP": "testAcrRG", "AZURECONTAINERREGISTRY": "testAcr", - "CONTAINERNAME": "testContainer", + "CONTAINERNAME": "testcontainer", "CLUSTERRESOURCEGROUP": "testClusterRG", "CLUSTERRESOURCETYPE": "Microsoft.ContainerService/managedClusters", "CLUSTERNAME": "testCluster", From e148e053746d75de88f7657d99ec3fb6871d8e54 Mon Sep 17 00:00:00 2001 From: David Gamero Date: Mon, 12 Oct 2026 14:27:49 -0400 Subject: [PATCH 3/4] test: use valid Kubernetes names in integration dry runs --- .github/workflows/integration-per-language.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/integration-per-language.yml b/.github/workflows/integration-per-language.yml index bbd787e4d..8e0b1ddf5 100644 --- a/.github/workflows/integration-per-language.yml +++ b/.github/workflows/integration-per-language.yml @@ -44,11 +44,11 @@ jobs: --skip-file-detection \ --deploy-type helm \ --variable PORT=8080 \ - --variable APPNAME=testingCreateCommand \ + --variable APPNAME=testing-create-command \ --variable VERSION=1.11 \ --variable BUILDERVERSION=1.11 \ --variable SERVICEPORT=8080 \ - --variable NAMESPACE=testNamespace \ + --variable NAMESPACE=test-namespace \ --variable IMAGENAME=testimage \ --variable IMAGETAG=latest \ --variable ENTRYPOINT=myapp.py @@ -216,11 +216,11 @@ jobs: --skip-file-detection \ --deploy-type kustomize \ --variable PORT=8080 \ - --variable APPNAME=testingCreateCommand \ + --variable APPNAME=testing-create-command \ --variable VERSION=1.11 \ --variable BUILDERVERSION=1.11 \ --variable SERVICEPORT=8080 \ - --variable NAMESPACE=testNamespace \ + --variable NAMESPACE=test-namespace \ --variable IMAGENAME=testimage \ --variable IMAGETAG=latest \ --variable ENTRYPOINT=myapp.py @@ -380,11 +380,11 @@ jobs: --skip-file-detection \ --deploy-type manifests \ --variable PORT=8080 \ - --variable APPNAME=testingCreateCommand \ + --variable APPNAME=testing-create-command \ --variable VERSION=1.11 \ --variable BUILDERVERSION=1.11 \ --variable SERVICEPORT=80 \ - --variable NAMESPACE=testNamespace \ + --variable NAMESPACE=test-namespace \ --variable IMAGENAME=testimage \ --variable IMAGETAG=latest \ --variable ENTRYPOINT=myapp.py From 59f882407660c92afca39f912efe93e9224e09a4 Mon Sep 17 00:00:00 2001 From: David Gamero Date: Mon, 12 Oct 2026 18:45:29 -0400 Subject: [PATCH 4/4] test: use lowercase image names in workflow generation --- .github/workflows/integration-per-language.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration-per-language.yml b/.github/workflows/integration-per-language.yml index 8e0b1ddf5..6e7623e0b 100644 --- a/.github/workflows/integration-per-language.yml +++ b/.github/workflows/integration-per-language.yml @@ -150,7 +150,7 @@ jobs: --variable BRANCHNAME=main \ --variable ACRRESOURCEGROUP=someAcrResourceGroup \ --variable AZURECONTAINERREGISTRY=someRegistry \ - --variable CONTAINERNAME=someContainer \ + --variable CONTAINERNAME=somecontainer \ --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup \ --variable CLUSTERNAME=someAksCluster \ --variable DOCKERFILE=./Dockerfile \ @@ -315,7 +315,7 @@ jobs: --variable BRANCHNAME=main \ --variable ACRRESOURCEGROUP=someAcrResourceGroup \ --variable AZURECONTAINERREGISTRY=someRegistry \ - --variable CONTAINERNAME=someContainer \ + --variable CONTAINERNAME=somecontainer \ --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup \ --variable CLUSTERNAME=someAksCluster \ --variable DOCKERFILE=./Dockerfile \ @@ -472,7 +472,7 @@ jobs: --variable BRANCHNAME=main \ --variable ACRRESOURCEGROUP=someAcrResourceGroup \ --variable AZURECONTAINERREGISTRY=someRegistry \ - --variable CONTAINERNAME=someContainer \ + --variable CONTAINERNAME=somecontainer \ --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup \ --variable CLUSTERNAME=someAksCluster \ --variable DOCKERFILE=./Dockerfile \