diff --git a/.github/workflows/cd-cdk.yaml b/.github/workflows/cd-cdk.yaml index 510cc45..1246d46 100644 --- a/.github/workflows/cd-cdk.yaml +++ b/.github/workflows/cd-cdk.yaml @@ -69,7 +69,18 @@ jobs: - name: Publish .NET project if: ${{ inputs.dotnet-publish-project != '' }} - run: dotnet publish ${{ inputs.dotnet-publish-project }} --configuration Release --runtime linux-arm64 --self-contained false --output $(dirname ${{ inputs.dotnet-publish-project }})/bin/Release/net8.0/linux-arm64/publish + # Env-var indirection (not inline expression interpolation) so shell + # metacharacters in the input are never parsed as script; the input is a + # single project path, so it stays quoted (no word-split) — an unquoted + # $(dirname ...) split the output path on whitespace. + env: + DOTNET_PUBLISH_PROJECT: ${{ inputs.dotnet-publish-project }} + run: | + dotnet publish "$DOTNET_PUBLISH_PROJECT" \ + --configuration Release \ + --runtime linux-arm64 \ + --self-contained false \ + --output "$(dirname "$DOTNET_PUBLISH_PROJECT")/bin/Release/net8.0/linux-arm64/publish" - uses: actions/setup-node@v7 with: @@ -89,10 +100,13 @@ jobs: - name: Install Python dependencies if: ${{ inputs.python-version != '' }} + # NUL-delimited read loop rather than `for req in $(find ...)`: the + # command-substitution form word-splits and globs every path it finds. + shell: bash run: | - for req in $(find . -name requirements.txt -not -path '*/node_modules/*'); do + while IFS= read -r -d '' req; do pip install -r "$req" - done + done < <(find . -name requirements.txt -not -path '*/node_modules/*' -print0) - uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6 with: @@ -130,7 +144,13 @@ jobs: # $STACKS deliberately word-splits multiple selectors. env: STACKS: ${{ inputs.stacks }} - run: npx -y cdk deploy $STACKS --require-approval never + run: | + # $STACKS is deliberately unquoted: it carries one or more + # space-separated CDK stack selectors (default "--all") that must reach + # `cdk deploy` as separate argv entries. Quoting it would collapse them + # into one bogus selector and break every multi-stack deploy. + # shellcheck disable=SC2086 + npx -y cdk deploy $STACKS --require-approval never - name: Post-deploy script if: ${{ inputs.post-deploy-script != '' }} diff --git a/.github/workflows/cd-sam.yaml b/.github/workflows/cd-sam.yaml index 5628911..5b81e36 100644 --- a/.github/workflows/cd-sam.yaml +++ b/.github/workflows/cd-sam.yaml @@ -90,6 +90,12 @@ jobs: if [ -n "$PARAM_OVERRIDES" ]; then PARAMS="--parameter-overrides $PARAM_OVERRIDES" fi + # $PARAMS is deliberately unquoted: it is either empty (no overrides, + # so no flag at all) or "--parameter-overrides Key=Value [Key=Value…]", + # which must reach `sam deploy` as separate argv entries. Quoting it + # would pass one empty or one concatenated argument and break every + # parameterised deploy. + # shellcheck disable=SC2086 sam deploy \ --stack-name ${{ inputs.stack-name }} \ --template-file .aws-sam/build/template.yaml \ diff --git a/.github/workflows/ci-python-sam.yaml b/.github/workflows/ci-python-sam.yaml index 03b8df5..ac35356 100644 --- a/.github/workflows/ci-python-sam.yaml +++ b/.github/workflows/ci-python-sam.yaml @@ -72,13 +72,16 @@ jobs: - name: Install Python dependencies if: ${{ inputs.run-tests || inputs.run-cdk-synth }} + # NUL-delimited read loop rather than `for req in $(find ...)`: the + # command-substitution form word-splits and globs every path it finds. + shell: bash run: | if [ "${{ inputs.run-tests }}" = "true" ]; then pip install pytest fi - for req in $(find . -name requirements.txt -not -path './.aws-sam/*'); do + while IFS= read -r -d '' req; do pip install -r "$req" - done + done < <(find . -name requirements.txt -not -path './.aws-sam/*' -print0) - name: Run tests if: ${{ inputs.run-tests }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 66d50d9..80e2e63 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -23,11 +23,19 @@ name: ci # supply-chain surface auditable. Bump ACTIONLINT_VERSION + ACTIONLINT_SHA256 # together (checksum from the release's *_checksums.txt). # -# actionlint's shellcheck integration is disabled (`-shellcheck=`) for now: it -# reports 4 pre-existing findings in the deploy/CI run-steps (SC2044 find-in-for -# loops, SC2046/SC2086 quoting, one of which is intentional word-splitting in the -# SAM deploy step). Those deserve a separate, tested cleanup rather than being -# bundled into the gate that unblocks the repo. Re-enable shellcheck once fixed. +# actionlint's shellcheck integration is ON (it defaults to the `shellcheck` on +# PATH; the ubuntu-latest runner image ships shellcheck 0.9.0, so nothing extra +# is installed). Do NOT re-add `-shellcheck=` — the empty value silently turns +# the whole shell-linting half of this gate back off. +# +# Two run-steps carry a narrowly-scoped `# shellcheck disable=SC2086` on the +# single line above the command, because the unquoted expansion there is the +# point: `sam deploy … $PARAMS` (cd-sam.yaml) and `cdk deploy $STACKS` +# (cd-cdk.yaml) rely on word-splitting to turn one variable into several argv +# entries. Quoting them would collapse multiple parameter overrides or stack +# selectors into one argument and break those deploys. Every other finding was +# fixed in the shell rather than suppressed. Suppressions stay per-line and +# commented — never file-wide, and never by weakening this invocation. on: pull_request: @@ -56,5 +64,7 @@ jobs: shell: bash - name: Lint workflows - run: ./actionlint -color -shellcheck= + run: | + shellcheck --version + ./actionlint -color shell: bash diff --git a/README.md b/README.md index 8d1fc95..51d9839 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Organization-level GitHub configuration for Sea Haven Industries. **`.github/workflows/callable-dependency-review.yaml`** — Dependency review on PRs, failing on high severity. Requires Dependency Graph. -**`.github/workflows/ci.yaml`** — Self-CI for this repo: actionlint (checksum-verified install) over all workflow files, emitting the required `ci / ci` status context. +**`.github/workflows/ci.yaml`** — Self-CI for this repo: actionlint (checksum-verified install) over all workflow files, emitting the required `ci / ci` status context. Its shellcheck integration is enabled, so `run:` bodies are shell-linted too; the two deploy steps that rely on intentional word-splitting (`sam deploy … $PARAMS`, `cdk deploy $STACKS`) carry a per-line, commented `# shellcheck disable=SC2086` rather than being quoted or globally exempted. **`.github/workflows/compliance-audit.yaml`** — **DEPRECATED (2026-06-10).** The weekly scheduled org-wide audit has been retired: the schedule was removed and the workflow is disabled in the Actions tab (manual `workflow_dispatch` only, kept for historical reference). Repo compliance is now handled by the Claude Code App on pull requests and the engineering handbook directly. Safe to delete in a future cleanup.