这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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.

## 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.
Expand All @@ -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).
74 changes: 74 additions & 0 deletions __tests__/labeler.test.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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) => {
Expand Down Expand Up @@ -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);
});
});
12 changes: 10 additions & 2 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1083,11 +1084,18 @@ function labeler() {
}
}
catch (error) {
if (error.name !== 'HttpError' ||
if (error.name === 'HttpError' &&
error.status === 403 &&
error.message.toLowerCase().includes('unauthorized')) {
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' ||
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);
Expand Down
14 changes: 12 additions & 2 deletions src/labeler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -65,14 +65,24 @@ async function labeler() {
}
} catch (error: any) {
if (
error.name === 'HttpError' &&
error.status === 403 &&
error.message.toLowerCase().includes('unauthorized')
) {
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' ||
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`,
`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`
}
Expand Down
Loading