├── .editorconfig ├── .github ├── actions │ └── prepare-tag-name │ │ └── action.yml └── workflows │ ├── checksums.yml │ ├── compare.yml │ ├── fetch-build.yml │ ├── hashes.yml │ ├── lint.yml │ ├── offer.yml │ ├── rave.yml │ ├── reproduce.yml │ └── verify.yml ├── .gitignore ├── LICENSE ├── checksums └── 6 │ ├── 6.6.1.md5 │ ├── 6.6.2.md5 │ ├── 6.6.md5 │ ├── 6.7.1.md5 │ ├── 6.7.2.md5 │ ├── 6.7.md5 │ ├── 6.8.1.md5 │ └── 6.8.md5 ├── offers.json └── readme.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | indent_style = tab 12 | 13 | [*.yml] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.github/actions/prepare-tag-name/action.yml: -------------------------------------------------------------------------------- 1 | name: Prepare tag name 2 | description: Prepare the tag name given a WordPress version number 3 | 4 | inputs: 5 | tag: 6 | description: Tag name for WordPress release 7 | required: true 8 | type: string 9 | outputs: 10 | short: 11 | description: Short tag name, eg 6.7 or 6.7.1 12 | value: ${{ steps.tag.outputs.short }} 13 | long: 14 | description: Long tag name, eg. 6.7.0 or 6.7.1 15 | value: ${{ steps.tag.outputs.long }} 16 | branch: 17 | description: Branch number, eg. 6.7 18 | value: ${{ steps.tag.outputs.branch }} 19 | major: 20 | description: Major number, eg. 6 21 | value: ${{ steps.tag.outputs.major }} 22 | 23 | runs: 24 | using: composite 25 | steps: 26 | - name: Prepare tag name 27 | shell: bash 28 | id: tag 29 | env: 30 | LONG_TAG: ${{ inputs.tag }} 31 | run: | #shell 32 | echo short="${LONG_TAG%.0}" >> "$GITHUB_OUTPUT" 33 | echo long="${LONG_TAG}" >> "$GITHUB_OUTPUT" 34 | echo branch="${LONG_TAG%.*}" >> "$GITHUB_OUTPUT" 35 | echo major="${LONG_TAG%%.*}" >> "$GITHUB_OUTPUT" 36 | -------------------------------------------------------------------------------- /.github/workflows/checksums.yml: -------------------------------------------------------------------------------- 1 | name: Verify file checksums 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | tag: 7 | description: Tag name for WordPress release 8 | required: true 9 | type: string 10 | 11 | permissions: {} 12 | 13 | jobs: 14 | checksums: 15 | name: ${{ inputs.tag }} 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: write 19 | pull-requests: write 20 | timeout-minutes: 10 21 | environment: 22 | name: checksums 23 | url: "https://api.wordpress.org/core/checksums/1.0/?version=${{ steps.tag.outputs.short }}" 24 | steps: 25 | - name: Prepare tag name 26 | id: tag 27 | uses: johnbillion/rave-wordpress/.github/actions/prepare-tag-name@trunk 28 | with: 29 | tag: ${{ inputs.tag }} 30 | 31 | - name: Checkout repository 32 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 33 | with: 34 | persist-credentials: true 35 | 36 | - name: Download checksums from wordpress.org 37 | env: 38 | TAG: ${{ steps.tag.outputs.short }} 39 | run: | #shell 40 | wget -O checksums.json "https://api.wordpress.org/core/checksums/1.0/?version=${TAG}" \ 41 | --user-agent="WordPress/${TAG}" 42 | 43 | - name: Download and extract zip from wordpress.org 44 | env: 45 | TAG: ${{ steps.tag.outputs.short }} 46 | run: | #shell 47 | wget "https://wordpress.org/wordpress-${TAG}.zip" \ 48 | --user-agent="WordPress/${TAG}" 49 | unzip -q "wordpress-${TAG}.zip" 50 | 51 | - name: Convert checksums.json into format that can be used by md5sum 52 | env: 53 | MAJOR: ${{ steps.tag.outputs.major }} 54 | TAG: ${{ steps.tag.outputs.short }} 55 | run: | #shell 56 | mkdir -p "checksums/${MAJOR}" 57 | jq -r --arg tag "$TAG" '.checksums[$tag] | to_entries[] | "\(.value) \(.key)"' checksums.json > "checksums/${MAJOR}/${TAG}.md5" 58 | 59 | - name: Verify checksums 60 | env: 61 | MAJOR: ${{ steps.tag.outputs.major }} 62 | TAG: ${{ steps.tag.outputs.short }} 63 | run: | #shell 64 | (cd wordpress && md5sum -c "../checksums/${MAJOR}/${TAG}.md5") 65 | 66 | - name: Create pull request if there are additions or changes 67 | if: github.ref == 'refs/heads/trunk' 68 | uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 69 | with: 70 | add-paths: checksums 71 | title: "Update checksums for WordPress ${{ steps.tag.outputs.short }}" 72 | body: "This pull request updates the checksums for WordPress ${{ steps.tag.outputs.short }}." 73 | branch: "checksums/${{ steps.tag.outputs.short }}" 74 | commit-message: "Update checksums for WordPress ${{ steps.tag.outputs.short }}" 75 | base: trunk 76 | delete-branch: true 77 | -------------------------------------------------------------------------------- /.github/workflows/compare.yml: -------------------------------------------------------------------------------- 1 | name: Compare sources 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | tag: 7 | description: Tag name for WordPress release 8 | required: true 9 | type: string 10 | source: 11 | description: Source for WordPress release 12 | required: true 13 | type: string 14 | 15 | permissions: {} 16 | 17 | jobs: 18 | compare: 19 | name: "${{ inputs.source }}" 20 | runs-on: ubuntu-latest 21 | permissions: {} 22 | timeout-minutes: 10 23 | steps: 24 | - name: Download canonical source artifact 25 | uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # 4.1.8 26 | with: 27 | name: wordpress-${{ inputs.tag }}-develop.svn.wordpress.org 28 | path: develop.svn.wordpress.org 29 | 30 | - name: Download comparison source artifact 31 | uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # 4.1.8 32 | with: 33 | name: wordpress-${{ inputs.tag }}-${{ inputs.source }} 34 | path: ${{ inputs.source }} 35 | 36 | - name: Compare files 37 | env: 38 | SOURCE: ${{ inputs.source }} 39 | run: | #shell 40 | diff --strip-trailing-cr --recursive --color=always develop.svn.wordpress.org "${SOURCE}" 41 | -------------------------------------------------------------------------------- /.github/workflows/fetch-build.yml: -------------------------------------------------------------------------------- 1 | name: Fetch build 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | tag: 7 | description: Tag name for WordPress release 8 | required: true 9 | type: string 10 | build: 11 | description: Build name for WordPress release 12 | required: true 13 | type: string 14 | 15 | permissions: {} 16 | 17 | jobs: 18 | reproduce: 19 | name: "${{ inputs.build }}" 20 | runs-on: ubuntu-latest 21 | permissions: {} 22 | timeout-minutes: 10 23 | steps: 24 | - name: Prepare tag name 25 | id: tag 26 | uses: johnbillion/rave-wordpress/.github/actions/prepare-tag-name@trunk 27 | with: 28 | tag: ${{ inputs.tag }} 29 | 30 | - name: Git checkout core.git.wordpress.org 31 | if: inputs.build == 'core.git.wordpress.org' 32 | env: 33 | TAG: ${{ steps.tag.outputs.long }} 34 | run: | #shell 35 | git clone --no-checkout git://core.git.wordpress.org/ . 36 | git checkout "tags/${TAG}" -b "${TAG}" 37 | 38 | - name: SVN checkout core.svn.wordpress.org 39 | if: inputs.build == 'core.svn.wordpress.org' 40 | env: 41 | TAG: ${{ steps.tag.outputs.short }} 42 | run: | #shell 43 | sudo apt-get update 44 | sudo apt-get install -y subversion 45 | svn --version 46 | svn checkout "https://core.svn.wordpress.org/tags/${TAG}" --ignore-externals . 47 | rm -rf .svn 48 | rm -rf wp-content/plugins/akismet 49 | 50 | - name: Upload artifact 51 | uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # 4.5.0 52 | with: 53 | name: wordpress-${{ steps.tag.outputs.long }}-${{ inputs.build }} 54 | path: . 55 | if-no-files-found: error 56 | -------------------------------------------------------------------------------- /.github/workflows/hashes.yml: -------------------------------------------------------------------------------- 1 | name: Verify package hashes 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | tag: 7 | description: Tag name for WordPress release 8 | required: true 9 | type: string 10 | 11 | permissions: {} 12 | 13 | jobs: 14 | zip-md5: 15 | name: Zip md5 16 | runs-on: ubuntu-latest 17 | permissions: {} 18 | timeout-minutes: 10 19 | environment: 20 | name: zip md5 hash 21 | url: "https://wordpress.org/wordpress-${{ steps.tag.outputs.short }}.zip.md5" 22 | steps: 23 | - name: Prepare tag name 24 | id: tag 25 | uses: johnbillion/rave-wordpress/.github/actions/prepare-tag-name@trunk 26 | with: 27 | tag: ${{ inputs.tag }} 28 | 29 | - name: Download zip and md5 hash from wordpress.org 30 | env: 31 | TAG: ${{ steps.tag.outputs.short }} 32 | run: | #shell 33 | wget -O package.zip "https://wordpress.org/wordpress-${TAG}.zip" \ 34 | --user-agent="WordPress/${TAG}" 35 | wget -O package.zip.md5 "https://wordpress.org/wordpress-${TAG}.zip.md5" \ 36 | --user-agent="WordPress/${TAG}" 37 | 38 | - name: Check the md5 hash 39 | run: | #shell 40 | echo "$(cat package.zip.md5) package.zip" | md5sum -c - 41 | 42 | zip-sha1: 43 | name: Zip sha1 44 | runs-on: ubuntu-latest 45 | permissions: {} 46 | timeout-minutes: 10 47 | environment: 48 | name: zip sha1 hash 49 | url: "https://wordpress.org/wordpress-${{ steps.tag.outputs.short }}.zip.sha1" 50 | steps: 51 | - name: Prepare tag name 52 | id: tag 53 | uses: johnbillion/rave-wordpress/.github/actions/prepare-tag-name@trunk 54 | with: 55 | tag: ${{ inputs.tag }} 56 | 57 | - name: Download zip and sha1 hash from wordpress.org 58 | env: 59 | TAG: ${{ steps.tag.outputs.short }} 60 | run: | #shell 61 | wget -O package.zip "https://wordpress.org/wordpress-${TAG}.zip" \ 62 | --user-agent="WordPress/${TAG}" 63 | wget -O package.zip.sha1 "https://wordpress.org/wordpress-${TAG}.zip.sha1" \ 64 | --user-agent="WordPress/${TAG}" 65 | 66 | - name: Check the sha1 hash 67 | run: | #shell 68 | echo "$(cat package.zip.sha1) package.zip" | sha1sum -c - 69 | 70 | tar-md5: 71 | name: Tar md5 72 | runs-on: ubuntu-latest 73 | permissions: {} 74 | timeout-minutes: 10 75 | environment: 76 | name: tar md5 hash 77 | url: "https://wordpress.org/wordpress-${{ steps.tag.outputs.short }}.tar.gz.md5" 78 | steps: 79 | - name: Prepare tag name 80 | id: tag 81 | uses: johnbillion/rave-wordpress/.github/actions/prepare-tag-name@trunk 82 | with: 83 | tag: ${{ inputs.tag }} 84 | 85 | - name: Download tar.gz and md5 hash from wordpress.org 86 | env: 87 | TAG: ${{ steps.tag.outputs.short }} 88 | run: | #shell 89 | wget -O package.tar.gz "https://wordpress.org/wordpress-${TAG}.tar.gz" \ 90 | --user-agent="WordPress/${TAG}" 91 | wget -O package.tar.gz.md5 "https://wordpress.org/wordpress-${TAG}.tar.gz.md5" \ 92 | --user-agent="WordPress/${TAG}" 93 | 94 | - name: Check the md5 hash 95 | run: | #shell 96 | echo "$(cat package.tar.gz.md5) package.tar.gz" | md5sum -c - 97 | 98 | tar-sha1: 99 | name: Tar sha1 100 | runs-on: ubuntu-latest 101 | permissions: {} 102 | timeout-minutes: 10 103 | environment: 104 | name: tar sha1 hash 105 | url: "https://wordpress.org/wordpress-${{ steps.tag.outputs.short }}.tar.gz.sha1" 106 | steps: 107 | - name: Prepare tag name 108 | id: tag 109 | uses: johnbillion/rave-wordpress/.github/actions/prepare-tag-name@trunk 110 | with: 111 | tag: ${{ inputs.tag }} 112 | 113 | - name: Download tar.gz and sha1 hash from wordpress.org 114 | env: 115 | TAG: ${{ steps.tag.outputs.short }} 116 | run: | #shell 117 | wget -O package.tar.gz "https://wordpress.org/wordpress-${TAG}.tar.gz" \ 118 | --user-agent="WordPress/${TAG}" 119 | wget -O package.tar.gz.sha1 "https://wordpress.org/wordpress-${TAG}.tar.gz.sha1" \ 120 | --user-agent="WordPress/${TAG}" 121 | 122 | - name: Check the sha1 hash 123 | run: | #shell 124 | echo "$(cat package.tar.gz.sha1) package.tar.gz" | sha1sum -c - 125 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow 2 | 3 | name: Lint 4 | on: 5 | pull_request: 6 | paths: 7 | # Only run when changes are made to workflow files. 8 | - '.github/workflows/**' 9 | push: 10 | branches: 11 | - trunk 12 | paths: 13 | # Only run when changes are made to workflow files. 14 | - '.github/workflows/**' 15 | 16 | permissions: {} 17 | 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ github.ref }} 20 | cancel-in-progress: true 21 | 22 | jobs: 23 | lint: 24 | name: Lint 25 | permissions: 26 | security-events: write 27 | actions: read 28 | contents: read 29 | uses: johnbillion/plugin-infrastructure/.github/workflows/reusable-workflow-lint.yml@1264303fa2e8b81ac9b49265cd96c96947eaacd0 30 | -------------------------------------------------------------------------------- /.github/workflows/offer.yml: -------------------------------------------------------------------------------- 1 | name: Verify the update offers 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | tag: 7 | description: Tag name for WordPress release 8 | required: true 9 | type: string 10 | 11 | permissions: {} 12 | 13 | jobs: 14 | verify: 15 | name: ${{ inputs.tag }} 16 | runs-on: ubuntu-latest 17 | permissions: {} 18 | timeout-minutes: 10 19 | environment: 20 | name: offers 21 | url: https://api.wordpress.org/core/version-check/1.7/ 22 | steps: 23 | - name: Prepare tag name 24 | id: tag 25 | uses: johnbillion/rave-wordpress/.github/actions/prepare-tag-name@trunk 26 | with: 27 | tag: ${{ inputs.tag }} 28 | 29 | - name: Download artifact 30 | uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # 4.1.8 31 | with: 32 | name: offers 33 | 34 | - name: Verify the offer URLs 35 | env: 36 | TAG: ${{ steps.tag.outputs.short }} 37 | run: | #shell 38 | EXPECTED_URL_FULL="https://downloads.w.org/release/wordpress-${TAG}.zip" 39 | EXPECTED_URL_NO_CONTENT="https://downloads.w.org/release/wordpress-${TAG}-no-content.zip" 40 | MATCHING_OBJECT=$(jq --arg tag "$TAG" '.[] | select(.version == $tag)' offers.json) 41 | 42 | if [ -z "$MATCHING_OBJECT" ]; then 43 | echo "No matching version found for tag $TAG" 44 | exit 1 45 | fi 46 | 47 | DOWNLOAD_URL=$(echo "$MATCHING_OBJECT" | jq -r '.download') 48 | PACKAGE_URL=$(echo "$MATCHING_OBJECT" | jq -r '.packages.full') 49 | NO_CONTENT_URL=$(echo "$MATCHING_OBJECT" | jq -r '.packages.no_content') 50 | 51 | if [ "$DOWNLOAD_URL" != "$EXPECTED_URL_FULL" ]; then 52 | echo "Download URL $DOWNLOAD_URL does not match the expected $EXPECTED_URL_FULL" 53 | exit 1 54 | fi 55 | 56 | if [ "$PACKAGE_URL" != "$EXPECTED_URL_FULL" ]; then 57 | echo "Package URL $PACKAGE_URL does not match the expected $EXPECTED_URL_FULL" 58 | exit 1 59 | fi 60 | 61 | if [ "$NO_CONTENT_URL" != "$EXPECTED_URL_NO_CONTENT" ]; then 62 | echo "No-content URL $NO_CONTENT_URL does not match the expected $EXPECTED_URL_NO_CONTENT" 63 | exit 1 64 | fi 65 | -------------------------------------------------------------------------------- /.github/workflows/rave.yml: -------------------------------------------------------------------------------- 1 | name: Reproduce and verify packages 2 | 3 | on: 4 | push: 5 | branches: 6 | - trunk 7 | pull_request: 8 | branches: 9 | - '**' 10 | schedule: 11 | # Hourly schedule. 12 | # 13 | # ┌───────────── minute (0 - 59) 14 | # │ ┌────────── hour (0 - 23) 15 | # │ │ ┌─────── day of the month (1 - 31) 16 | # │ │ │ ┌──── month (1 - 12 or JAN-DEC) 17 | # │ │ │ │ ┌─ day of the week (0 - 6 or SUN-SAT) 18 | # │ │ │ │ │ 19 | # │ │ │ │ │ 20 | # │ │ │ │ │ 21 | - cron: '0 * * * *' 22 | workflow_dispatch: 23 | inputs: 24 | version: 25 | description: Full semver tag name for WordPress release in `x.y.z` format. 26 | required: true 27 | type: string 28 | 29 | permissions: {} 30 | 31 | jobs: 32 | versions: 33 | name: Determine branch numbers 34 | runs-on: ubuntu-latest 35 | permissions: 36 | contents: write 37 | pull-requests: write 38 | outputs: 39 | versions: ${{ steps.fetch.outputs.versions }} 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 43 | with: 44 | persist-credentials: true 45 | 46 | - name: Fetch offers from version check API 47 | run: | #shell 48 | # Fetches the latest release from each of the three latest minor branches 49 | wget -q -O - https://api.wordpress.org/core/version-check/1.7/ \ 50 | --user-agent='WordPress/6.8' \ 51 | | jq ' 52 | .offers 53 | | map(select(.response == "autoupdate")) 54 | | group_by(.version | capture("(?\\d+)\\.(?\\d+)").minor) 55 | | map(max_by(.version | split(".") | map(tonumber))) 56 | | sort_by(.version | split(".") | map(tonumber) | .[:2]) 57 | | reverse 58 | | .[0:3] 59 | ' > offers.json 60 | 61 | - name: Populate versions matrix from API data 62 | if: ${{ github.event_name != 'workflow_dispatch' }} 63 | run: | #shell 64 | # Map each version and if the number is in major.minor syntax, append a .0 65 | versions=$(jq -c '. | map(.version | capture("(?\\d+)\\.(?\\d+)(\\.(?\\d+))?") | .major + "." + .minor + "." + (.patch // "0") )' offers.json) 66 | echo "$versions" > versions.json 67 | 68 | - name: Fetch version matrix from workflow input 69 | if: ${{ github.event_name == 'workflow_dispatch' }} 70 | env: 71 | VERSION: ${{ github.event.inputs.version }} 72 | run: | #shell 73 | jq --null-input --compact-output --arg version "${VERSION}" '[$version]' > versions.json 74 | 75 | - name: Output versions from versions.json 76 | id: fetch 77 | run: | #shell 78 | echo versions="$(cat versions.json)" >> "$GITHUB_OUTPUT" 79 | 80 | - name: Save versions.json 81 | uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # 4.5.0 82 | with: 83 | name: versions 84 | path: versions.json 85 | if-no-files-found: error 86 | 87 | - name: Save offers.json 88 | uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # 4.5.0 89 | with: 90 | name: offers 91 | path: offers.json 92 | if-no-files-found: error 93 | 94 | - name: Create pull request if there are changes to offers.json 95 | if: github.ref == 'refs/heads/trunk' 96 | uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 97 | with: 98 | add-paths: offers.json 99 | title: "Update offers.json" 100 | body: "This pull request updates the offers.json file." 101 | branch: offers 102 | commit-message: "Update offers.json" 103 | base: trunk 104 | delete-branch: true 105 | 106 | reproduce: 107 | name: "Reproduce: ${{ matrix.tag }}" 108 | permissions: {} 109 | needs: 110 | - versions 111 | strategy: 112 | matrix: 113 | tag: ${{ fromJson(needs.versions.outputs.versions) }} 114 | source: 115 | - develop.svn.wordpress.org 116 | - develop.git.wordpress.org 117 | - github-wordpress-develop 118 | include: 119 | # Only reproduce the latest tag from core.trac.wordpress.org due to rate limiting 120 | - tag: ${{ fromJson(needs.versions.outputs.versions)[0] }} 121 | source: core.trac.wordpress.org 122 | fail-fast: false 123 | uses: ./.github/workflows/reproduce.yml 124 | with: 125 | tag: ${{ matrix.tag }} 126 | source: ${{ matrix.source }} 127 | 128 | fetch-build: 129 | name: "Fetch builds: ${{ matrix.tag }}" 130 | permissions: {} 131 | needs: 132 | - versions 133 | strategy: 134 | matrix: 135 | tag: ${{ fromJson(needs.versions.outputs.versions) }} 136 | build: 137 | - core.svn.wordpress.org 138 | - core.git.wordpress.org 139 | fail-fast: false 140 | uses: ./.github/workflows/fetch-build.yml 141 | with: 142 | tag: ${{ matrix.tag }} 143 | build: ${{ matrix.build }} 144 | 145 | compare: 146 | name: "Compare: ${{ matrix.tag }}" 147 | permissions: {} 148 | needs: 149 | - reproduce 150 | - fetch-build 151 | - versions 152 | strategy: 153 | matrix: 154 | tag: ${{ fromJson(needs.versions.outputs.versions) }} 155 | source: 156 | - develop.git.wordpress.org 157 | - github-wordpress-develop 158 | - core.git.wordpress.org 159 | - core.svn.wordpress.org 160 | include: 161 | # Only compare the latest tag from core.trac.wordpress.org due to rate limiting 162 | - tag: ${{ fromJson(needs.versions.outputs.versions)[0] }} 163 | source: core.trac.wordpress.org 164 | fail-fast: false 165 | uses: ./.github/workflows/compare.yml 166 | with: 167 | tag: ${{ matrix.tag }} 168 | source: ${{ matrix.source }} 169 | 170 | offers: 171 | name: ${{ matrix.label }} 172 | if: ${{ github.event_name != 'workflow_dispatch' }} 173 | permissions: {} 174 | needs: 175 | - versions 176 | strategy: 177 | matrix: 178 | tag: ${{ fromJson(needs.versions.outputs.versions) }} 179 | label: 180 | - Verify offers 181 | fail-fast: false 182 | uses: ./.github/workflows/offer.yml 183 | with: 184 | tag: ${{ matrix.tag }} 185 | 186 | verify-packages: 187 | name: "Verify packages: ${{ matrix.tag }}" 188 | permissions: 189 | contents: read 190 | needs: 191 | - reproduce 192 | - versions 193 | strategy: 194 | matrix: 195 | tag: ${{ fromJson(needs.versions.outputs.versions) }} 196 | package: 197 | - wordpress.org-zip 198 | - wordpress.org-tar 199 | - downloads.wordpress.org-zip 200 | - downloads.wordpress.org-tar 201 | - downloads.w.org-zip 202 | - downloads.w.org-tar 203 | - github-zip 204 | - github-tar 205 | - docker-wordpress 206 | - wpengine-zip 207 | - aspirecloud-zip 208 | - roots-wordpress-full 209 | - johnpbloch-wordpress 210 | include: 211 | # Only verify the latest tag from build.trac.wordpress.org due to rate limiting 212 | - tag: ${{ fromJson(needs.versions.outputs.versions)[0] }} 213 | package: build.trac.wordpress.org 214 | fail-fast: false 215 | uses: ./.github/workflows/verify.yml 216 | with: 217 | tag: ${{ matrix.tag }} 218 | package: ${{ matrix.package }} 219 | 220 | verify-latest-package: 221 | name: "Verify packages: ${{ matrix.tag }}" 222 | if: ${{ github.event_name != 'workflow_dispatch' }} 223 | permissions: 224 | contents: read 225 | needs: 226 | - reproduce 227 | strategy: 228 | matrix: 229 | tag: 230 | - 'latest' 231 | package: 232 | - wordpress.org-zip 233 | - wordpress.org-tar 234 | fail-fast: false 235 | uses: ./.github/workflows/verify.yml 236 | with: 237 | tag: ${{ matrix.tag }} 238 | package: ${{ matrix.package }} 239 | 240 | verify-hashes: 241 | name: "Verify hashes: ${{ matrix.tag }}" 242 | permissions: {} 243 | needs: 244 | - versions 245 | strategy: 246 | matrix: 247 | tag: ${{ fromJson(needs.versions.outputs.versions) }} 248 | fail-fast: false 249 | uses: ./.github/workflows/hashes.yml 250 | with: 251 | tag: ${{ matrix.tag }} 252 | 253 | verify-checksums: 254 | name: ${{ matrix.label }} 255 | permissions: 256 | contents: write 257 | pull-requests: write 258 | needs: 259 | - versions 260 | - verify-packages 261 | strategy: 262 | matrix: 263 | tag: ${{ fromJson(needs.versions.outputs.versions) }} 264 | label: 265 | - Verify checksums 266 | fail-fast: false 267 | uses: ./.github/workflows/checksums.yml 268 | with: 269 | tag: ${{ matrix.tag }} 270 | -------------------------------------------------------------------------------- /.github/workflows/reproduce.yml: -------------------------------------------------------------------------------- 1 | name: Reproduce from source 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | tag: 7 | description: Tag name for WordPress release 8 | required: true 9 | type: string 10 | source: 11 | description: Source for WordPress release 12 | required: true 13 | type: string 14 | 15 | permissions: {} 16 | 17 | jobs: 18 | reproduce: 19 | name: "${{ inputs.source }}" 20 | runs-on: ubuntu-latest 21 | permissions: {} 22 | timeout-minutes: 10 23 | steps: 24 | - name: Prepare tag name 25 | id: tag 26 | uses: johnbillion/rave-wordpress/.github/actions/prepare-tag-name@trunk 27 | with: 28 | tag: ${{ inputs.tag }} 29 | 30 | - name: Git checkout github.com/wordpress/wordpress-develop 31 | if: inputs.source == 'github-wordpress-develop' 32 | env: 33 | TAG: ${{ steps.tag.outputs.long }} 34 | run: | #shell 35 | git clone --no-checkout https://github.com/wordpress/wordpress-develop/ . 36 | git checkout "tags/${TAG}" -b "${TAG}" 37 | 38 | - name: Git checkout develop.git.wordpress.org 39 | if: inputs.source == 'develop.git.wordpress.org' 40 | env: 41 | TAG: ${{ steps.tag.outputs.long }} 42 | run: | #shell 43 | git clone --no-checkout git://develop.git.wordpress.org/ . 44 | git checkout "tags/${TAG}" -b "${TAG}" 45 | 46 | - name: SVN checkout develop.svn.wordpress.org 47 | if: inputs.source == 'develop.svn.wordpress.org' 48 | env: 49 | TAG: ${{ steps.tag.outputs.short }} 50 | run: | #shell 51 | sudo apt-get update 52 | sudo apt-get install -y subversion 53 | svn --version 54 | svn checkout "https://develop.svn.wordpress.org/tags/${TAG}" --ignore-externals . 55 | rm -rf .svn 56 | 57 | - name: Download zip from core.trac.wordpress.org 58 | if: inputs.source == 'core.trac.wordpress.org' 59 | env: 60 | TAG: ${{ steps.tag.outputs.short }} 61 | run: | #shell 62 | wget -O package.zip "https://core.trac.wordpress.org/browser/tags/${TAG}?format=zip" \ 63 | --user-agent="WordPress/${TAG}" 64 | unzip -q package.zip 65 | shopt -s dotglob 66 | mv "${TAG}"/* . 67 | shopt -u dotglob 68 | rm -rf "${TAG}" 69 | rm package.zip 70 | 71 | - name: Log file list 72 | run: | #shell 73 | ls -la 74 | 75 | - name: Set up Node.js 76 | uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # 4.1.0 77 | with: 78 | node-version-file: .nvmrc 79 | 80 | - name: Log debug information 81 | run: | #shell 82 | npm --version 83 | node --version 84 | git --version 85 | 86 | - name: Install npm Dependencies 87 | run: | #shell 88 | npm ci 89 | 90 | - name: Update Twemoji processing for debugging 91 | run: | #shell 92 | sed -i "s/'Unable to fetch Twemoji file list'/files.stderr.toString()/" Gruntfile.js 93 | 94 | - name: Run Emoji precommit task 95 | env: 96 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 97 | run: | #shell 98 | npm run grunt precommit:emoji 99 | 100 | - name: Build WordPress 101 | run: | #shell 102 | npm run build 103 | 104 | - name: Verify update check URL is as expected 105 | run: | #shell 106 | grep --fixed-strings "://api.wordpress.org/core/version-check/1.7/" build/wp-includes/update.php 107 | 108 | - name: Upload artifact 109 | uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # 4.5.0 110 | with: 111 | name: wordpress-${{ steps.tag.outputs.long }}-${{ inputs.source }} 112 | path: build 113 | if-no-files-found: error 114 | -------------------------------------------------------------------------------- /.github/workflows/verify.yml: -------------------------------------------------------------------------------- 1 | name: Verify package 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | tag: 7 | description: Tag name for WordPress release 8 | required: true 9 | type: string 10 | package: 11 | description: Package for WordPress release 12 | required: true 13 | type: string 14 | 15 | permissions: {} 16 | 17 | env: 18 | PACKAGE_URL: "" 19 | 20 | jobs: 21 | verify: 22 | name: "${{ inputs.package }}" 23 | runs-on: ubuntu-latest 24 | permissions: {} 25 | timeout-minutes: 10 26 | environment: 27 | name: ${{ inputs.package }} 28 | url: ${{ env.PACKAGE_URL }} 29 | steps: 30 | - name: Prepare tag name 31 | id: tag 32 | uses: johnbillion/rave-wordpress/.github/actions/prepare-tag-name@trunk 33 | with: 34 | tag: ${{ inputs.tag }} 35 | 36 | - name: Prepare URL 37 | id: url 38 | env: 39 | PACKAGE: ${{ inputs.package }} 40 | TAG_SHORT: ${{ steps.tag.outputs.short }} 41 | TAG_LONG: ${{ steps.tag.outputs.long }} 42 | run: | #shell 43 | case "$PACKAGE" in 44 | github-zip) 45 | PACKAGE_URL="https://github.com/WordPress/WordPress/archive/refs/tags/${TAG_SHORT}.zip" 46 | ;; 47 | github-tar) 48 | PACKAGE_URL="https://github.com/WordPress/WordPress/archive/refs/tags/${TAG_SHORT}.tar.gz" 49 | ;; 50 | wordpress.org-zip) 51 | PACKAGE_URL="https://wordpress.org/wordpress-${TAG_SHORT}.zip" 52 | ;; 53 | wordpress.org-tar) 54 | PACKAGE_URL="https://wordpress.org/wordpress-${TAG_SHORT}.tar.gz" 55 | ;; 56 | downloads.wordpress.org-zip) 57 | PACKAGE_URL="https://downloads.wordpress.org/release/wordpress-${TAG_SHORT}.zip" 58 | ;; 59 | downloads.wordpress.org-tar) 60 | PACKAGE_URL="https://downloads.wordpress.org/release/wordpress-${TAG_SHORT}.tar.gz" 61 | ;; 62 | downloads.w.org-zip) 63 | PACKAGE_URL="https://downloads.w.org/release/wordpress-${TAG_SHORT}.zip" 64 | ;; 65 | downloads.w.org-tar) 66 | PACKAGE_URL="https://downloads.w.org/release/wordpress-${TAG_SHORT}.tar.gz" 67 | ;; 68 | build.trac.wordpress.org) 69 | PACKAGE_URL="https://build.trac.wordpress.org/browser/tags/${TAG_SHORT}?format=zip" 70 | ;; 71 | docker-wordpress) 72 | PACKAGE_URL="https://hub.docker.com/_/wordpress/tags?name=${TAG_LONG}&ordering=-name" 73 | ;; 74 | wpengine-zip) 75 | PACKAGE_URL="https://core-updates.wpengine.com/wordpress-${TAG_SHORT}.zip" 76 | ;; 77 | aspirecloud-zip) 78 | PACKAGE_URL="https://api.aspirecloud.net/download/wordpress-${TAG_SHORT}.zip" 79 | ;; 80 | roots-wordpress-full) 81 | PACKAGE_URL="https://packagist.org/packages/roots/wordpress-full#${TAG_SHORT}" 82 | ;; 83 | johnpbloch-wordpress) 84 | PACKAGE_URL="https://packagist.org/packages/johnpbloch/wordpress#${TAG_SHORT}" 85 | ;; 86 | esac 87 | echo PACKAGE_URL="$PACKAGE_URL" >> "$GITHUB_ENV" 88 | 89 | - name: Download artifact 90 | if: inputs.tag != 'latest' 91 | uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # 4.1.8 92 | with: 93 | name: wordpress-${{ steps.tag.outputs.long }}-develop.svn.wordpress.org 94 | path: source 95 | 96 | - name: Download versions artifact 97 | if: inputs.tag == 'latest' 98 | uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # 4.1.8 99 | with: 100 | name: versions 101 | 102 | - name: Extract the latest version number from versions.json 103 | if: inputs.tag == 'latest' 104 | id: extract 105 | run: | #shell 106 | echo latest="$(jq -r '.[0]' versions.json)" >> "$GITHUB_OUTPUT" 107 | 108 | - name: Download latest artifact 109 | if: inputs.tag == 'latest' 110 | uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # 4.1.8 111 | with: 112 | name: "wordpress-${{ steps.extract.outputs.latest }}-develop.svn.wordpress.org" 113 | path: source 114 | 115 | - name: Get zip file from GitHub 116 | if: inputs.package == 'github-zip' 117 | env: 118 | TAG: ${{ steps.tag.outputs.short }} 119 | run: | #shell 120 | wget -O package.zip "$PACKAGE_URL" \ 121 | --user-agent="WordPress/${TAG}" 122 | unzip -q package.zip -d wordpress 123 | mv "wordpress/WordPress-${TAG}" package 124 | 125 | - name: Get tar file from GitHub 126 | if: inputs.package == 'github-tar' 127 | env: 128 | TAG: ${{ steps.tag.outputs.short }} 129 | run: | #shell 130 | wget -O package.tar.gz "$PACKAGE_URL" \ 131 | --user-agent="WordPress/${TAG}" 132 | mkdir wordpress 133 | tar -xzf package.tar.gz -C wordpress 134 | mv "wordpress/WordPress-${TAG}" package 135 | 136 | - name: Get zip file from wordpress.org 137 | if: inputs.package == 'wordpress.org-zip' && inputs.tag != 'latest' 138 | env: 139 | TAG: ${{ steps.tag.outputs.short }} 140 | run: | #shell 141 | wget -O package.zip "$PACKAGE_URL" \ 142 | --user-agent="WordPress/${TAG}" 143 | unzip -q package.zip -d wordpress 144 | mv wordpress/wordpress package 145 | 146 | - name: Get tar file from wordpress.org 147 | if: inputs.package == 'wordpress.org-tar' && inputs.tag != 'latest' 148 | env: 149 | TAG: ${{ steps.tag.outputs.short }} 150 | run: | #shell 151 | wget -O package.tar.gz "$PACKAGE_URL" \ 152 | --user-agent="WordPress/${TAG}" 153 | mkdir wordpress 154 | tar -xzf package.tar.gz -C wordpress 155 | mv wordpress/wordpress package 156 | 157 | - name: Get latest zip file from wordpress.org 158 | if: inputs.package == 'wordpress.org-zip' && inputs.tag == 'latest' 159 | env: 160 | TAG: ${{ steps.tag.outputs.short }} 161 | run: | #shell 162 | wget -O package.zip "https://wordpress.org/latest.zip" \ 163 | --user-agent="WordPress/${TAG}" 164 | unzip -q package.zip -d wordpress 165 | mv wordpress/wordpress package 166 | 167 | - name: Get latest tar file from wordpress.org 168 | if: inputs.package == 'wordpress.org-tar' && inputs.tag == 'latest' 169 | env: 170 | TAG: ${{ steps.tag.outputs.short }} 171 | run: | #shell 172 | wget -O package.tar.gz "https://wordpress.org/latest.tar.gz" \ 173 | --user-agent="WordPress/${TAG}" 174 | mkdir wordpress 175 | tar -xzf package.tar.gz -C wordpress 176 | mv wordpress/wordpress package 177 | 178 | - name: Get zip file from downloads.wordpress.org 179 | if: inputs.package == 'downloads.wordpress.org-zip' 180 | env: 181 | TAG: ${{ steps.tag.outputs.short }} 182 | run: | #shell 183 | wget -O package.zip "$PACKAGE_URL" \ 184 | --user-agent="WordPress/${TAG}" 185 | unzip -q package.zip -d wordpress 186 | mv wordpress/wordpress package 187 | 188 | - name: Get tar file from downloads.wordpress.org 189 | if: inputs.package == 'downloads.wordpress.org-tar' 190 | env: 191 | TAG: ${{ steps.tag.outputs.short }} 192 | run: | #shell 193 | wget -O package.tar.gz "$PACKAGE_URL" \ 194 | --user-agent="WordPress/${TAG}" 195 | mkdir wordpress 196 | tar -xzf package.tar.gz -C wordpress 197 | mv wordpress/wordpress package 198 | 199 | - name: Get zip file from downloads.w.org 200 | if: inputs.package == 'downloads.w.org-zip' 201 | env: 202 | TAG: ${{ steps.tag.outputs.short }} 203 | run: | #shell 204 | wget -O package.zip "$PACKAGE_URL" \ 205 | --user-agent="WordPress/${TAG}" 206 | unzip -q package.zip -d wordpress 207 | mv wordpress/wordpress package 208 | 209 | - name: Get tar file from downloads.w.org 210 | if: inputs.package == 'downloads.w.org-tar' 211 | env: 212 | TAG: ${{ steps.tag.outputs.short }} 213 | run: | #shell 214 | wget -O package.tar.gz "$PACKAGE_URL" \ 215 | --user-agent="WordPress/${TAG}" 216 | mkdir wordpress 217 | tar -xzf package.tar.gz -C wordpress 218 | mv wordpress/wordpress package 219 | 220 | - name: Download zip from build.trac.wordpress.org 221 | if: inputs.package == 'build.trac.wordpress.org' 222 | env: 223 | TAG: ${{ steps.tag.outputs.short }} 224 | run: | #shell 225 | wget -O package.zip "$PACKAGE_URL" \ 226 | --user-agent="WordPress/${TAG}" 227 | unzip -q package.zip -d wordpress 228 | mv "wordpress/${TAG}" package 229 | 230 | - name: Download Docker image 231 | if: inputs.package == 'docker-wordpress' 232 | env: 233 | TAG: ${{ steps.tag.outputs.long }} 234 | run: | #shell 235 | yq --null-input '{"services":{"wordpress":{"image":"wordpress:"+strenv(TAG),"container_name":"wordpress","volumes":["./package:/var/www/html"]}}}' > docker-compose.yml 236 | docker compose up -d wordpress 237 | docker compose run --rm wordpress -- ls -al /var/www/html 238 | docker compose run --rm wordpress -- rm /var/www/html/.htaccess 239 | docker compose run --rm wordpress -- rm /var/www/html/wp-config-docker.php 240 | docker compose run --rm wordpress -- rm -r /var/www/html/wp-content/plugins/akismet 241 | 242 | - name: Download zip from core-updates.wpengine.com 243 | if: inputs.package == 'wpengine-zip' 244 | env: 245 | TAG: ${{ steps.tag.outputs.short }} 246 | run: | #shell 247 | wget -O package.zip "$PACKAGE_URL" \ 248 | --user-agent="WordPress/${TAG}" 249 | unzip -q package.zip -d wordpress 250 | mv wordpress/wordpress package 251 | 252 | - name: Download zip from api.aspirecloud.net 253 | if: inputs.package == 'aspirecloud-zip' 254 | env: 255 | TAG: ${{ steps.tag.outputs.short }} 256 | run: | #shell 257 | wget -O package.zip "$PACKAGE_URL" \ 258 | --user-agent="WordPress/${TAG}" 259 | unzip -q package.zip -d wordpress 260 | mv wordpress/wordpress package 261 | 262 | - name: Download roots/wordpress-full 263 | if: inputs.package == 'roots-wordpress-full' 264 | env: 265 | TAG: ${{ steps.tag.outputs.long }} 266 | run: | #shell 267 | composer require "roots/wordpress-full:${TAG}" --prefer-dist --no-progress 268 | mv vendor/roots/wordpress-full package 269 | 270 | - name: Download johnpbloch/wordpress 271 | if: inputs.package == 'johnpbloch-wordpress' 272 | env: 273 | TAG: ${{ steps.tag.outputs.long }} 274 | run: | #shell 275 | composer require "johnpbloch/wordpress:${TAG}" --prefer-dist --no-progress --no-plugins 276 | mv vendor/johnpbloch/wordpress-core package 277 | rm package/composer.json 278 | 279 | - name: Handle anomalies with build files in TT and TT1 280 | if: inputs.package == 'github-zip' || inputs.package == 'github-tar' || inputs.package == 'build.trac.wordpress.org' 281 | run: | #shell 282 | rm package/wp-content/themes/twentytwenty/.npmrc 283 | rm package/wp-content/themes/twentytwenty/.stylelintrc.json 284 | rm package/wp-content/themes/twentytwentyone/.npmrc 285 | rm package/wp-content/themes/twentytwentyone/.stylelintignore 286 | rm package/wp-content/themes/twentytwentyone/.stylelintrc-css.json 287 | rm package/wp-content/themes/twentytwentyone/.stylelintrc.json 288 | 289 | - name: Handle known differences in bundled themes 290 | if: inputs.package != 'github-zip' && inputs.package != 'github-tar' && inputs.package != 'build.trac.wordpress.org' 291 | env: 292 | TAG: ${{ inputs.tag }} 293 | BRANCH: ${{ steps.tag.outputs.branch }} 294 | run: | #shell 295 | rm -rf source/wp-content/themes/twentyten 296 | rm -rf source/wp-content/themes/twentyeleven 297 | rm -rf source/wp-content/themes/twentytwelve 298 | rm -rf source/wp-content/themes/twentythirteen 299 | rm -rf source/wp-content/themes/twentyfourteen 300 | rm -rf source/wp-content/themes/twentyfifteen 301 | rm -rf source/wp-content/themes/twentysixteen 302 | rm -rf source/wp-content/themes/twentyseventeen 303 | if [[ "${TAG}" == "latest" ]] || dpkg --compare-versions "${BRANCH}" ge "6.4"; then 304 | rm -rf source/wp-content/themes/twentynineteen 305 | rm -rf source/wp-content/themes/twentytwenty 306 | rm -rf source/wp-content/themes/twentytwentyone 307 | fi 308 | if [[ "${TAG}" == "latest" ]] || dpkg --compare-versions "${BRANCH}" ge "6.7"; then 309 | rm -rf source/wp-content/themes/twentytwentytwo 310 | fi 311 | 312 | - name: Handle known differences in bundled plugins 313 | run: | #shell 314 | rm -rf package/wp-content/plugins/akismet 315 | 316 | - name: Tree source 317 | run: | #shell 318 | tree source || true 319 | 320 | - name: Tree package 321 | run: | #shell 322 | tree package || true 323 | 324 | - name: Compare files 325 | run: | #shell 326 | diff --strip-trailing-cr --recursive --color=always source package 327 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /package 2 | /source 3 | /vendor 4 | 5 | /composer.lock 6 | 7 | *.zip 8 | *.tar.gz 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 John Blackbourn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /offers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "response": "autoupdate", 4 | "download": "https://downloads.w.org/release/wordpress-6.8.1.zip", 5 | "locale": "en_US", 6 | "packages": { 7 | "full": "https://downloads.w.org/release/wordpress-6.8.1.zip", 8 | "no_content": "https://downloads.w.org/release/wordpress-6.8.1-no-content.zip", 9 | "new_bundled": "https://downloads.w.org/release/wordpress-6.8.1-new-bundled.zip", 10 | "partial": false, 11 | "rollback": false 12 | }, 13 | "current": "6.8.1", 14 | "version": "6.8.1", 15 | "php_version": "7.2.24", 16 | "mysql_version": "5.5.5", 17 | "new_bundled": "6.7", 18 | "partial_version": false, 19 | "new_files": true 20 | }, 21 | { 22 | "response": "autoupdate", 23 | "download": "https://downloads.w.org/release/wordpress-6.7.2.zip", 24 | "locale": "en_US", 25 | "packages": { 26 | "full": "https://downloads.w.org/release/wordpress-6.7.2.zip", 27 | "no_content": "https://downloads.w.org/release/wordpress-6.7.2-no-content.zip", 28 | "new_bundled": "https://downloads.w.org/release/wordpress-6.7.2-new-bundled.zip", 29 | "partial": false, 30 | "rollback": false 31 | }, 32 | "current": "6.7.2", 33 | "version": "6.7.2", 34 | "php_version": "7.2.24", 35 | "mysql_version": "5.5.5", 36 | "new_bundled": "6.7", 37 | "partial_version": false, 38 | "new_files": true 39 | }, 40 | { 41 | "response": "autoupdate", 42 | "download": "https://downloads.w.org/release/wordpress-6.6.2.zip", 43 | "locale": "en_US", 44 | "packages": { 45 | "full": "https://downloads.w.org/release/wordpress-6.6.2.zip", 46 | "no_content": "https://downloads.w.org/release/wordpress-6.6.2-no-content.zip", 47 | "new_bundled": "https://downloads.w.org/release/wordpress-6.6.2-new-bundled.zip", 48 | "partial": false, 49 | "rollback": false 50 | }, 51 | "current": "6.6.2", 52 | "version": "6.6.2", 53 | "php_version": "7.2.24", 54 | "mysql_version": "5.5.5", 55 | "new_bundled": "6.7", 56 | "partial_version": false, 57 | "new_files": true 58 | } 59 | ] 60 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Reproduce and verify packages](https://github.com/johnbillion/rave-wordpress/actions/workflows/rave.yml/badge.svg?event=schedule)](https://github.com/johnbillion/rave-wordpress/actions/workflows/rave.yml) 2 | 3 | # RAVE for WordPress 4 | 5 | RAVE for WordPress is a supply chain security tool which compares the contents of published packages of WordPress with the canonical source code to verify they have not been tampered with. The packages from WordPress.org are tested along with those from GitHub and various third parties. 6 | 7 | A CI system runs on GitHub Actions which reproduces the build from the canonical sources, fetches published packages from various locations, and compares them all against one another to verify their integrity and identify any anomalies. 8 | 9 | RAVE stands for Reproduce And Verify. 10 | 11 | ## What's tested? 12 | 13 | ### Source code 14 | 15 | * ✅ `develop.svn.wordpress.org` 16 | * ✅ `develop.git.wordpress.org` 17 | * ✅ `github.com/wordpress/wordpress-develop` 18 | * ✅ `core.trac.wordpress.org` 19 | 20 | ### Official packages 21 | 22 | * ✅ `wordpress.org/latest.zip` 23 | * ✅ `wordpress.org/wordpress-{tag}.zip` 24 | * ✅ `downloads.wordpress.org/release/wordpress-{tag}.zip` 25 | * ✅ `downloads.w.org/release/wordpress-{tag}.zip` 26 | * ✅ `github.com/wordpress/wordpress` 27 | * ✅ `build.trac.wordpress.org` 28 | 29 | ### Official builds 30 | 31 | * ✅ `core.git.wordpress.org` 32 | * ✅ `core.svn.wordpress.org` 33 | 34 | ### Unofficial packages 35 | 36 | * ✅ `wordpress` image from Docker Hub 37 | * ✅ `core-updates.wpengine.com` from WPEngine 38 | * ✅ `api.aspirecloud.net` from AspireCloud 39 | * ✅ `roots/wordpress-full` on Packagist 40 | * ✅ `johnpbloch/wordpress` on Packagist 41 | 42 | ### Other verifications 43 | 44 | * ✅ md5 and sha1 hashes published on wordpress.org are verified against the wordpress.org packages 45 | * ✅ Checksums returned by `api.wordpress.org/core/checksums` are verified against file contents 46 | * ✅ Update URLs returned by `api.wordpress.org/core/version-check` are verified against expected URLs 47 | 48 | ## When do the tests run? 49 | 50 | The GitHub Actions workflow runs once an hour. It verifies the latest version in the three most recent branches, which is currently: 51 | 52 | * WordPress 6.8 53 | * WordPress 6.7 54 | * WordPress 6.6 55 | 56 | _Note:_ Due to request rate limiting on wordpress.org, the `core.trac.wordpress.org` source and the `build.trac.wordpress.org` package are only verified for the most recent branch. All other sources and packages are verified for the three most recent branches. 57 | 58 | ## Why test the official package? 59 | 60 | There are several opportunities for the official WordPress package to be tampered with so that it differs from the actual source code in the source control repos. This could come via an attack on the build server, on wordpress.org or downloads.wordpress.org, from external hackers, from those in control of the wordpress.org CDN, from disgruntled members of the meta or systems teams, from anyone who gains access to accounts or credentials of such team members, or from the project lead. 61 | 62 | ### Real world examples 63 | 64 | * [In 2007 a cracker gained access to the wordpress.org servers and added a backdoor to the WordPress 2.1.1 package](https://wordpress.org/news/2007/03/upgrade-212/). RAVE would have successfully detected this by: 65 | * Detecting that the 2.1.1 package served by wordpress.org didn't match the package that it reproduced from the source code 66 | * Making the backdoor code immediately visible in the diff in the failing workflow 67 | * [In 2016 Wordfence identified a vulnerability in a webhook mechanism on api.wordpress.org](https://www.wordfence.com/blog/2016/11/hacking-27-web-via-wordpress-auto-update/) and demonstrated that a cracker could theoretically execute a shell command on the api.wordpress.org server. If a cracker exploited this vulnerability to modify an existing release or create a new one then RAVE would have successfully detected this by: 68 | * Identifying a new version that didn't exist in the source repos 69 | * Identifying a modified package that didn't match the code built from the source repos and didn't match the versions published to Packagist and Docker Hub 70 | * Identifying any attempt to serve an update from an unexpected host name or URL in the update API response 71 | * Identifying any discrepancies between the published sha1 and md5 hashes, the published checksums, and the offers returned by the update API 72 | 73 | ## Why test unofficial packages? 74 | 75 | There are several opportunities for unofficial WordPress packages to be tampered with so they differ from the official package. This could come via an attack on the servers distributing the packages, from external hackers, from those in control of the packaging, or from anyone who gains access to accounts or credentials of people or processes involved in the packaging or distribution. 76 | 77 | ## How? 78 | 79 | By using the `diff` utility to compare the contents of the distributed package at its various locations with the output of building the source code from its various locations, we can identify anomalies between them. This reduces the opportunity for malicious or unwanted code to be introduced into WordPress packages without it also being present in the source repos or pipeline repos. 80 | 81 | If one of the GitHub Actions workflows in this repo fails, it should be investigated to see if the failure was caused by divergent code. 82 | 83 | ## What's not tested? 84 | 85 | This approach: 86 | 87 | * Does **not** detect malicious or unwanted code that gets committed to the Subversion source code repo and subsequently makes it into the published package. 88 | * Does **not** detect a targeted attack that causes the contents of the package to vary based on a parameter such as the IP address or user agent of the client making the request. 89 | * Does **not** verify the contents of the bundled Akismet plugin. 90 | * Does **not** deal with build provenance verification or software signing. 91 | 92 | ## Reproducible WordPress 93 | 94 | To quote [reproducible-builds.org](https://reproducible-builds.org/): 95 | 96 | > Reproducible builds are a set of software development practices that create an independently-verifiable path from source to binary code. 97 | 98 | [The process that builds and packages WordPress](https://build.trac.wordpress.org/timeline) is reproducible as far as the built code is concerned, although I believe that the ZIP file generation does not result in a stable hash between invocations. Unfortunately the process itself is not open source. The process differs from the `npm run build` process in the source code because it makes some additions (eg. the Akismet plugin) and some exclusions (older default themes). The verifications performed by this repo take this into account. 99 | 100 | ## Verifiable WordPress 101 | 102 | The WordPress.org website [provides the md5 and sha1 hash of its WordPress package files](https://wordpress.org/download/releases/). This is not enough to verify a package on its own, because the hash only represents the zip or tar file and if a package on WordPress.org was altered then its published md5 and sha1 hashes could be altered too. 103 | 104 | The WordPress open source project does not make use of package signing which could be used to verify a package. [See ticket #39309 for discussion on this topic](https://core.trac.wordpress.org/ticket/39309). 105 | 106 | Therefore, this library has been created to provide a means of verifying that the contents of published packages matches the code built from the official source code repos. 107 | 108 | ## License 109 | 110 | MIT 111 | --------------------------------------------------------------------------------