Skip to content

Feature: Enable cross-organization project access with GH_PROJECT_ONLY_TOKEN#16

Merged
miroslavpojer merged 2 commits intomasterfrom
feature/fix-cross-org-access-to-projejct-to-update-priorities
Mar 20, 2026
Merged

Feature: Enable cross-organization project access with GH_PROJECT_ONLY_TOKEN#16
miroslavpojer merged 2 commits intomasterfrom
feature/fix-cross-org-access-to-projejct-to-update-priorities

Conversation

@miroslavpojer
Copy link
Contributor

Release Notes

  • Introduced fix/solution how to be able sync Issue's Project Priority with alert's Severity.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Enables cross-organization access to GitHub Projects V2 for the priority-sync feature by allowing GraphQL calls to authenticate with a dedicated PAT (GH_PROJECT_ONLY_TOKEN) while keeping the rest of the pipeline on the default github.token.

Changes:

  • Add optional env passthrough to run_cmd / run_gh, and use it in Projects GraphQL calls to temporarily override GH_TOKEN when GH_PROJECT_ONLY_TOKEN is present.
  • Update reusable workflow + example caller to request repository-projects: write and forward GH_PROJECT_ONLY_TOKEN.
  • Document cross-org project token setup and troubleshooting guidance.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/shared/github_projects.py Uses GH_PROJECT_ONLY_TOKEN (when set) to authenticate gh api graphql calls for cross-org ProjectV2 access.
src/shared/common.py Extends subprocess helpers to accept an optional environment mapping.
docs/security/security.md Adds documentation and troubleshooting for cross-org ProjectV2 token usage.
docs/security/example_workflows/aquasec-night-scan.yml Updates required permissions and forwards the optional project-only token secret.
.github/workflows/aquasec-scan.yml Updates permissions and adds GH_PROJECT_ONLY_TOKEN as a workflow-call secret forwarded into the sync step env.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Collaborator

@tmikula-dev tmikula-dev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic looks correct and should work. I have mainly issue with the naming and overwhelming information that are not needed for better clean code and also clear documentation.

Comment on lines +53 to +54
# Required only when project-org differs from this repository's org.
# See docs/security/security.md – "Cross-org project token" for how to create it.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Required only when project-org differs from this repository's org.
# See docs/security/security.md – "Cross-org project token" for how to create it.
# Required only when project-org differs from AbsaOSS.
# See docs/security/security.md – "Cross-org project token" for how to create it.

If it really is about differing from THIS repository's org, it is always AbsaOSS.

Comment on lines +69 to +74
GH_PROJECT_ONLY_TOKEN:
description: >
Classic PAT with 'project' scope on an account that is a member of the
org that owns the ProjectV2 board. Required only when the project lives
in a different organisation than the calling repository. When omitted,
github.token is used (works only for same-org projects).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a big fan of the naming, what only stands for in this case? What about ORG_PROJECT_PAT,CROSS_ORG_PROJECT_TOKEN or something like this. Then in the description I would capitalize the word ONLY, to catch an eye.


##### Cross-org project token

When `project-org` is a **different organisation** than the one that owns the calling repository, the automatic `github.token` cannot resolve the ProjectV2 board and you will see:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
When `project-org` is a **different organisation** than the one that owns the calling repository, the automatic `github.token` cannot resolve the ProjectV2 board and you will see:
When `project-org` is in a **different organisation** than the AbsaOSS, the automatic `github.token` cannot resolve the ProjectV2 board and you will see:

TEAMS_WEBHOOK_URL: ${{ secrets.TEAMS_WEBHOOK_URL }}
```

##### Cross-org project token
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpicking: having a 5th level header for this big chapter is not correct. Should this doc be refactored in the future less needed PR.


The fix is a **Personal Access Token (classic)** with the `project` scope, created by an account that is a member of the org owning the project board.

> **Why classic and not fine-grained?** Fine-grained PATs require the org admin to explicitly allow them under org settings. If that is not enabled, a classic PAT with the `project` scope is the practical alternative. Classic PATs are less granular (they apply to all orgs the account belongs to) but the `project` scope is the minimum checkbox needed and grants no code or issue access.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to overwhelm people with information why it is not working?

Comment on lines +231 to +246
The example caller workflow already passes this secret:

```yaml
secrets:
GH_PROJECT_ONLY_TOKEN: ${{ secrets.GH_PROJECT_ONLY_TOKEN }}
```

The reusable workflow forwards it to the Python script as the `GH_PROJECT_ONLY_TOKEN` environment variable. The script uses it **only** for ProjectV2 GraphQL calls; all other operations (issue list/create/update) continue to use the scoped `github.token`.

**Minimum required scope summary**

| Scope | Reason |
| --- | --- |
| `project` | Read/write access to org-level ProjectV2 — query metadata + set Priority field values |
| *(everything else)* | Not needed — do not check |

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep the docs as little as needed to hand over the information to the final consumer.

Comment on lines +55 to +61
"""Execute a GraphQL query via ``gh api graphql`` and return parsed JSON.

When ``GH_PROJECT_ONLY_TOKEN`` is set in the environment the GraphQL call is made
with that token instead of the default ``GH_TOKEN``. This allows cross-org
project access while the rest of the pipeline continues to use the scoped
``github.token``.
"""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Execute a GraphQL query via ``gh api graphql`` and return parsed JSON.
When ``GH_PROJECT_ONLY_TOKEN`` is set in the environment the GraphQL call is made
with that token instead of the default ``GH_TOKEN``. This allows cross-org
project access while the rest of the pipeline continues to use the scoped
``github.token``.
"""
"""Execute a GraphQL query via ``gh api graphql`` and return parsed JSON.
When ``GH_PROJECT_ONLY_TOKEN`` is set in the environment the GraphQL call is made
with that token instead of the default ``GH_TOKEN``.
"""

env: Mapping[str, str] | None = None
project_token = os.environ.get("GH_PROJECT_ONLY_TOKEN", "")
if project_token:
env = {**os.environ, "GH_TOKEN": project_token}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the specific env for some scenario, we should rename this env.

cmd = ["gh"] + args
try:
return run_cmd(cmd, capture_output=capture_output)
return run_cmd(cmd, capture_output=capture_output, env=env)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, what is the cmd? I would rename the cmd, the env here as well.

args += ["-F", f"{k}={v}"]
res = run_gh(args)
env: Mapping[str, str] | None = None
project_token = os.environ.get("GH_PROJECT_ONLY_TOKEN", "")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is some specific token, idea is to have GH_PROJECT_ONLY_TOKEN (hope renamed) will be 1:1 with this current project_token. It is specific project token for this situation.

@miroslavpojer
Copy link
Contributor Author

The logic looks correct and should work. I have mainly issue with the naming and overwhelming information that are not needed for better clean code and also clear documentation.

I will keep your review comment for following PR as I would like to check if proposed solution even work before beautifing.

Copy link
Collaborator

@tmikula-dev tmikula-dev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving for following development.

@miroslavpojer miroslavpojer merged commit d5379d7 into master Mar 20, 2026
7 checks passed
@miroslavpojer miroslavpojer deleted the feature/fix-cross-org-access-to-projejct-to-update-priorities branch March 20, 2026 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants