From b5aa45ebbf28921e3e3fad4d08a9c2ab1aca7293 Mon Sep 17 00:00:00 2001 From: monkseekee Date: Thu, 16 Jul 2026 22:25:56 +0800 Subject: [PATCH 1/2] fix(governance): allow signed provenance trailers --- governance/commitlint/commitlint.config.mjs | 4 ++++ .../commitlint/test/commitlint.test.mjs | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/governance/commitlint/commitlint.config.mjs b/governance/commitlint/commitlint.config.mjs index 78470f5..d83e6ff 100644 --- a/governance/commitlint/commitlint.config.mjs +++ b/governance/commitlint/commitlint.config.mjs @@ -20,6 +20,10 @@ export default { ], rules: { ...conventional.rules, + // Signed release provenance uses self-describing trailer keys plus + // base64 Ed25519 signatures. Keep a bounded exception for those values + // without disabling footer length enforcement organization-wide. + 'footer-max-line-length': [2, 'always', 140], 'subject-no-han': [2, 'always'], 'body-no-han': [2, 'always'], 'footer-no-han': [2, 'always'], diff --git a/governance/commitlint/test/commitlint.test.mjs b/governance/commitlint/test/commitlint.test.mjs index 09e894c..9c3a001 100644 --- a/governance/commitlint/test/commitlint.test.mjs +++ b/governance/commitlint/test/commitlint.test.mjs @@ -39,6 +39,25 @@ test('accepts a conventional English commit message', () => { assert.equal(result.status, 0, result.stdout || result.stderr); }); +test('accepts bounded cryptographic provenance trailers', () => { + const message = [ + 'chore(public): promote HMG v1.7.7', + '', + `HMG-Provenance-Key-ID: ed25519-spki-sha256-${'a'.repeat(64)}`, + `HMG-Provenance-Signature-Ed25519: ${'A'.repeat(86)}==`, + '', + ].join('\n'); + const result = lint(message); + assert.equal(result.status, 0, result.stdout || result.stderr); +}); + +test('still rejects footer lines longer than the provenance bound', () => { + assertLintFailure( + `chore: reject an oversized footer\n\nAudit-Value: ${'a'.repeat(130)}\n`, + 'footer-max-line-length', + ); +}); + test('rejects Unicode Han script in subject, body, and footer', () => { assertLintFailure('fix: 修复 tenant lookup\n', 'subject-no-han'); assertLintFailure( From e7becffaa380ad26286ffb5f6b81dfd3780add9f Mon Sep 17 00:00:00 2001 From: monkseekee Date: Thu, 16 Jul 2026 22:46:55 +0800 Subject: [PATCH 2/2] fix(governance): make required commit policy immutable --- .github/workflows/required-quality-gate.yml | 62 +++++++++++++++++-- governance/commitlint/commitlint.config.mjs | 30 +++++++-- .../commitlint/test/commitlint.test.mjs | 13 +++- 3 files changed, 93 insertions(+), 12 deletions(-) diff --git a/.github/workflows/required-quality-gate.yml b/.github/workflows/required-quality-gate.yml index 6d903e7..3f0a9d0 100644 --- a/.github/workflows/required-quality-gate.yml +++ b/.github/workflows/required-quality-gate.yml @@ -13,11 +13,63 @@ concurrency: jobs: quality-gate: - name: quality-gate - uses: HMG-AI/.github/.github/workflows/reusable-commitlint.yml@978d6d08d6bfc1e8a3662c1f9b0daab8bfa0e557 - with: - base_sha: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }} - head_sha: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} + name: quality-gate / commitlint + runs-on: ubuntu-latest + timeout-minutes: 10 + container: + image: node:24.11.1-bookworm@sha256:9a2ed90cd91b1f3412affe080b62e69b057ba8661d9844e143a6bbd76a23260f + options: --security-opt=no-new-privileges + steps: + - name: Validate commit SHA inputs + shell: bash + env: + BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} + run: | + set -Eeuo pipefail + readonly sha_pattern='^[0-9a-f]{40}$' + + for sha_name in BASE_SHA HEAD_SHA; do + sha_value="${!sha_name}" + if [[ ! "${sha_value}" =~ ${sha_pattern} ]]; then + echo "::error::${sha_name} must be a full 40-character lowercase hexadecimal commit SHA" + exit 2 + fi + done + + - name: Check out caller repository history + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} + path: caller + persist-credentials: false + + - name: Check out immutable organization commit policy + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: HMG-AI/.github + ref: ${{ github.workflow_sha }} + path: governance-source + sparse-checkout: governance/commitlint + persist-credentials: false + + - name: Install locked commitlint dependencies + shell: bash + working-directory: governance-source/governance/commitlint + run: npm ci --ignore-scripts --no-audit --no-fund + + - name: Lint every commit in base..head + shell: bash + env: + BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} + run: | + set -Eeuo pipefail + bash "${GITHUB_WORKSPACE}/governance-source/governance/commitlint/lint-range.sh" \ + "${GITHUB_WORKSPACE}/caller" \ + "${BASE_SHA}" \ + "${HEAD_SHA}" workflow-security-policy: name: workflow-security-policy diff --git a/governance/commitlint/commitlint.config.mjs b/governance/commitlint/commitlint.config.mjs index d83e6ff..e22e22a 100644 --- a/governance/commitlint/commitlint.config.mjs +++ b/governance/commitlint/commitlint.config.mjs @@ -7,6 +7,26 @@ const noHan = (field) => (parsed) => [ `${field} must not contain Unicode Han script characters`, ]; +const canonicalLongProvenanceTrailers = [ + /^HMG-Provenance-Key-ID: ed25519-spki-sha256-[0-9a-f]{64}$/, + /^HMG-Provenance-Signature-Ed25519: [A-Za-z0-9+/]{86}==$/, +]; + +const boundedFooterLines = (parsed, _when, limit = 100) => { + const invalidLines = (parsed.footer ?? '') + .split('\n') + .filter( + (line) => + line.length > limit && + !canonicalLongProvenanceTrailers.some((pattern) => pattern.test(line)), + ); + + return [ + invalidLines.length === 0, + `footer lines must not exceed ${limit} characters unless they are canonical HMG provenance trailers`, + ]; +}; + export default { ...conventional, plugins: [ @@ -15,15 +35,17 @@ export default { 'subject-no-han': noHan('subject'), 'body-no-han': noHan('body'), 'footer-no-han': noHan('footer'), + 'footer-bounded-lines': boundedFooterLines, }, }, ], rules: { ...conventional.rules, - // Signed release provenance uses self-describing trailer keys plus - // base64 Ed25519 signatures. Keep a bounded exception for those values - // without disabling footer length enforcement organization-wide. - 'footer-max-line-length': [2, 'always', 140], + // The two canonical cryptographic trailers exceed the conventional + // 100-character limit. Disable the broad built-in rule and replace it + // with a strict shape-aware exception. + 'footer-max-line-length': [0], + 'footer-bounded-lines': [2, 'always', 100], 'subject-no-han': [2, 'always'], 'body-no-han': [2, 'always'], 'footer-no-han': [2, 'always'], diff --git a/governance/commitlint/test/commitlint.test.mjs b/governance/commitlint/test/commitlint.test.mjs index 9c3a001..46ebe0a 100644 --- a/governance/commitlint/test/commitlint.test.mjs +++ b/governance/commitlint/test/commitlint.test.mjs @@ -39,7 +39,7 @@ test('accepts a conventional English commit message', () => { assert.equal(result.status, 0, result.stdout || result.stderr); }); -test('accepts bounded cryptographic provenance trailers', () => { +test('accepts canonical cryptographic provenance trailers', () => { const message = [ 'chore(public): promote HMG v1.7.7', '', @@ -51,10 +51,17 @@ test('accepts bounded cryptographic provenance trailers', () => { assert.equal(result.status, 0, result.stdout || result.stderr); }); -test('still rejects footer lines longer than the provenance bound', () => { +test('rejects an ordinary footer line longer than 100 characters', () => { assertLintFailure( `chore: reject an oversized footer\n\nAudit-Value: ${'a'.repeat(130)}\n`, - 'footer-max-line-length', + 'footer-bounded-lines', + ); +}); + +test('rejects a long provenance-shaped trailer with a malformed value', () => { + assertLintFailure( + `chore(public): reject malformed provenance\n\nHMG-Provenance-Signature-Ed25519: ${'A'.repeat(85)}===\n`, + 'footer-bounded-lines', ); });