From fee03d94cf8df6e3b76d82cd0a01ee88a06f7a1f Mon Sep 17 00:00:00 2001 From: chiranjib-swain Date: Fri, 22 Aug 2025 17:55:34 +0530 Subject: [PATCH 1/6] Update README.md and labeler.ts to clarify permissions for GitHub Labeler Action --- README.md | 27 ++++++++++++++++++++++++--- dist/index.js | 13 ++++++++++--- src/labeler.ts | 12 +++++++++++- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 31a53acd2..0627ec3cd 100644 --- a/README.md +++ b/README.md @@ -258,15 +258,36 @@ jobs: ## Recommended Permissions -In order to add labels to pull requests, the GitHub labeler action requires write permissions on the pull-request. However, when the action runs on a pull request from a forked repository, GitHub only grants read access tokens for `pull_request` events, at most. If you encounter an `Error: HttpError: Resource not accessible by integration`, it's likely due to these permission constraints. To resolve this issue, you can modify the `on:` section of your workflow to use -[`pull_request_target`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target) instead of `pull_request` (see example [above](#create-workflow)). This change allows the action to have write access, because `pull_request_target` alters the [context of the action](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target) and safely grants additional permissions. There exists a potentially dangerous misuse of the pull_request_target workflow trigger that may lead to malicious PR authors (i.e. attackers) being able to obtain repository write permissions or stealing repository secrets, Hence it is advisible that pull_request_target should only be used in workflows that are carefully designed to avoid executing untrusted code and to also ensure that workflows using pull_request_target limit access to sensitive resources. Refer to the [GitHub token permissions documentation](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token) for more details about access levels and event contexts. +To successfully add labels to pull requests using the GitHub Labeler Action, specific permissions must be granted based on your use case: + +1. **Adding Existing Labels**: + - Requires: `pull-requests: write` + - Use this if all labels already exist in the repository (i.e., pre-defined in `.github/labeler.yml`). + +2. **Creating New Labels**: + - Requires: `issues: write` + - This is necessary if the action needs to create labels that do not already exist in the repository. + +However, when the action runs on a pull request from a forked repository, GitHub only grants read access tokens for `pull_request` events, at most. If you encounter an `Error: HttpError: Resource not accessible by integration`, it's likely due to these permission constraints. To resolve this issue, you can modify the `on:` section of your workflow to use +[`pull_request_target`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target) instead of `pull_request` (see example [above](#create-workflow)). This change allows the action to have write access, because `pull_request_target` alters the [context of the action](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target) and safely grants additional permissions. + +There exists a potentially dangerous misuse of the `pull_request_target` workflow trigger that may lead to malicious PR authors (i.e. attackers) being able to obtain repository write permissions or stealing repository secrets. Hence, it is advisable that `pull_request_target` should only be used in workflows that are carefully designed to avoid executing untrusted code and to also ensure that workflows using `pull_request_target` limit access to sensitive resources. Refer to the [GitHub token permissions documentation](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token) for more details about access levels and event contexts. + +### Example Workflow Permissions + +To ensure the action works correctly, include the following permissions in your workflow file: ```yml permissions: contents: read pull-requests: write + issues: write ``` +### Manual label creation (Alternative to Granting `issues: write` Permission) + +If you prefer not to grant the `issues: write` permission in your workflow, you can manually create all required labels in the repository before the action runs. + ## Notes regarding `pull_request_target` event Using the `pull_request_target` event trigger involves several peculiarities related to initial set up of the labeler or updating version of the labeler. @@ -291,4 +312,4 @@ Once you confirm that the updated configuration files function as intended, you ## Contributions -Contributions are welcome! See the [Contributor's Guide](CONTRIBUTING.md). +Contributions are welcome! See the [Contributor's Guide](CONTRIBUTING.md). \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index f720b3c7d..bc69446c9 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1083,11 +1083,18 @@ function labeler() { } } catch (error) { - if (error.name !== 'HttpError' || + if (error.name === 'HttpError' && + error.status === 403 && + error.message.includes('unauthorized')) { + core.error(`Failed to set labels for PR #${pullRequest.number}. The workflow does not have permission to create labels. ` + + `Ensure the 'issues: write' permission is granted in the workflow file or manually create the missing labels in the repository before running the action.`); + } + else if (error.name !== 'HttpError' || error.message !== 'Resource not accessible by integration') { throw error; } - core.warning(`The action requires write permission to add labels to pull requests. For more information please refer to the action documentation: https://github.com/actions/labeler#recommended-permissions`, { + core.warning(`The action requires 'issues: write' permission to create new labels or 'pull-requests: write' permission to add existing labels to pull requests. ` + + `For more information, refer to the action documentation: https://github.com/actions/labeler#recommended-permissions`, { title: `${process.env['GITHUB_ACTION_REPOSITORY']} running under '${github.context.eventName}' is misconfigured` }); core.setFailed(error.message); @@ -9559,7 +9566,7 @@ var RequestError = class extends Error { if (options.request.headers.authorization) { requestCopy.headers = Object.assign({}, options.request.headers, { authorization: options.request.headers.authorization.replace( - /(? Date: Fri, 22 Aug 2025 18:04:52 +0530 Subject: [PATCH 2/6] Update dist/index.js with latest build changes --- dist/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/index.js b/dist/index.js index bc69446c9..492183ec5 100644 --- a/dist/index.js +++ b/dist/index.js @@ -9566,7 +9566,7 @@ var RequestError = class extends Error { if (options.request.headers.authorization) { requestCopy.headers = Object.assign({}, options.request.headers, { authorization: options.request.headers.authorization.replace( - / .*$/, + /(? Date: Tue, 26 Aug 2025 14:56:19 +0530 Subject: [PATCH 3/6] Update README.md to clarify manual label creation as an alternative to granting issues write permission --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0627ec3cd..48ec898a1 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ To ensure the action works correctly, include the following permissions in your issues: write ``` -### Manual label creation (Alternative to Granting `issues: write` Permission) +### Manual Label Creation as an Alternative to Granting issues write Permission If you prefer not to grant the `issues: write` permission in your workflow, you can manually create all required labels in the repository before the action runs. From 23770bfd76674d35aec13b38e633506dfc20e70f Mon Sep 17 00:00:00 2001 From: chiranjib-swain Date: Mon, 1 Sep 2025 15:27:30 +0530 Subject: [PATCH 4/6] Fix labeler error handling to ensure case-insensitive check for unauthorized access --- dist/index.js | 2 +- src/labeler.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/index.js b/dist/index.js index 492183ec5..e9b1c4b31 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1085,7 +1085,7 @@ function labeler() { catch (error) { if (error.name === 'HttpError' && error.status === 403 && - error.message.includes('unauthorized')) { + error.message.toLowerCase().includes('unauthorized')) { core.error(`Failed to set labels for PR #${pullRequest.number}. The workflow does not have permission to create labels. ` + `Ensure the 'issues: write' permission is granted in the workflow file or manually create the missing labels in the repository before running the action.`); } diff --git a/src/labeler.ts b/src/labeler.ts index 770bae237..18e5c0cc7 100644 --- a/src/labeler.ts +++ b/src/labeler.ts @@ -67,7 +67,7 @@ async function labeler() { if ( error.name === 'HttpError' && error.status === 403 && - error.message.includes('unauthorized') + error.message.toLowerCase().includes('unauthorized') ) { core.error( `Failed to set labels for PR #${pullRequest.number}. The workflow does not have permission to create labels. ` + From 3540d7f1a5dee9acb2b79acc8f63c090689bba8d Mon Sep 17 00:00:00 2001 From: chiranjib-swain Date: Thu, 4 Sep 2025 17:43:33 +0530 Subject: [PATCH 5/6] Refactor error handling in labeler to throw an error for unauthorized access instead of logging --- dist/index.js | 2 +- src/labeler.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/index.js b/dist/index.js index e9b1c4b31..b57c433c0 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1086,7 +1086,7 @@ function labeler() { if (error.name === 'HttpError' && error.status === 403 && error.message.toLowerCase().includes('unauthorized')) { - core.error(`Failed to set labels for PR #${pullRequest.number}. The workflow does not have permission to create labels. ` + + throw new Error(`Failed to set labels for PR #${pullRequest.number}. The workflow does not have permission to create labels. ` + `Ensure the 'issues: write' permission is granted in the workflow file or manually create the missing labels in the repository before running the action.`); } else if (error.name !== 'HttpError' || diff --git a/src/labeler.ts b/src/labeler.ts index 18e5c0cc7..d1639f651 100644 --- a/src/labeler.ts +++ b/src/labeler.ts @@ -69,7 +69,7 @@ async function labeler() { error.status === 403 && error.message.toLowerCase().includes('unauthorized') ) { - core.error( + throw new Error( `Failed to set labels for PR #${pullRequest.number}. The workflow does not have permission to create labels. ` + `Ensure the 'issues: write' permission is granted in the workflow file or manually create the missing labels in the repository before running the action.` ); From 42bb543fcb529fa8696107e5b332d3b3f12bca36 Mon Sep 17 00:00:00 2001 From: chiranjib-swain Date: Mon, 8 Sep 2025 11:40:33 +0530 Subject: [PATCH 6/6] Add tests for labeler error handling and improve error reporting --- __tests__/labeler.test.ts | 74 +++++++++++++++++++++++++++++++++++++++ dist/index.js | 1 + src/labeler.ts | 2 +- 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/__tests__/labeler.test.ts b/__tests__/labeler.test.ts index b780c82ff..75d25ef2f 100644 --- a/__tests__/labeler.test.ts +++ b/__tests__/labeler.test.ts @@ -1,5 +1,8 @@ import * as yaml from 'js-yaml'; import * as core from '@actions/core'; +import * as api from '../src/api'; +import {labeler} from '../src/labeler'; +import * as github from '@actions/github'; import * as fs from 'fs'; import {checkMatchConfigs} from '../src/labeler'; import { @@ -10,6 +13,7 @@ import { } from '../src/api/get-label-configs'; jest.mock('@actions/core'); +jest.mock('../src/api'); beforeAll(() => { jest.spyOn(core, 'getInput').mockImplementation((name, options) => { @@ -159,3 +163,73 @@ describe('checkMatchConfigs', () => { }); }); }); + +describe('labeler error handling', () => { + const mockClient = {} as any; + const mockPullRequest = { + number: 123, + data: {labels: []}, + changedFiles: [] + }; + + beforeEach(() => { + jest.resetAllMocks(); + + (github.getOctokit as jest.Mock).mockReturnValue(mockClient); + (api.getPullRequests as jest.Mock).mockReturnValue([ + { + ...mockPullRequest, + data: {labels: [{name: 'old-label'}]} + } + ]); + + (api.getLabelConfigs as jest.Mock).mockResolvedValue( + new Map([['new-label', ['dummy-config']]]) + ); + + // Force match so "new-label" is always added + jest.spyOn({checkMatchConfigs}, 'checkMatchConfigs').mockReturnValue(true); + }); + + it('throws a custom error for HttpError 403 with "unauthorized" message', async () => { + (api.setLabels as jest.Mock).mockRejectedValue({ + name: 'HttpError', + status: 403, + message: 'Request failed with status code 403: Unauthorized' + }); + + await expect(labeler()).rejects.toThrow( + /does not have permission to create labels/ + ); + }); + + it('rethrows unexpected HttpError', async () => { + const unexpectedError = { + name: 'HttpError', + status: 404, + message: 'Not Found' + }; + (api.setLabels as jest.Mock).mockRejectedValue(unexpectedError); + + // NOTE: In the current implementation, labeler rethrows the raw error object (not an Error instance). + // `rejects.toThrow` only works with real Error objects, so here we must use `rejects.toEqual`. + // If labeler is updated to always wrap errors in `Error`, this test can be changed to use `rejects.toThrow`. + await expect(labeler()).rejects.toEqual(unexpectedError); + }); + + it('handles "Resource not accessible by integration" gracefully', async () => { + const error = { + name: 'HttpError', + message: 'Resource not accessible by integration' + }; + (api.setLabels as jest.Mock).mockRejectedValue(error); + + await labeler(); + + expect(core.warning).toHaveBeenCalledWith( + expect.stringContaining("requires 'issues: write'"), + expect.any(Object) + ); + expect(core.setFailed).toHaveBeenCalledWith(error.message); + }); +}); diff --git a/dist/index.js b/dist/index.js index b57c433c0..a293843c4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1028,6 +1028,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.run = void 0; +exports.labeler = labeler; exports.checkMatchConfigs = checkMatchConfigs; exports.checkAny = checkAny; exports.checkAll = checkAll; diff --git a/src/labeler.ts b/src/labeler.ts index d1639f651..8f9484f34 100644 --- a/src/labeler.ts +++ b/src/labeler.ts @@ -22,7 +22,7 @@ export const run = () => core.setFailed(error.message); }); -async function labeler() { +export async function labeler() { const {token, configPath, syncLabels, dot, prNumbers} = getInputs(); if (!prNumbers.length) {