From 4320e098d8743b7999d0e762b501f3103e77d751 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Mon, 21 Sep 2026 22:01:59 -0300 Subject: [PATCH 1/4] fix: validate release app credentials early Signed-off-by: Vitor Mattos --- VERSION | 2 +- actions/release-authorization/action.yml | 8 ++++---- actions/release-post-merge/action.yml | 21 +++++++++++++++++++++ actions/release-prepare/action.yml | 21 +++++++++++++++++++++ docs/cross-repository-automation.md | 14 +++++++++++++- tests/test_release_authorization.py | 7 +++++++ tests/test_release_post_merge_action.py | 8 ++++++++ tests/test_release_prepare_action.py | 9 +++++++++ 8 files changed, 84 insertions(+), 6 deletions(-) diff --git a/VERSION b/VERSION index ee6cdce..b616048 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.1 +0.6.2 diff --git a/actions/release-authorization/action.yml b/actions/release-authorization/action.yml index cbeaa72..edcdaac 100644 --- a/actions/release-authorization/action.yml +++ b/actions/release-authorization/action.yml @@ -64,10 +64,10 @@ runs: { echo "## Release authorization" echo - echo "- Actor: `${RELEASE_ACTOR}`" - echo "- Required: `${RELEASE_MINIMUM_PERMISSION}`" - echo "- Actual: `${actual_permission}`" - echo "- Authorized: **${authorized}**" + printf -- '- Actor: `%s`\n' "${RELEASE_ACTOR}" + printf -- '- Required: `%s`\n' "${RELEASE_MINIMUM_PERMISSION}" + printf -- '- Actual: `%s`\n' "${actual_permission}" + printf -- '- Authorized: **%s**\n' "${authorized}" } >> "${GITHUB_STEP_SUMMARY}" if [[ "${authorized}" != "true" ]]; then diff --git a/actions/release-post-merge/action.yml b/actions/release-post-merge/action.yml index af2c1eb..0c030ae 100644 --- a/actions/release-post-merge/action.yml +++ b/actions/release-post-merge/action.yml @@ -49,6 +49,27 @@ outputs: runs: using: composite steps: + - name: Validate GitHub App credentials + shell: bash + env: + RELEASE_APP_ID: ${{ inputs.app-id }} + RELEASE_APP_PRIVATE_KEY: ${{ inputs.app-private-key }} + run: | + set -euo pipefail + + if [[ -z "${RELEASE_APP_ID}" ]]; then + echo "::error::GitHub App id is empty. Configure LIBRECODE_WORKFLOW_APP_ID as an Actions variable in the consumer repository or organization." + exit 1 + fi + if [[ ! "${RELEASE_APP_ID}" =~ ^[0-9]+$ ]]; then + echo "::error::GitHub App id must be numeric." + exit 1 + fi + if [[ -z "${RELEASE_APP_PRIVATE_KEY}" ]]; then + echo "::error::GitHub App private key is empty. Configure LIBRECODE_WORKFLOW_APP_PRIVATE_KEY as an Actions secret in the consumer repository or organization." + exit 1 + fi + - id: restore name: Restore preparation contracts uses: $/actions/release-artifact-restore diff --git a/actions/release-prepare/action.yml b/actions/release-prepare/action.yml index 9a99a61..8c37fea 100644 --- a/actions/release-prepare/action.yml +++ b/actions/release-prepare/action.yml @@ -73,6 +73,27 @@ outputs: runs: using: composite steps: + - name: Validate GitHub App credentials + shell: bash + env: + RELEASE_APP_ID: ${{ inputs.app-id }} + RELEASE_APP_PRIVATE_KEY: ${{ inputs.app-private-key }} + run: | + set -euo pipefail + + if [[ -z "${RELEASE_APP_ID}" ]]; then + echo "::error::GitHub App id is empty. Configure LIBRECODE_WORKFLOW_APP_ID as an Actions variable in the consumer repository or organization." + exit 1 + fi + if [[ ! "${RELEASE_APP_ID}" =~ ^[0-9]+$ ]]; then + echo "::error::GitHub App id must be numeric." + exit 1 + fi + if [[ -z "${RELEASE_APP_PRIVATE_KEY}" ]]; then + echo "::error::GitHub App private key is empty. Configure LIBRECODE_WORKFLOW_APP_PRIVATE_KEY as an Actions secret in the consumer repository or organization." + exit 1 + fi + - id: plan name: Build release plan uses: $/actions/release-plan diff --git a/docs/cross-repository-automation.md b/docs/cross-repository-automation.md index 3d94fbc..b27637f 100644 --- a/docs/cross-repository-automation.md +++ b/docs/cross-repository-automation.md @@ -43,7 +43,19 @@ permissions are required. - Actions variable `LIBRECODE_WORKFLOW_APP_ID`; - Actions secret `LIBRECODE_WORKFLOW_APP_PRIVATE_KEY`. -The private key must never be committed to the repository. +Consumer repositories that execute write-capable release orchestration also need +those values available in their own Actions context, either directly at +repository level or inherited from an organization configuration that includes +the repository: + +- Actions variable `LIBRECODE_WORKFLOW_APP_ID`; +- Actions secret `LIBRECODE_WORKFLOW_APP_PRIVATE_KEY`. + +The GitHub App installation must also include the consumer repository. A +credential configured only in `LibreCodeCoop/github-workflows` is not visible +to a workflow running in another repository. + +The private key must never be committed to a repository. ## Token model diff --git a/tests/test_release_authorization.py b/tests/test_release_authorization.py index 0a2868d..4691627 100644 --- a/tests/test_release_authorization.py +++ b/tests/test_release_authorization.py @@ -33,6 +33,13 @@ def test_rejects_unknown_actual_permission(self) -> None: with self.assertRaisesRegex(ValueError, "unsupported repository permission"): module.is_authorized("custom", "read") + def test_action_summary_uses_printf_for_markdown_code(self) -> None: + content = (ROOT / "actions" / "release-authorization" / "action.yml").read_text(encoding="utf-8") + self.assertIn("printf -- '- Actor: `%s`", content) + self.assertIn("printf -- '- Required: `%s`", content) + self.assertIn("printf -- '- Actual: `%s`", content) + self.assertNotIn('echo "- Actor: `', content) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_release_post_merge_action.py b/tests/test_release_post_merge_action.py index 79aa9c6..a80315f 100644 --- a/tests/test_release_post_merge_action.py +++ b/tests/test_release_post_merge_action.py @@ -10,6 +10,14 @@ class ReleasePostMergeActionTest(unittest.TestCase): + def test_credentials_are_validated_before_artifact_restore(self) -> None: + content = ACTION.read_text(encoding="utf-8") + validate = content.index("Validate GitHub App credentials") + restore = content.index("Restore preparation contracts") + self.assertLess(validate, restore) + self.assertIn("LIBRECODE_WORKFLOW_APP_ID", content) + self.assertIn("LIBRECODE_WORKFLOW_APP_PRIVATE_KEY", content) + def test_authorization_happens_before_mutation(self) -> None: content = ACTION.read_text(encoding="utf-8") authorize = content.index("Authorize release PR merger") diff --git a/tests/test_release_prepare_action.py b/tests/test_release_prepare_action.py index 6982cd6..ace172d 100644 --- a/tests/test_release_prepare_action.py +++ b/tests/test_release_prepare_action.py @@ -28,6 +28,15 @@ def test_prepare_composes_policy_contracts_and_scoped_mutation(self) -> None: self.assertNotIn("permission-workflows: write", content) self.assertIn("release:prepare", content) + def test_prepare_validates_mutation_credentials_before_planning(self) -> None: + content = ACTION.read_text(encoding="utf-8") + validate = content.index("Validate GitHub App credentials") + plan = content.index("Build release plan") + self.assertLess(validate, plan) + self.assertIn("LIBRECODE_WORKFLOW_APP_ID", content) + self.assertIn("LIBRECODE_WORKFLOW_APP_PRIVATE_KEY", content) + self.assertIn('[[ ! "${RELEASE_APP_ID}" =~ ^[0-9]+$ ]]', content) + def test_prepare_persists_plan_and_preparation_by_pr_number(self) -> None: content = ACTION.read_text(encoding="utf-8") self.assertIn("release-plan.json", content) From 06f7967ecca1c8821ccad15db11fce75de939e1c Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Mon, 21 Sep 2026 22:03:14 -0300 Subject: [PATCH 2/4] fix: align release app id with secret configuration Signed-off-by: Vitor Mattos --- actions/release-prepare/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/release-prepare/action.yml b/actions/release-prepare/action.yml index 8c37fea..b38b9a8 100644 --- a/actions/release-prepare/action.yml +++ b/actions/release-prepare/action.yml @@ -82,7 +82,7 @@ runs: set -euo pipefail if [[ -z "${RELEASE_APP_ID}" ]]; then - echo "::error::GitHub App id is empty. Configure LIBRECODE_WORKFLOW_APP_ID as an Actions variable in the consumer repository or organization." + echo "::error::GitHub App id is empty. Configure LIBRECODE_WORKFLOW_APP_ID as an Actions secret in the consumer repository or organization." exit 1 fi if [[ ! "${RELEASE_APP_ID}" =~ ^[0-9]+$ ]]; then From 85423b9ad09c87762ac0a6ea91618422a6071a88 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Mon, 21 Sep 2026 22:03:20 -0300 Subject: [PATCH 3/4] fix: align release app id with secret configuration Signed-off-by: Vitor Mattos --- actions/release-post-merge/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/release-post-merge/action.yml b/actions/release-post-merge/action.yml index 0c030ae..1f31e68 100644 --- a/actions/release-post-merge/action.yml +++ b/actions/release-post-merge/action.yml @@ -58,7 +58,7 @@ runs: set -euo pipefail if [[ -z "${RELEASE_APP_ID}" ]]; then - echo "::error::GitHub App id is empty. Configure LIBRECODE_WORKFLOW_APP_ID as an Actions variable in the consumer repository or organization." + echo "::error::GitHub App id is empty. Configure LIBRECODE_WORKFLOW_APP_ID as an Actions secret in the consumer repository or organization." exit 1 fi if [[ ! "${RELEASE_APP_ID}" =~ ^[0-9]+$ ]]; then From bc9fccc9d2cd9c5648fbf7269585d61f969304d1 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Mon, 21 Sep 2026 22:03:24 -0300 Subject: [PATCH 4/4] fix: align release app id with secret configuration Signed-off-by: Vitor Mattos --- docs/cross-repository-automation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/cross-repository-automation.md b/docs/cross-repository-automation.md index b27637f..e9a75e2 100644 --- a/docs/cross-repository-automation.md +++ b/docs/cross-repository-automation.md @@ -40,15 +40,15 @@ permissions are required. `LibreCodeCoop/github-workflows` stores: -- Actions variable `LIBRECODE_WORKFLOW_APP_ID`; +- Actions secret `LIBRECODE_WORKFLOW_APP_ID`; - Actions secret `LIBRECODE_WORKFLOW_APP_PRIVATE_KEY`. Consumer repositories that execute write-capable release orchestration also need -those values available in their own Actions context, either directly at +both secrets available in their own Actions context, either directly at repository level or inherited from an organization configuration that includes the repository: -- Actions variable `LIBRECODE_WORKFLOW_APP_ID`; +- Actions secret `LIBRECODE_WORKFLOW_APP_ID`; - Actions secret `LIBRECODE_WORKFLOW_APP_PRIVATE_KEY`. The GitHub App installation must also include the consumer repository. A