Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ ci:
- go.mod
- go.sum
cli_version: v0.1.0
pin_mode: sha
manifest_file: .github/manifest.yaml
manifest_key: ci
validate:
Expand Down
15 changes: 14 additions & 1 deletion .github/workflows/hotfix.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ name: Hotfix
# by trusted calling workflows, not by external users. All dynamic values are
# passed through env variables where they are used in run commands.

# Least-privilege default: read-only at the top level. Only the finalize job,
# which commits the manifest, pushes the version tag, publishes the release, and
# merges back to trunk, is granted contents: write.
permissions:
contents: write
contents: read

on:
workflow_call:
Expand Down Expand Up @@ -58,6 +61,8 @@ jobs:
validate:
name: Validate Hotfix
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
sha: ${{ steps.validate.outputs.sha }}
version: ${{ steps.version.outputs.final_version }}
Expand Down Expand Up @@ -171,6 +176,8 @@ jobs:
needs: validate
if: ${{ !inputs.dry_run }}
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
image_tag: ${{ steps.tag.outputs.image_tag }}
strategy:
Expand Down Expand Up @@ -219,6 +226,8 @@ jobs:
needs: [validate, build]
if: ${{ !inputs.dry_run }}
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
deploy: ${{ fromJson(needs.validate.outputs.deploy_names) }}
Expand Down Expand Up @@ -258,6 +267,8 @@ jobs:
always() &&
(needs.deploy.result == 'success' || needs.deploy.result == 'skipped')
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
deployed_sha: ${{ needs.validate.outputs.sha }}
changelog: ${{ steps.changelog.outputs.changelog }}
Expand Down Expand Up @@ -392,6 +403,8 @@ jobs:
needs: validate
if: inputs.dry_run
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Dry Run Summary
env:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/orchestrate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
changelog_base_sha: ${{ steps.setup.outputs.changelog_base_sha }}
base_build_cli: ${{ steps.setup.outputs.base_build_cli }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
- name: Setup CLI
Expand Down Expand Up @@ -82,7 +82,7 @@ jobs:
cli_result: ${{ needs.build-cli.outputs.result }}
validate_result: ${{ needs.validate.outputs.result }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
- name: Generate Summary
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/promote.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ jobs:
can_proceed: ${{ steps.preflight.outputs.can_proceed }}
promotion_result: ${{ steps.preflight.outputs.promotion_result }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
- name: Setup CLI
Expand Down Expand Up @@ -126,7 +126,7 @@ jobs:
if: ${{ github.event.inputs.dry_run != 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup CLI
uses: stablekernel/cascade/.github/actions/setup-cli@v0.1.0
with:
Expand All @@ -151,7 +151,7 @@ jobs:
contents: write
actions: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
- name: Setup CLI
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ on:
# fire when releases are managed via API calls in a workflow run.
workflow_dispatch:

# Least-privilege default: read-only at the top level. Only the release job,
# which cuts the GitHub Release and uploads built assets, is granted write.
permissions:
contents: write
contents: read

jobs:
# Integration protection: because Integration now gates merge to main via branch
Expand All @@ -34,6 +36,8 @@ jobs:
test:
name: Test
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

Expand All @@ -54,6 +58,8 @@ jobs:
name: Release
needs: test
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build stage
FROM golang:1.25-alpine AS builder
FROM golang:1.25-alpine@sha256:523c3effe300580ed375e43f43b1c9b091b68e935a7c3a92bfcc4e7ed55b18c2 AS builder

WORKDIR /build

Expand All @@ -24,7 +24,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build \
./cmd/cascade

# Runtime stage - minimal image with git
FROM alpine:3.19
FROM alpine:3.19@sha256:6baf43584bcb78f2e5847d1de515f23499913ac9f12bdf834811a3145eb11ca1

RUN apk add --no-cache git ca-certificates

Expand Down
4 changes: 2 additions & 2 deletions e2e/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@ require (
go.opentelemetry.io/otel v1.41.0 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect
go.opentelemetry.io/otel/trace v1.41.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/crypto v0.52.0 // indirect
golang.org/x/sys v0.45.0 // indirect
)
12 changes: 6 additions & 6 deletions e2e/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,16 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5w
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Expand Down
73 changes: 73 additions & 0 deletions internal/config/fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package config

import "testing"

// FuzzLoadManifest exercises the manifest parse-and-validate path with mutated
// bytes to assert the parser and validator never panic. Malformed input must
// surface as an error, never a crash. Errors are an acceptable outcome; a panic
// (or any other non-error abort) is a failure.
//
// Run the seed corpus as a normal test: go test -run FuzzLoadManifest ./internal/config/
// Run active fuzzing locally: go test -fuzz=FuzzLoadManifest ./internal/config/
func FuzzLoadManifest(f *testing.F) {
seeds := []string{
// Minimal valid manifest.
`ci:
config:
trunk_branch: main
environments: [dev, prod]
`,
// Manifest exercising config-level fields, pins, and rollout.
`ci:
config:
trunk_branch: main
environments: [dev, staging, prod]
runs_on: ubuntu-latest
job_timeout_minutes: 30
pin_mode: sha
action_pins:
actions/checkout: v4.2.2
validate:
workflow: .github/workflows/validate.yaml
builds:
- name: cli
workflow: .github/workflows/build-cli.yaml
changelog:
contributors: true
`,
// Manifest carrying a state section alongside config.
`ci:
config:
trunk_branch: main
environments: [dev, prod]
state:
prerelease:
sha: a47eb32931502bf345db5c5eba67d17f7ef6b886
version: v0.3.0-rc.1
`,
// Empty body and a body missing the required key: both must error, not panic.
``,
`not_ci:
config:
trunk_branch: main
`,
}
for _, s := range seeds {
f.Add([]byte(s))
}

f.Fuzz(func(t *testing.T, data []byte) {
// ParseManifestBytes is the entry point for a manifest read from a
// branch ref. A nil error implies a config we can hand to Validate.
file, err := ParseManifestBytes(data, DefaultManifestKey)
if err != nil {
return
}
if file == nil || file.Config == nil {
return
}
// Validate must tolerate any parsed config without panicking. The
// returned messages are not asserted; only the absence of a panic is.
_ = Validate(file.Config)
})
}
Loading