diff --git a/.github/workflows/add-community-bundle.md b/.github/workflows/add-community-bundle.md index a54a35f890..b7c4da15a9 100644 --- a/.github/workflows/add-community-bundle.md +++ b/.github/workflows/add-community-bundle.md @@ -118,11 +118,24 @@ Run every check and collect all failures before deciding the outcome. ### 2c. Release artifact -- The download URL must be an HTTPS GitHub release asset URL under the submitted - repository: +- The download URL MUST belong to the submitted repository + (`https://github.com///...` with the same `/` as + the Repository URL). Reject URLs for any other GitHub repository. +- The download URL MUST follow the accepted tag-pinned pattern: `https://github.com///releases/download//.zip`. -- Confirm the release exists, its tag corresponds to the submitted version - (`vX.Y.Z` or `X.Y.Z`), and the exact ZIP asset is attached to that release. +- If the download URL path contains `releases/latest/`, reject with an + explanation — this URL is floating and not acceptable. Mark this pinning + check failed and skip the HTTP request for this URL, then continue the + remaining validations. +- The `` segment in the URL MUST correspond to the submitted version. + Accept `vX.Y.Z`, `X.Y.Z`, and scoped tags whose version suffix matches + (for example `aide-v1.0.0` for version `1.0.0`). Reject a tag whose + embedded semver does not equal the submitted version. +- `sha256` is optional. If the submission includes it, verify it matches the + downloaded archive. Requiring `sha256` on every catalog entry is follow-up + work and MUST NOT fail this check when the field is absent. +- Only after the pinning checks pass: confirm the release exists, the exact ZIP + asset is attached to that release, and the download URL returns HTTP 200. - Confirm the asset name is versioned and consistent with the submitted bundle ID and version. diff --git a/.github/workflows/add-community-extension.md b/.github/workflows/add-community-extension.md index 0521e52100..3ef2637244 100644 --- a/.github/workflows/add-community-extension.md +++ b/.github/workflows/add-community-extension.md @@ -110,11 +110,26 @@ deciding pass/fail: - Confirm the repository contains a `LICENSE` file ### 2d. Release and download URL validation -- The download URL should follow the pattern - `https://github.com///archive/refs/tags/v.zip` +- The download URL MUST belong to the submitted repository + (`https://github.com///...` with the same `/` as + the Repository URL). Reject URLs for any other GitHub repository. +- The download URL MUST follow one of the accepted tag-pinned patterns: + `https://github.com///archive/refs/tags/.zip` or `https://github.com///releases/download//.zip` -- Verify a GitHub release exists matching the submitted version +- If the download URL path contains `releases/latest/`, reject with an + explanation — this URL is floating and not acceptable. Mark this pinning + check failed and skip the HTTP request for this URL, then continue the + remaining validations. +- The `` segment in the URL MUST correspond to the submitted version. + Accept `vX.Y.Z`, `X.Y.Z`, and scoped tags whose version suffix matches + (for example `aide-v1.0.0` for version `1.0.0`). Reject a tag whose + embedded semver does not equal the submitted version. +- `sha256` is optional. If the submission includes it, verify it matches the + downloaded archive. Requiring `sha256` on every catalog entry is follow-up + work and MUST NOT fail this check when the field is absent. +- Only after the pinning checks pass: verify a GitHub release exists for that + tag, and that the download URL returns HTTP 200. ### 2e. Submission checklists - Confirm that all required checkboxes in the Testing Checklist and Submission diff --git a/.github/workflows/add-community-preset.md b/.github/workflows/add-community-preset.md index 038fbbe1a1..9eaaadedb0 100644 --- a/.github/workflows/add-community-preset.md +++ b/.github/workflows/add-community-preset.md @@ -161,11 +161,26 @@ preset** — not just any file named `README.md`, and not a product/framework pi `specify preset add ...` command for this preset; otherwise it fails check 2d above. ### 2e. Release and download URL validation -- The download URL should follow the pattern - `https://github.com///archive/refs/tags/v.zip` +- The download URL MUST belong to the submitted repository + (`https://github.com///...` with the same `/` as + the Repository URL). Reject URLs for any other GitHub repository. +- The download URL MUST follow one of the accepted tag-pinned patterns: + `https://github.com///archive/refs/tags/.zip` or `https://github.com///releases/download//.zip` -- Verify a GitHub release exists matching the submitted version +- If the download URL path contains `releases/latest/`, reject with an + explanation — this URL is floating and not acceptable. Mark this pinning + check failed and skip the HTTP request for this URL, then continue the + remaining validations. +- The `` segment in the URL MUST correspond to the submitted version. + Accept `vX.Y.Z`, `X.Y.Z`, and scoped tags whose version suffix matches + (for example `aide-v1.0.0` for version `1.0.0`). Reject a tag whose + embedded semver does not equal the submitted version. +- `sha256` is optional. If the submission includes it, verify it matches the + downloaded archive. Requiring `sha256` on every catalog entry is follow-up + work and MUST NOT fail this check when the field is absent. +- Only after the pinning checks pass: verify a GitHub release exists for that + tag, and that the download URL returns HTTP 200. ### 2f. Submission checklists - Confirm that all required checkboxes in the Testing Checklist and Submission diff --git a/tests/test_github_workflows.py b/tests/test_github_workflows.py index aeb8ad7e21..c54804727d 100644 --- a/tests/test_github_workflows.py +++ b/tests/test_github_workflows.py @@ -111,6 +111,62 @@ def test_community_submission_automation_is_wired_to_allowed_files(): assert label in assignment_text +# Full clauses from the catalog download-URL checks (issue #4185). Assert the +# complete sentences so independent keywords cannot drift apart. +_CATALOG_DOWNLOAD_URL_CLAUSES = ( + ( + "The download URL MUST belong to the submitted repository\n" + " (`https://github.com///...` with the same `/` as\n" + " the Repository URL). Reject URLs for any other GitHub repository." + ), + ( + "If the download URL path contains `releases/latest/`, reject with an\n" + " explanation — this URL is floating and not acceptable. Mark this pinning\n" + " check failed and skip the HTTP request for this URL, then continue the\n" + " remaining validations." + ), + ( + "The `` segment in the URL MUST correspond to the submitted version.\n" + " Accept `vX.Y.Z`, `X.Y.Z`, and scoped tags whose version suffix matches\n" + " (for example `aide-v1.0.0` for version `1.0.0`). Reject a tag whose\n" + " embedded semver does not equal the submitted version." + ), + ( + "`sha256` is optional. If the submission includes it, verify it matches the\n" + " downloaded archive. Requiring `sha256` on every catalog entry is follow-up\n" + " work and MUST NOT fail this check when the field is absent." + ), +) + + +def test_community_submission_workflows_require_tag_pinned_download_urls(): + """Catalog agents must reject floating releases/latest URLs (issue #4185).""" + for workflow, *_ in COMMUNITY_SUBMISSION_WORKFLOWS: + source_text = (WORKFLOWS_DIR / f"add-community-{workflow}.md").read_text( + encoding="utf-8" + ) + + assert "should follow the pattern" not in source_text.lower() + for clause in _CATALOG_DOWNLOAD_URL_CLAUSES: + assert clause in source_text, f"missing clause in {workflow}: {clause!r}" + + if workflow == "bundle": + assert ( + "`https://github.com///releases/download//.zip`." + in source_text + ) + assert "archive/refs/tags/" not in source_text + else: + assert ( + "`https://github.com///archive/refs/tags/.zip`" + in source_text + ) + assert ( + "`https://github.com///releases/download//.zip`" + in source_text + ) + + def test_community_submission_allowed_files_do_not_include_other_catalogs_or_docs(): allowed_by_workflow = { workflow: set(