diff --git a/.github/actions/build-appimage/action.yml b/.github/actions/build-appimage/action.yml index aaaef4f..e663598 100644 --- a/.github/actions/build-appimage/action.yml +++ b/.github/actions/build-appimage/action.yml @@ -21,9 +21,13 @@ inputs: required: false default: 'OpenSSH-GUI' appimagetool-tag: - description: 'Release tag of appimagetool to download' + description: 'Release tag of appimagetool to download (must match appimagetool-sha256)' required: false - default: 'continuous' + default: '1.9.1' + appimagetool-sha256: + description: 'Expected SHA-256 of appimagetool-x86_64.AppImage for the given tag' + required: false + default: 'ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0' update-information: description: 'zsync update information string (empty disables delta-update embedding)' required: false @@ -49,23 +53,30 @@ runs: - name: Download appimagetool shell: bash + env: + TOOL_TAG: ${{ inputs.appimagetool-tag }} + TOOL_SHA256: ${{ inputs.appimagetool-sha256 }} run: | set -euo pipefail - TOOL_URL="https://github.com/AppImage/appimagetool/releases/download/${{ inputs.appimagetool-tag }}/appimagetool-x86_64.AppImage" + TOOL_URL="https://github.com/AppImage/appimagetool/releases/download/${TOOL_TAG}/appimagetool-x86_64.AppImage" echo "::notice::Downloading appimagetool from $TOOL_URL" wget --progress=dot:giga "$TOOL_URL" -O appimagetool + # The tool is executed with access to the build output - verify it before running it. + echo "${TOOL_SHA256} appimagetool" | sha256sum --check --strict chmod +x appimagetool - name: Assemble and build AppImage id: build shell: bash + env: + BINARY_PATH: ${{ inputs.binary-path }} + VERSION: ${{ inputs.version }} + IS_NIGHTLY: ${{ inputs.is-nightly }} + ASSET_PREFIX: ${{ inputs.asset-prefix }} + UPDATE_INFO: ${{ inputs.update-information }} run: | set -euo pipefail - BINARY_PATH="${{ inputs.binary-path }}" - VERSION="${{ inputs.version }}" - IS_NIGHTLY="${{ inputs.is-nightly }}" - ASSET_PREFIX="${{ inputs.asset-prefix }}" APP_ID="io.github.frequency403.openssh_gui" # Validate all required source files exist before doing any work @@ -123,7 +134,6 @@ runs: # Assemble AppImage (--appimage-extract-and-run bypasses FUSE requirement) APPIMAGE_NAME="${ASSET_PREFIX}-x86_64.AppImage" - UPDATE_INFO="${{ inputs.update-information }}" if [[ -n "$UPDATE_INFO" ]]; then ARCH=x86_64 ./appimagetool --appimage-extract-and-run \ diff --git a/.github/actions/deploy-aur/action.yml b/.github/actions/deploy-aur/action.yml index aeb1e97..e670932 100644 --- a/.github/actions/deploy-aur/action.yml +++ b/.github/actions/deploy-aur/action.yml @@ -41,7 +41,7 @@ runs: - name: Deploy via AUR Action (primary) id: aur_action continue-on-error: true - uses: KSXGitHub/github-actions-deploy-aur@v4.1.3 + uses: KSXGitHub/github-actions-deploy-aur@da03e160361ce01bf087e790b6ffd196d7dccff7 # v4.1.3 with: pkgname: ${{ inputs.pkgname }} pkgbuild: ${{ inputs.pkgbuild }} @@ -56,15 +56,19 @@ runs: - name: Setup SSH key for fallback if: steps.aur_action.outcome == 'failure' shell: bash + env: + SSH_PRIVATE_KEY: ${{ inputs.ssh_private_key }} run: | + set -euo pipefail mkdir -p ~/.ssh - echo "${{ inputs.ssh_private_key }}" > ~/.ssh/aur_key - chmod 600 ~/.ssh/aur_key + chmod 700 ~/.ssh + # Passed via env: inline expression expansion would place the key into the generated script file. + (umask 077 && printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/aur_key) cat >> ~/.ssh/config << 'EOF' Host aur.archlinux.org IdentityFile ~/.ssh/aur_key User aur - StrictHostKeyChecking no + StrictHostKeyChecking accept-new EOF - name: Deploy via manual push (fallback) @@ -72,22 +76,31 @@ runs: continue-on-error: true if: steps.aur_action.outcome == 'failure' shell: bash + # Inputs are passed via env instead of inline expression expansion to prevent script injection + # (e.g. through commit messages). + env: + PKGNAME: ${{ inputs.pkgname }} + PKGBUILD_PATH: ${{ inputs.pkgbuild }} + COMMIT_USERNAME: ${{ inputs.commit_username }} + COMMIT_EMAIL: ${{ inputs.commit_email }} + COMMIT_MESSAGE: ${{ inputs.commit_message }} + FORCE_PUSH: ${{ inputs.force_push }} + REGENERATE_SRCINFO: ${{ inputs.regenerate_srcinfo }} + UPDPKGSUMS: ${{ inputs.updpkgsums }} run: | set -euo pipefail echo "::warning::AUR Action failed, falling back to manual push" - PKGBUILD_DIR="$(dirname "${{ inputs.pkgbuild }}")" - cd /tmp rm -rf aur-deploy mkdir aur-deploy cd aur-deploy - git clone ssh://aur@aur.archlinux.org/${{ inputs.pkgname }}.git . + git clone "ssh://aur@aur.archlinux.org/${PKGNAME}.git" . git checkout -B master origin/master - cp "$GITHUB_WORKSPACE/${{ inputs.pkgbuild }}" ./PKGBUILD + cp "$GITHUB_WORKSPACE/$PKGBUILD_PATH" ./PKGBUILD - if [[ "${{ inputs.updpkgsums }}" == "true" ]]; then + if [[ "$UPDPKGSUMS" == "true" ]]; then if command -v updpkgsums >/dev/null 2>&1; then updpkgsums PKGBUILD else @@ -95,24 +108,24 @@ runs: fi fi - if [[ "${{ inputs.regenerate_srcinfo }}" == "true" ]]; then + if [[ "$REGENERATE_SRCINFO" == "true" ]]; then docker run --rm -v "$(pwd)":/pkg archlinux:latest \ bash -c "useradd -m builder \ && cp /pkg/PKGBUILD /home/builder/ \ && su builder -c 'cd /home/builder && makepkg --printsrcinfo > /pkg/.SRCINFO'" fi - git config user.name "${{ inputs.commit_username }}" - git config user.email "${{ inputs.commit_email }}" + git config user.name "$COMMIT_USERNAME" + git config user.email "$COMMIT_EMAIL" git add PKGBUILD - if [[ "${{ inputs.regenerate_srcinfo }}" == "true" ]]; then + if [[ "$REGENERATE_SRCINFO" == "true" ]]; then git add .SRCINFO fi - git commit -m "${{ inputs.commit_message }}" || true + git commit -m "$COMMIT_MESSAGE" || true - if [[ "${{ inputs.force_push }}" == "true" ]]; then + if [[ "$FORCE_PUSH" == "true" ]]; then git push origin master --force else git push origin master @@ -121,6 +134,8 @@ runs: - name: Verify deployment succeeded if: steps.aur_action.outcome == 'failure' && steps.aur_manual.outcome == 'failure' shell: bash + env: + PKGNAME: ${{ inputs.pkgname }} run: | - echo "::error::Both deployment paths failed for ${{ inputs.pkgname }}" + echo "::error::Both deployment paths failed for $PKGNAME" exit 1 \ No newline at end of file diff --git a/.github/actions/determine-version/action.yml b/.github/actions/determine-version/action.yml index 9060a79..b045d7d 100644 --- a/.github/actions/determine-version/action.yml +++ b/.github/actions/determine-version/action.yml @@ -32,28 +32,30 @@ runs: - name: Extract Version id: extract shell: bash + env: + FILE: ${{ inputs.file-name }} + SUFFIX: ${{ inputs.suffix }} + PROPERTY: ${{ inputs.property }} run: | set -euo pipefail - FILE="${{ inputs.file-name }}" - SUFFIX="${{ inputs.suffix }}" if [[ ! -f "$FILE" ]]; then echo "::error::$FILE not found" exit 1 fi - BUILD_VERSION="$(dotnet msbuild "$FILE" -nologo -getProperty:${{ inputs.property }} -p:VersionSuffix="$SUFFIX" | tr -d '\r')" + BUILD_VERSION="$(dotnet msbuild "$FILE" -nologo -getProperty:"$PROPERTY" -p:VersionSuffix="$SUFFIX" | tr -d '\r')" if [[ -z "$BUILD_VERSION" ]]; then - echo "::error::${{ inputs.property }} not found in $FILE" + echo "::error::$PROPERTY not found in $FILE" exit 1 fi VERSION="${BUILD_VERSION%%-*}" if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "::error::Invalid Property ${{ inputs.property }}: $VERSION" + echo "::error::Invalid Property $PROPERTY: $VERSION" exit 1 fi diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 0f5854f..abb589d 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout merge commit - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: 0 ref: ${{ github.event.pull_request.merge_commit_sha }} @@ -35,7 +35,7 @@ jobs: - name: Create Tag Action id: create_tag_action - uses: rickstaa/action-create-tag@v1 + uses: rickstaa/action-create-tag@a1c7777fcb2fee4f19b0f283ba888afa11678b72 # v1 with: commit_sha: ${{ github.event.pull_request.merge_commit_sha }} github_token: ${{ secrets.PAT_TOKEN }} diff --git a/.github/workflows/build-and-package.yml b/.github/workflows/build-and-package.yml index 65b2f9d..f28772d 100644 --- a/.github/workflows/build-and-package.yml +++ b/.github/workflows/build-and-package.yml @@ -11,6 +11,9 @@ on: type: string default: 'OpenSSH-GUI' +permissions: + contents: read + jobs: build: name: Build for ${{ matrix.target }} @@ -29,10 +32,10 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Setup .NET environment - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 with: global-json-file: global.json cache: true @@ -47,6 +50,8 @@ jobs: - name: Publish application shell: bash + env: + VERSION: ${{ inputs.version }} run: | set -euo pipefail dotnet publish OpenSSH_GUI/OpenSSH_GUI.csproj \ @@ -56,14 +61,16 @@ jobs: -p:PublishSingleFile=true \ -p:PublishReadyToRun=true \ -p:IncludeNativeLibrariesForSelfExtract=true \ - -p:Version="${{ inputs.version }}" + -p:Version="$VERSION" - name: Rename artifact for distribution id: rename shell: bash + env: + ASSET_NAME_PREFIX: ${{ inputs.asset_name_prefix }} run: | set -euo pipefail - ASSET_NAME="${{ inputs.asset_name_prefix }}-${{ matrix.target }}${{ matrix.asset_extension }}" + ASSET_NAME="${ASSET_NAME_PREFIX}-${{ matrix.target }}${{ matrix.asset_extension }}" SOURCE="./publish/OpenSSH_GUI${{ matrix.asset_extension }}" if [[ ! -f "$SOURCE" ]]; then echo "::error::Expected build output not found: $SOURCE" @@ -83,7 +90,7 @@ jobs: - name: Upload AppImage if: matrix.target == 'linux-x64' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: ${{ steps.appimage.outputs.appimage-name }} path: ${{ steps.appimage.outputs.appimage-name }} @@ -91,7 +98,7 @@ jobs: - name: Upload AppImage zsync file if: matrix.target == 'linux-x64' && steps.appimage.outputs.appimage-zsync-name != '' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: ${{ steps.appimage.outputs.appimage-zsync-name }} path: ${{ steps.appimage.outputs.appimage-zsync-name }} @@ -99,7 +106,7 @@ jobs: - name: Upload .desktop file if: matrix.target == 'linux-x64' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: io.github.frequency403.openssh_gui.desktop path: AppDir/usr/share/applications/io.github.frequency403.openssh_gui.desktop @@ -107,14 +114,14 @@ jobs: - name: Upload app icon if: matrix.target == 'linux-x64' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: appicon path: AppDir/appicon.png if-no-files-found: error - name: Upload platform binary - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: ${{ steps.rename.outputs.ASSET_NAME }} path: ./publish/${{ steps.rename.outputs.ASSET_NAME }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7b2d143..a84c6c0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: tag: ${{ steps.version.outputs.tag }} steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: '1' ref: ${{ github.ref }} @@ -52,13 +52,13 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: '1' ref: ${{ github.ref }} - name: Download Windows binary - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: OpenSSH-GUI-win-x64.exe path: ./publish @@ -96,7 +96,7 @@ jobs: run: makensis installer.nsi - name: Upload installer artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: OpenSSH-GUI-${{ needs.prepare.outputs.version }}-Setup path: OpenSSH-GUI-${{ needs.prepare.outputs.version }}-Setup.exe @@ -111,10 +111,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Download all build artifacts - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: path: artifacts/ @@ -163,7 +163,7 @@ jobs: ls -lah release-assets/ - name: Publish GitHub Release - uses: softprops/action-gh-release@v3 + uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3 with: tag_name: ${{ needs.prepare.outputs.tag }} files: release-assets/* @@ -171,7 +171,7 @@ jobs: token: ${{ secrets.PAT_TOKEN }} - name: Trigger deployment - uses: peter-evans/repository-dispatch@v4 + uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4 with: event-type: deploy token: ${{ secrets.PAT_TOKEN }} diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml index 9c5de6f..c3fc4e1 100644 --- a/.github/workflows/deploy-release.yml +++ b/.github/workflows/deploy-release.yml @@ -15,15 +15,20 @@ jobs: steps: - name: Checkout PKGBUILD only - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: ref: ${{ github.event.client_payload.tag }} - name: Update PKGBUILD version + env: + VERSION: ${{ github.event.client_payload.version }} run: | set -euo pipefail - VERSION="${{ github.event.client_payload.version }}" + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Invalid version in dispatch payload: $VERSION" + exit 1 + fi sed -i "s/^pkgver=.*/pkgver=$VERSION/" openssh-gui-bin/PKGBUILD sed -i "s/^pkgrel=.*/pkgrel=1/" openssh-gui-bin/PKGBUILD @@ -48,7 +53,7 @@ jobs: timeout-minutes: 10 steps: - name: Update Winget manifest - uses: vedantmgoyal9/winget-releaser@main + uses: vedantmgoyal9/winget-releaser@a8fff44b123f115ce4c21543edf8b677376fc7e0 # main with: max-versions-to-keep: 5 identifier: frequency403.OpenSSHGUI diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index e95fa7e..6f811d5 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -8,6 +8,9 @@ on: concurrency: group: staging-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + jobs: aur_publish: name: Update GIT AUR package @@ -15,14 +18,14 @@ jobs: timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: '1' sparse-checkout: | openssh-gui-git/PKGBUILD ref: ${{ github.ref }} - name: Deploy - uses: KSXGitHub/github-actions-deploy-aur@v4.1.3 + uses: KSXGitHub/github-actions-deploy-aur@da03e160361ce01bf087e790b6ffd196d7dccff7 # v4.1.3 with: pkgname: openssh-gui-git pkgbuild: openssh-gui-git/PKGBUILD diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 76f7331..bf0b8d6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,9 @@ concurrency: group: tests-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: true +permissions: + contents: read + jobs: test: name: ${{matrix.os}}-tests @@ -23,12 +26,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: ref: ${{ github.ref }} - name: Setup .NET environment - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 with: global-json-file: global.json cache: true diff --git a/.github/workflows/version-guard.yml b/.github/workflows/version-guard.yml index a69e16c..6af375a 100644 --- a/.github/workflows/version-guard.yml +++ b/.github/workflows/version-guard.yml @@ -10,6 +10,9 @@ on: - reopened - edited +permissions: + contents: read + jobs: validate-version: runs-on: ubuntu-latest @@ -19,7 +22,7 @@ jobs: steps: - name: Checkout target (master) - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: ref: master fetch-depth: 0 @@ -30,7 +33,7 @@ jobs: - name: Checkout PR branch - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.sha }} @@ -42,23 +45,25 @@ jobs: - name: Compare versions shell: bash + # The release version is read from the PR branch - pass it via env, never inline into scripts. + env: + MASTER: ${{ steps.master_version.outputs.version }} + RELEASE: ${{ steps.release_version.outputs.version }} run: | - MASTER="${{ steps.master_version.outputs.version }}" - RELEASE="${{ steps.release_version.outputs.version }}" - echo "Master: $MASTER" echo "Release: $RELEASE" python3 - << 'PYEOF' + import os import sys from packaging.version import Version - master = Version("${{ steps.master_version.outputs.version }}") - release = Version("${{ steps.release_version.outputs.version }}") + master = Version(os.environ["MASTER"]) + release = Version(os.environ["RELEASE"]) if release <= master: print(f"Release version {release} must be greater than master {master}") sys.exit(1) print("Version check passed") - PYEOF \ No newline at end of file + PYEOF