diff --git a/.github/actions/test-results-verification/action.yml b/.github/actions/test-results-verification/action.yml index df371343..55fd239d 100644 --- a/.github/actions/test-results-verification/action.yml +++ b/.github/actions/test-results-verification/action.yml @@ -35,10 +35,10 @@ runs: shell: bash run: | - echo "::group::+++ test-results-summary [-t ${{ env.INPUT_TYPE }}][-r ${{ env.INPUT_RESULTS_FOLDER }}] +++" + echo "::group::+++ test-results-summary [-t ${INPUT_TYPE}][-r ${INPUT_RESULTS_FOLDER}] +++" - chmod +x ${{ github.action_path }}/test-results-summary.sh - ${{ github.action_path }}/test-results-summary.sh -t '${{ env.INPUT_TYPE }}' -r '${{ env.INPUT_RESULTS_FOLDER }}' + chmod +x "${{ github.action_path }}/test-results-summary.sh" + "${{ github.action_path }}/test-results-summary.sh" -t "$INPUT_TYPE" -r "$INPUT_RESULTS_FOLDER" echo "::endgroup::" @@ -46,16 +46,23 @@ runs: id: test-results-verification if: ${{ ( inputs.threshold != null && steps.test-results-summary.outputs.result-value != null ) }} uses: actions/github-script@v9 + env: + INPUT_TYPE: ${{ inputs.type }} + INPUT_RESULTS_FOLDER: ${{ inputs.results_folder }} + INPUT_THRESHOLD: ${{ inputs.threshold }} + RESULT_TYPE: ${{ steps.test-results-summary.outputs.result-type }} + RESULT_LABEL: ${{ steps.test-results-summary.outputs.result-label }} + RESULT_VALUE: ${{ steps.test-results-summary.outputs.result-value }} with: script: | - console.log(`::group::+++ test-results-verification [-t ${{ inputs.type }}][-p ${{ inputs.results_folder }}] +++`); + console.log(`::group::+++ test-results-verification [-t ${process.env.INPUT_TYPE}][-p ${process.env.INPUT_RESULTS_FOLDER}] +++`); - var threshold = parseFloat('${{ inputs.threshold }}'); + var threshold = parseFloat(process.env.INPUT_THRESHOLD); - var type = '${{ steps.test-results-summary.outputs.result-type }}'; - var label = '${{ steps.test-results-summary.outputs.result-label }}'; - var result = parseFloat('${{ steps.test-results-summary.outputs.result-value }}'); + var type = process.env.RESULT_TYPE; + var label = process.env.RESULT_LABEL; + var result = parseFloat(process.env.RESULT_VALUE); console.log(`${type} - ${label} [${result}] - threshold [${threshold}]`); diff --git a/.github/actions/test-results-verification/test-results-summary.sh b/.github/actions/test-results-verification/test-results-summary.sh index 61493691..91ff0484 100644 --- a/.github/actions/test-results-verification/test-results-summary.sh +++ b/.github/actions/test-results-verification/test-results-summary.sh @@ -39,11 +39,11 @@ parse_results() { # Load parser # shellcheck disable=SC1090 source "$PARSER" - # Define command, for example parse_surefire surefire code/target/reports - COMMAND="parse_$RESULTS_TYPE $RESULTS_TYPE $RESULTS_FOLDER" - # Execute command - echo "Executing [$COMMAND]" - eval "$COMMAND" + # Execute command directly, for example parse_surefire surefire code/target/reports + # (RESULTS_TYPE is validated above against an existing parser file, so the + # function name is not attacker-controlled; call it directly instead of eval) + echo "Executing [parse_$RESULTS_TYPE $RESULTS_TYPE $RESULTS_FOLDER]" + "parse_$RESULTS_TYPE" "$RESULTS_TYPE" "$RESULTS_FOLDER" else echo "Unsupported Type [$RESULTS_TYPE]" fi diff --git a/.github/workflows/code-maven_java-QA_e2e_karate.yml b/.github/workflows/code-maven_java-QA_e2e_karate.yml index 7f9e0c10..c9364996 100644 --- a/.github/workflows/code-maven_java-QA_e2e_karate.yml +++ b/.github/workflows/code-maven_java-QA_e2e_karate.yml @@ -52,6 +52,10 @@ env: WORKFLOW_VERSION: 1.0.0 MAVEN_OPTS: "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" +# Least-privilege: this QA job only reads the repo, builds/runs the app locally, and uploads artifacts +permissions: + contents: read + # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -72,6 +76,7 @@ jobs: uses: actions/checkout@v6 with: fetch-depth: 0 + persist-credentials: false # config - name: config @@ -88,6 +93,16 @@ jobs: id: app-config if: ${{ fromJSON(steps.config.outputs.config).karate.app.enabled == true }} working-directory: code + # Config values come from github_config.yml, which a fork PR can edit. + # Bind them to env vars so their contents cannot inject shell via ${{ }} splicing. + env: + CFG_APP_PORT: ${{ fromJSON(steps.config.outputs.config).karate.app.port }} + CFG_APP_JAR: ${{ fromJSON(steps.config.outputs.config).karate.app.jar }} + CFG_APP_HEALTH_PROBE: ${{ fromJSON(steps.config.outputs.config).karate.app.health_probe }} + CFG_JACOCO_REPORT_FOLDER: ${{ fromJSON(steps.config.outputs.config).karate.jacoco.report.folder }} + CFG_JACOCO_INCLUDES: ${{ join(fromJSON(steps.config.outputs.config).karate.jacoco.includes, ':') }} + CFG_JACOCO_EXCLUDES: ${{ join(fromJSON(steps.config.outputs.config).karate.jacoco.excludes, ':') }} + CFG_JACOCO_SOURCE_FILES: ${{ join(fromJSON(steps.config.outputs.config).karate.jacoco.sourcefiles, ' --sourcefiles ') }} run: | # app-config @@ -95,7 +110,7 @@ jobs: echo "::group::+++ app-config +++" # App Port - APP_PORT=${{ fromJSON(steps.config.outputs.config).karate.app.port }} + APP_PORT="$CFG_APP_PORT" echo "APP_PORT=$APP_PORT" # Project Version from pom.xml @@ -103,7 +118,7 @@ jobs: echo "PROJECT_VERSION=$PROJECT_VERSION" # App Path to Jar - APP_PATH_TO_JAR="${{ fromJSON(steps.config.outputs.config).karate.app.jar }}-$PROJECT_VERSION.jar" + APP_PATH_TO_JAR="$CFG_APP_JAR-$PROJECT_VERSION.jar" echo "APP_PATH_TO_JAR=$APP_PATH_TO_JAR" # App Options @@ -111,7 +126,7 @@ jobs: echo "APP_OPTIONS=$APP_OPTIONS" # App Health Probe from config - APP_HEALTH_PROBE="${{ fromJSON(steps.config.outputs.config).karate.app.health_probe }}" + APP_HEALTH_PROBE="$CFG_APP_HEALTH_PROBE" echo "APP_HEALTH_PROBE=$APP_HEALTH_PROBE" # App Health Check URL @@ -123,19 +138,19 @@ jobs: echo "JACOCO_VERSION=$JACOCO_VERSION" # JaCoCo report folder relative to the code folder - JACOCO_RELATIVE_FOLDER=$(echo ${{ fromJSON(steps.config.outputs.config).karate.jacoco.report.folder }} | sed -E "s/^code\///" ) + JACOCO_RELATIVE_FOLDER=$(echo "$CFG_JACOCO_REPORT_FOLDER" | sed -E "s/^code\///" ) echo "JACOCO_RELATIVE_FOLDER=$JACOCO_RELATIVE_FOLDER" # JaCoCo Settings - Includes - JACOCO_INCLUDES="${{ join(fromJSON(steps.config.outputs.config).karate.jacoco.includes, ':') }}" + JACOCO_INCLUDES="$CFG_JACOCO_INCLUDES" echo "JACOCO_INCLUDES=$JACOCO_INCLUDES" # JaCoCo Settings - Excludes - JACOCO_EXCLUDES="${{ join(fromJSON(steps.config.outputs.config).karate.jacoco.excludes, ':') }}" + JACOCO_EXCLUDES="$CFG_JACOCO_EXCLUDES" echo "JACOCO_EXCLUDES=$JACOCO_EXCLUDES" # JaCoCo Settings - Source Files - JACOCO_SOURCE_FILES="${{ join(fromJSON(steps.config.outputs.config).karate.jacoco.sourcefiles, ' --sourcefiles ') }}" + JACOCO_SOURCE_FILES="$CFG_JACOCO_SOURCE_FILES" echo "JACOCO_SOURCE_FILES=$JACOCO_SOURCE_FILES" # JaCoCo Settings - Class Dump Directory @@ -191,16 +206,17 @@ jobs: # save-tool-versions-content - name: save-tool-versions-content run: | + DELIMITER="EOF_$(openssl rand -hex 16)" { - echo "TOOL_VERSIONS<> "$GITHUB_ENV" # asdf-install - name: asdf-install id: asdf-install - uses: asdf-vm/actions/install@v4 + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4 with: tool_versions: ${{ env.TOOL_VERSIONS }} @@ -244,6 +260,15 @@ jobs: id: start-java-app if: ${{ fromJSON(steps.config.outputs.config).karate.app.enabled == true }} working-directory: code + # Bind config/step-output values to env vars so their contents cannot + # inject shell via ${{ }} splicing into this run: block. + env: + CFG_APP_LOGFILE: ${{ fromJSON(steps.config.outputs.config).karate.app.logfile }} + OUT_JACOCO_VERSION: ${{ steps.app-config.outputs.jacoco-version }} + OUT_APP_PATH_TO_JAR: ${{ steps.app-config.outputs.app-path-to-jar }} + OUT_APP_OPTIONS: ${{ steps.app-config.outputs.app-options }} + OUT_JACOCO_AGENT_OPTIONS: ${{ steps.app-config.outputs.jacoco-agent-options }} + OUT_APP_HEALTH_CHECK_URL: ${{ steps.app-config.outputs.app-health-check-url }} run: | # start-java-app @@ -251,10 +276,10 @@ jobs: echo "::group::+++ download-jacoco-agent +++" # Download JaCoCo agent - JACOCO_VERSION="${{ steps.app-config.outputs.jacoco-version }}" + JACOCO_VERSION="$OUT_JACOCO_VERSION" echo "JACOCO_VERSION=$JACOCO_VERSION" - curl -k -s -S -L -o target/jacocoagent.jar https://repo1.maven.org/maven2/org/jacoco/org.jacoco.agent/$JACOCO_VERSION/org.jacoco.agent-$JACOCO_VERSION-runtime.jar + curl -s -S -L -o target/jacocoagent.jar https://repo1.maven.org/maven2/org/jacoco/org.jacoco.agent/$JACOCO_VERSION/org.jacoco.agent-$JACOCO_VERSION-runtime.jar echo "$(ls -l target/jacoco*.jar 2>/dev/null)" @@ -263,10 +288,10 @@ jobs: echo "::group::+++ start-java-app +++" # Start the app - APP_LOG_FILE="${{ fromJSON(steps.config.outputs.config).karate.app.logfile }}" - APP_PATH_TO_JAR="${{ steps.app-config.outputs.app-path-to-jar }}" - APP_OPTIONS="${{ steps.app-config.outputs.app-options }}" - JACOCO_AGENT_OPTIONS="${{ steps.app-config.outputs.jacoco-agent-options }}" + APP_LOG_FILE="$CFG_APP_LOGFILE" + APP_PATH_TO_JAR="$OUT_APP_PATH_TO_JAR" + APP_OPTIONS="$OUT_APP_OPTIONS" + JACOCO_AGENT_OPTIONS="$OUT_JACOCO_AGENT_OPTIONS" START_APP_CMD="java $JACOCO_AGENT_OPTIONS -jar $APP_PATH_TO_JAR $APP_OPTIONS" @@ -288,8 +313,7 @@ jobs: echo "::group::+++ wait-for-app-health-check +++" # Wait for the app to start - APP_HEALTH_CHECK_URL="${{ steps.app-config.outputs.app-health-check-url }}" - APP_HEALTH_CHECK_CMD="curl -s -o /dev/null -w \"%{http_code}\" $APP_HEALTH_CHECK_URL" + APP_HEALTH_CHECK_URL="$OUT_APP_HEALTH_CHECK_URL" echo ">> Waiting for healthcheck to return 200: $APP_HEALTH_CHECK_URL" @@ -305,7 +329,7 @@ jobs: while [ $RETRY_COUNT -lt $((RETRY_TIMES)) ]; do RETRY_COUNT=$((RETRY_COUNT+1)); echo ">> Executing healthcheck [$APP_HEALTH_CHECK_URL], try [$RETRY_COUNT of $RETRY_TIMES]"; - response=$(eval "$APP_HEALTH_CHECK_CMD"); + response=$(curl -s -o /dev/null -w "%{http_code}" "$APP_HEALTH_CHECK_URL"); if [[ $response -eq 200 ]]; then echo ">> Started app with pid=$APP_PID at $(date +%Y/%m/%d-%H:%M:%S.%3N)" break; @@ -326,6 +350,12 @@ jobs: - name: mvn-clean-verify id: mvn-clean-verify working-directory: e2e/karate + # Config values come from github_config.yml, which a fork PR can edit. + # Bind them to env vars so their contents cannot inject shell via ${{ }} splicing. + env: + CFG_APP_PORT: ${{ fromJSON(steps.config.outputs.config).karate.app.port }} + CFG_KARATE_ENV: ${{ fromJSON(steps.config.outputs.config).karate.env }} + CFG_KARATE_OPTIONS: ${{ fromJSON(steps.config.outputs.config).karate.options }} run: | # mvn-clean-verify @@ -333,9 +363,9 @@ jobs: echo "::group::+++ mvn-clean-verify +++" # Maven Properties - APP_PORT=${{ fromJSON(steps.config.outputs.config).karate.app.port }} - KARATE_ENV=${{ fromJSON(steps.config.outputs.config).karate.env }} - KARATE_OPTIONS="${{ fromJSON(steps.config.outputs.config).karate.options }}" + APP_PORT="$CFG_APP_PORT" + KARATE_ENV="$CFG_KARATE_ENV" + KARATE_OPTIONS="$CFG_KARATE_OPTIONS" echo "MVN_PROPERTIES=-DAPP_PORT=$APP_PORT -Dkarate.env=$KARATE_ENV -Dkarate.options=$KARATE_OPTIONS" # mvn clean verify @@ -377,6 +407,9 @@ jobs: && fromJSON(steps.config.outputs.config).karate.app.enabled == true && steps.start-java-app.outcome == 'success' }} working-directory: code + env: + OUT_APP_PID: ${{ steps.start-java-app.outputs.app-pid }} + OUT_JACOCO_RELATIVE_FOLDER: ${{ steps.app-config.outputs.jacoco-relative-folder }} run: | # stop-java-app @@ -385,7 +418,7 @@ jobs: # Stop the app # Kill the app using the app-pid - APP_PID=${{ steps.start-java-app.outputs.app-pid }} + APP_PID="$OUT_APP_PID" echo ">> Killing app-pid=$APP_PID at $(date +%Y/%m/%d-%H:%M:%S.%3N)" while kill -15 $APP_PID 2>/dev/null; do sleep 1; done @@ -393,7 +426,7 @@ jobs: # Check JaCoCo agent Dump Files echo ">> JaCoCo agent dump files:" - echo "$(ls -l ${{ steps.app-config.outputs.jacoco-relative-folder }})" + ls -l "$OUT_JACOCO_RELATIVE_FOLDER" echo "::endgroup::" @@ -405,6 +438,9 @@ jobs: && fromJSON(steps.config.outputs.config).karate.app.enabled == true && steps.start-java-app.outcome == 'success' }} working-directory: code + env: + OUT_JACOCO_VERSION: ${{ steps.app-config.outputs.jacoco-version }} + OUT_JACOCO_CLI_REPORT_OPTIONS: ${{ steps.app-config.outputs.jacoco-cli-report-options }} run: | # generate-jacoco-report @@ -412,11 +448,11 @@ jobs: echo "::group::+++ download-jacoco-cli +++" # Download JaCoCo CLI - JACOCO_VERSION="${{ steps.app-config.outputs.jacoco-version }}" + JACOCO_VERSION="$OUT_JACOCO_VERSION" echo "JACOCO_VERSION=$JACOCO_VERSION" # Donwload the JaCoCo CLI - curl -k -s -S -L -o target/jacococli.jar https://repo1.maven.org/maven2/org/jacoco/org.jacoco.cli/$JACOCO_VERSION/org.jacoco.cli-$JACOCO_VERSION-nodeps.jar + curl -s -S -L -o target/jacococli.jar https://repo1.maven.org/maven2/org/jacoco/org.jacoco.cli/$JACOCO_VERSION/org.jacoco.cli-$JACOCO_VERSION-nodeps.jar echo "$(ls -l target/jacoco*.jar 2>/dev/null)" @@ -425,7 +461,7 @@ jobs: echo "::group::+++ generate-jacoco-report +++" # JaCoCo CLI Report Options - JACOCO_CLI_REPORT_OPTIONS="${{ steps.app-config.outputs.jacoco-cli-report-options }}" + JACOCO_CLI_REPORT_OPTIONS="$OUT_JACOCO_CLI_REPORT_OPTIONS" # Generate JaCoCo report echo ">> Generating JaCoCo report with options: $JACOCO_CLI_REPORT_OPTIONS" diff --git a/.github/workflows/code-maven_java-QA_integration.yml b/.github/workflows/code-maven_java-QA_integration.yml index 345739f3..7a045506 100644 --- a/.github/workflows/code-maven_java-QA_integration.yml +++ b/.github/workflows/code-maven_java-QA_integration.yml @@ -34,6 +34,10 @@ env: WORKFLOW_VERSION: 1.0.0 MAVEN_OPTS: "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" +# Least-privilege: these QA jobs only read the repo, run Maven, and upload artifacts +permissions: + contents: read + # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -54,6 +58,7 @@ jobs: uses: actions/checkout@v6 with: fetch-depth: 0 + persist-credentials: false # config - name: config @@ -88,16 +93,17 @@ jobs: # save-tool-versions-content - name: save-tool-versions-content run: | + DELIMITER="EOF_$(openssl rand -hex 16)" { - echo "TOOL_VERSIONS<> "$GITHUB_ENV" # asdf-install - name: asdf-install id: asdf-install - uses: asdf-vm/actions/install@v4 + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4 with: tool_versions: ${{ env.TOOL_VERSIONS }} @@ -120,6 +126,8 @@ jobs: - name: mvn-clean-verify id: mvn-clean-verify working-directory: code + env: + RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} run: | # mvn-clean-verify @@ -129,7 +137,7 @@ jobs: # Maven Properties MVN_PROPERTIES="-DskipUTs -DfailIfNoTests=false -Dmaven.test.failure.ignore=false" # if: github.event_name == 'release' add -DskipEnforceSnapshots - if [[ -n "${{ github.event.release.tag_name }}" ]]; then + if [[ -n "$RELEASE_TAG_NAME" ]]; then MVN_PROPERTIES="$MVN_PROPERTIES -DskipEnforceSnapshots" fi echo "MVN_PROPERTIES=${MVN_PROPERTIES}" diff --git a/.github/workflows/code-maven_java-QA_mutation.yml b/.github/workflows/code-maven_java-QA_mutation.yml index 3c88d532..b21536ad 100644 --- a/.github/workflows/code-maven_java-QA_mutation.yml +++ b/.github/workflows/code-maven_java-QA_mutation.yml @@ -34,6 +34,10 @@ env: WORKFLOW_VERSION: 1.0.0 MAVEN_OPTS: "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" +# Least-privilege: these QA jobs only read the repo, run Maven, and upload artifacts +permissions: + contents: read + # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -54,6 +58,7 @@ jobs: uses: actions/checkout@v6 with: fetch-depth: 0 + persist-credentials: false # config - name: config @@ -88,16 +93,17 @@ jobs: # save-tool-versions-content - name: save-tool-versions-content run: | + DELIMITER="EOF_$(openssl rand -hex 16)" { - echo "TOOL_VERSIONS<> "$GITHUB_ENV" # asdf-install - name: asdf-install id: asdf-install - uses: asdf-vm/actions/install@v4 + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4 with: tool_versions: ${{ env.TOOL_VERSIONS }} diff --git a/.github/workflows/code-maven_java-QA_unit.yml b/.github/workflows/code-maven_java-QA_unit.yml index 054b3402..9a6a0e31 100644 --- a/.github/workflows/code-maven_java-QA_unit.yml +++ b/.github/workflows/code-maven_java-QA_unit.yml @@ -34,6 +34,10 @@ env: WORKFLOW_VERSION: 1.0.0 MAVEN_OPTS: "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" +# Least-privilege: these QA jobs only read the repo, run Maven, and upload artifacts +permissions: + contents: read + # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -54,6 +58,7 @@ jobs: uses: actions/checkout@v6 with: fetch-depth: 0 + persist-credentials: false # config - name: config @@ -88,16 +93,17 @@ jobs: # save-tool-versions-content - name: save-tool-versions-content run: | + DELIMITER="EOF_$(openssl rand -hex 16)" { - echo "TOOL_VERSIONS<> "$GITHUB_ENV" # asdf-install - name: asdf-install id: asdf-install - uses: asdf-vm/actions/install@v4 + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4 with: tool_versions: ${{ env.TOOL_VERSIONS }} @@ -120,6 +126,8 @@ jobs: - name: mvn-clean-verify id: mvn-clean-verify working-directory: code + env: + RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} run: | # mvn-clean-verify @@ -129,7 +137,7 @@ jobs: # Maven Properties MVN_PROPERTIES="-DskipITs -DfailIfNoTests=false -Dmaven.test.failure.ignore=false" # if: github.event_name == 'release' add -DskipEnforceSnapshots - if [[ -n "${{ github.event.release.tag_name }}" ]]; then + if [[ -n "$RELEASE_TAG_NAME" ]]; then MVN_PROPERTIES="$MVN_PROPERTIES -DskipEnforceSnapshots" fi echo "MVN_PROPERTIES=${MVN_PROPERTIES}" diff --git a/.github/workflows/docs-build_snapshot.yml b/.github/workflows/docs-build_snapshot.yml index 400969a7..9c58e423 100644 --- a/.github/workflows/docs-build_snapshot.yml +++ b/.github/workflows/docs-build_snapshot.yml @@ -12,6 +12,10 @@ jobs: check-triggered-release: name: Check release-docs labeled runs-on: ubuntu-24.04 + # Least-privilege: reads the commit and its associated PR labels via the API; no writes + permissions: + contents: read + pull-requests: read outputs: labels: ${{ steps.get-labels.outputs.LABELS }} skip: ${{ steps.release-commits.outputs.SKIP }} @@ -44,6 +48,10 @@ jobs: name: Build Snapshot needs: check-triggered-release runs-on: ubuntu-24.04 + # Least-privilege: clones the repo (persist-credentials:false) and dispatches docs-publish via the default token + permissions: + contents: read + actions: write if: ${{ ((vars.DEVELOPMENT_FLOW == 'trunk-based-development' && (github.ref_name == 'main' || startsWith(github.ref_name, 'main-'))) || (vars.DEVELOPMENT_FLOW != 'trunk-based-development' && (github.ref_name == 'develop' || startsWith(github.ref_name, 'develop-')))) && !contains(join(needs.check-triggered-release.outputs.labels, ', '), 'release-docs') @@ -75,49 +83,20 @@ jobs: - name: Save tool-versions content run: | + DELIMITER="EOF_$(openssl rand -hex 16)" { - echo "TOOL_VERSIONS<> "$GITHUB_ENV" - name: Node / Setup asdf environment - uses: asdf-vm/actions/install@v4 + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4 # https://github.com/asdf-vm/actions/issues/356 if: steps.asdf-cache.outputs.cache-hit != 'true' with: tool_versions: ${{ env.TOOL_VERSIONS }} - - name: Prepare committer information - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GPG_PRIVATE_KEY: ${{ secrets.CI_GPG_SECRET_KEY }} - GPG_PASSPHRASE: ${{ secrets.CI_GPG_SECRET_KEY_PASSWORD }} - run: | - git config --global credential.helper store - cat <> ~/.git-credentials - https://ci-user:$GITHUB_TOKEN@github.com - EOT - - # GPG: non-interactive signing setup - mkdir -p ~/.gnupg && chmod 700 ~/.gnupg - printf 'allow-loopback-pinentry\nallow-preset-passphrase\n' > ~/.gnupg/gpg-agent.conf - printf 'use-agent\npinentry-mode loopback\n' > ~/.gnupg/gpg.conf - gpgconf --kill gpg-agent || true - echo "$GPG_PRIVATE_KEY" | gpg --batch --import - KEY_DATA=$(gpg --list-secret-keys --with-colons) - echo "$KEY_DATA" | awk -F: '/^grp:/ {print $10}' | while read -r GRIP; do - /usr/lib/gnupg/gpg-preset-passphrase --preset "$GRIP" <<< "$GPG_PASSPHRASE" - done - - # Git: identity and signing - FPR=$(echo "$KEY_DATA" | awk -F: '/^fpr:/ {print $10; exit}') - git config user.name "srvcosoitxtech" - git config user.email "oso@inditex.com" - git config user.signingkey "$FPR" - git config commit.gpgsign true - git config tag.gpgsign true - - name: Docker / Execute Kroki server run: | docker run \ @@ -127,7 +106,7 @@ jobs: --health-interval=2s \ --health-retries=5 \ --health-timeout=1s \ - -d yuzutech/kroki + -d yuzutech/kroki@sha256:6980bfb218b48b74ea14b888d9c7e8c032d1cb6325f3292277abdf62483abd9d max_attempts=5 attempt=1 diff --git a/.github/workflows/docs-publish.yml b/.github/workflows/docs-publish.yml index 35cc50dc..3bf0aa93 100644 --- a/.github/workflows/docs-publish.yml +++ b/.github/workflows/docs-publish.yml @@ -33,11 +33,14 @@ jobs: steps: - name: Decide reference branch id: decide-branch + env: + AUTODEPLOY_BRANCH: ${{ github.event.inputs.AUTODEPLOY_BRANCH }} + VERSION: ${{ github.event.inputs.VERSION }} run: | - if [ -n "${{ github.event.inputs.AUTODEPLOY_BRANCH }}" ]; then - echo "REFERENCE_BRANCH=${{ github.event.inputs.AUTODEPLOY_BRANCH }}" >> "$GITHUB_ENV" + if [ -n "$AUTODEPLOY_BRANCH" ]; then + echo "REFERENCE_BRANCH=$AUTODEPLOY_BRANCH" >> "$GITHUB_ENV" else - echo "REFERENCE_BRANCH=docs/v${{ github.event.inputs.VERSION }}" >> "$GITHUB_ENV" + echo "REFERENCE_BRANCH=docs/v${VERSION}" >> "$GITHUB_ENV" fi - name: Checkout repository @@ -45,6 +48,7 @@ jobs: with: fetch-depth: 0 ref: ${{ steps.decide-branch.outputs.REFERENCE_BRANCH }} + persist-credentials: false - name: Configure Pages uses: actions/configure-pages@v6 @@ -69,49 +73,20 @@ jobs: - name: Save tool-versions content run: | + DELIMITER="EOF_$(openssl rand -hex 16)" { - echo "TOOL_VERSIONS<> "$GITHUB_ENV" - name: Node / Setup asdf environment - uses: asdf-vm/actions/install@v4 + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4 # https://github.com/asdf-vm/actions/issues/356 if: steps.asdf-cache.outputs.cache-hit != 'true' with: tool_versions: ${{ env.TOOL_VERSIONS }} - - name: Prepare committer information - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GPG_PRIVATE_KEY: ${{ secrets.CI_GPG_SECRET_KEY }} - GPG_PASSPHRASE: ${{ secrets.CI_GPG_SECRET_KEY_PASSWORD }} - run: | - git config --global credential.helper store - cat <> ~/.git-credentials - https://ci-user:$GITHUB_TOKEN@github.com - EOT - - # GPG: non-interactive signing setup - mkdir -p ~/.gnupg && chmod 700 ~/.gnupg - printf 'allow-loopback-pinentry\nallow-preset-passphrase\n' > ~/.gnupg/gpg-agent.conf - printf 'use-agent\npinentry-mode loopback\n' > ~/.gnupg/gpg.conf - gpgconf --kill gpg-agent || true - echo "$GPG_PRIVATE_KEY" | gpg --batch --import - KEY_DATA=$(gpg --list-secret-keys --with-colons) - echo "$KEY_DATA" | awk -F: '/^grp:/ {print $10}' | while read -r GRIP; do - /usr/lib/gnupg/gpg-preset-passphrase --preset "$GRIP" <<< "$GPG_PASSPHRASE" - done - - # Git: identity and signing - FPR=$(echo "$KEY_DATA" | awk -F: '/^fpr:/ {print $10; exit}') - git config user.name "srvcosoitxtech" - git config user.email "oso@inditex.com" - git config user.signingkey "$FPR" - git config commit.gpgsign true - git config tag.gpgsign true - - name: Docker / Execute Kroki server run: | docker run \ @@ -121,7 +96,7 @@ jobs: --health-interval=2s \ --health-retries=5 \ --health-timeout=1s \ - -d yuzutech/kroki + -d yuzutech/kroki@sha256:6980bfb218b48b74ea14b888d9c7e8c032d1cb6325f3292277abdf62483abd9d max_attempts=5 attempt=1 diff --git a/.github/workflows/docs-release.yml b/.github/workflows/docs-release.yml index 8a5c41e3..74c91d8c 100644 --- a/.github/workflows/docs-release.yml +++ b/.github/workflows/docs-release.yml @@ -28,6 +28,9 @@ jobs: name: Docs / Dispatch if: ${{ github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release-docs') }} runs-on: ubuntu-24.04 + # Least-privilege: only re-dispatches this workflow via the default token; no checkout + permissions: + actions: write steps: - name: Trigger / Release Concurrently if: contains(join(github.event.pull_request.labels.*.name, ', '), 'release-type') @@ -47,6 +50,10 @@ jobs: name: Release Docs Process if: ${{ github.event_name == 'workflow_dispatch' && contains(inputs.RELEASE_LABELS, 'release-docs') }} runs-on: ubuntu-24.04 + # Least-privilege: every git push, tag, PR and dispatch uses the App token (steps.app-token); + # the default workflow token only clones the repo. + permissions: + contents: read steps: - uses: actions/create-github-app-token@v3 id: app-token @@ -81,14 +88,15 @@ jobs: - name: Save tool-versions content run: | + DELIMITER="EOF_$(openssl rand -hex 16)" { - echo "TOOL_VERSIONS<> "$GITHUB_ENV" - name: Node / Setup asdf environment - uses: asdf-vm/actions/install@v4 + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4 # https://github.com/asdf-vm/actions/issues/356 if: steps.asdf-cache.outputs.cache-hit != 'true' with: @@ -130,7 +138,7 @@ jobs: --health-interval=2s \ --health-retries=5 \ --health-timeout=1s \ - -d yuzutech/kroki + -d yuzutech/kroki@sha256:6980bfb218b48b74ea14b888d9c7e8c032d1cb6325f3292277abdf62483abd9d max_attempts=5 attempt=1 diff --git a/.github/workflows/docs-verify.yml b/.github/workflows/docs-verify.yml index bcd13e0a..6361ff9d 100644 --- a/.github/workflows/docs-verify.yml +++ b/.github/workflows/docs-verify.yml @@ -8,6 +8,9 @@ on: - 'docs/**' - '.github/workflows/docs-*' +permissions: + contents: read + jobs: unit-tests: name: Docs / Verify @@ -39,49 +42,20 @@ jobs: - name: Save tool-versions content run: | + DELIMITER="EOF_$(openssl rand -hex 16)" { - echo "TOOL_VERSIONS<> "$GITHUB_ENV" - name: Node / Setup asdf environment - uses: asdf-vm/actions/install@v4 + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4 # https://github.com/asdf-vm/actions/issues/356 if: steps.asdf-cache.outputs.cache-hit != 'true' with: tool_versions: ${{ env.TOOL_VERSIONS }} - - name: Prepare committer information - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GPG_PRIVATE_KEY: ${{ secrets.CI_GPG_SECRET_KEY }} - GPG_PASSPHRASE: ${{ secrets.CI_GPG_SECRET_KEY_PASSWORD }} - run: | - git config --global credential.helper store - cat <> ~/.git-credentials - https://ci-user:$GITHUB_TOKEN@github.com - EOT - - # GPG: non-interactive signing setup - mkdir -p ~/.gnupg && chmod 700 ~/.gnupg - printf 'allow-loopback-pinentry\nallow-preset-passphrase\n' > ~/.gnupg/gpg-agent.conf - printf 'use-agent\npinentry-mode loopback\n' > ~/.gnupg/gpg.conf - gpgconf --kill gpg-agent || true - echo "$GPG_PRIVATE_KEY" | gpg --batch --import - KEY_DATA=$(gpg --list-secret-keys --with-colons) - echo "$KEY_DATA" | awk -F: '/^grp:/ {print $10}' | while read -r GRIP; do - /usr/lib/gnupg/gpg-preset-passphrase --preset "$GRIP" <<< "$GPG_PASSPHRASE" - done - - # Git: identity and signing - FPR=$(echo "$KEY_DATA" | awk -F: '/^fpr:/ {print $10; exit}') - git config user.name "srvcosoitxtech" - git config user.email "oso@inditex.com" - git config user.signingkey "$FPR" - git config commit.gpgsign true - git config tag.gpgsign true - - name: Docker / Execute Kroki server run: | docker run \ @@ -91,7 +65,7 @@ jobs: --health-interval=2s \ --health-retries=5 \ --health-timeout=1s \ - -d yuzutech/kroki + -d yuzutech/kroki@sha256:6980bfb218b48b74ea14b888d9c7e8c032d1cb6325f3292277abdf62483abd9d max_attempts=5 attempt=1