Dashboard › publish › Session 11zPXPpYfE8Q
11zPXPpYfE8Qz9u7wPerform a FINAL security review of the current uncommitted release-approval authorization gate in /home/byk/Code/getsentry/publish. Do not edit. Threat model: public workflow must never expose security-as-code; unauthorized actors with publish triage must not approve arbitrary target releases; GitHub API/token failures must fail closed and remove accepted; only getsantry[bot] auto-approvals for exact auto-approve-repos.txt entries may bypass the repo permission check. Examine parsing, allowlist, token use, GitHub Actions semantics, injection/data exposure, and regressions. Report findings with severity and exact file:line. Classify each PASS/CONCERN/MUST-FIX and end exactly MERGE or DO-NOT-MERGE.
<path>/home/byk/Code/getsentry/publish</path> <type>directory</type> <entries> .eslintrc.js .git/ .github/ .gitignore .lore.md AGENTS.md auto-approve-repos.txt docs/ LICENSE node_modules/ package.json README.md src/ vitest.config.js yarn.lock (15 entries) </entries>
M .github/workflows/publish.yml
M AGENTS.md
M src/libs/__tests__/github.js
M src/libs/github.js
M src/modules/post-workflow-details.js
M src/modules/process-end-state.js
M src/modules/update-issue.js
M src/publish/inputs.js
M src/publish/post-result.js
M src/publish/post-workflow-details.js
M src/publish/update-issue.js
?? .lore.md
?? src/modules/__tests__/approval-authorizer.js
?? src/modules/approval-authorizer.js
?? src/publish/__tests__/
?? src/publish/authorize-approval.js
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index cff6bc3..081a17b 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -32,6 +32,11 @@ jobs:
&& github.event.issue.state == 'open'
&& startsWith(github.event.issue.title, 'publish: ')
steps:
+ - name: Get publish code
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
- name: Get auth token
id: token
uses: ...
<path>/home/byk/Code/getsentry/publish/.github/workflows/publish.yml</path>
<type>file</type>
<content>
1: name: Publish
2: on:
3: issues:
4: types: [labeled]
5:
6: concurrency:
7: # Use the issue title (e.g. "publish: getsentry/foo@1.2.3") so duplicate
8: # issues for the same repo@version share a concurrency group.
9: group: ${{ github.event.issue.title }}
10: cancel-in-progress: false
11:
12: permissions:
13: contents: read
14: issues: write
15: packages: write
16:
17: jobs:
18: # When accepted is added to a publish issue:
19: # - Add ci-pending (and remove ci-failed if retrying)
20: # - Enable the poller via CI_POLLER_HAS_PENDING=true
21: # - Comment on the issue
22: # - Trigger the poller immediately so we don't wait for the next cron tick
23: # The publish job below requires ci-ready, so it will not fire until the
24: # poller flips ci-pending → ci-ready (which also prevents publishing without
25: # CI verification in the ...
.github/workflows/publish.yml | 40 ++++++++++++++++++++++++++++++++++++
AGENTS.md | 6 ++++++
src/libs/__tests__/github.js | 2 +-
src/libs/github.js | 6 ++++--
src/modules/post-workflow-details.js | 4 ++--
src/modules/process-end-state.js | 4 ++--
src/modules/update-issue.js | 35 +++++++++++++++----------------
src/publish/inputs.js | 17 ++++++++-------
src/publish/post-result.js | 8 ++++----
src/publish/post-workflow-details.js | 8 ++++----
src/publish/update-issue.js | 6 +++---
11 files changed, 91 insertions(+), 45 deletions(-)
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index cff6bc3..081a17b 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -32,6 +32,11 @@ jobs:
&& github.event.issue.state == 'open'
&& startsWith(github.event.issue.title, 'publish: ')
steps:
+ - name: ...
<path>/home/byk/Code/getsentry/publish/src/modules/details-from-context.js</path>
<type>file</type>
<content>
1: /**
2: * Matches the entire "Targets" section of a github publish issue body.
3: */
4: const TARGETS_SECTION_PARSER_REGEX =
5: /^(?!### Targets$\s)(?: *- \[[ xX]\] \S+\s*$(?:\r?\n)?)+/m;
6:
7: /**
8: * Matches all targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`.
9: * The "id" of the targets is captured within a capture group.
10: */
11: const TARGETS_PARSER_REGEX = /^\s*- \[[ x]\] (\S+)/gim;
12:
13: /**
14: * Matches checked targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`.
15: * The "id" of the targets is captured within a capture group.
16: */
17: const CHECKED_TARGETS_PARSER_REGEX = /^\s*- \[x\] (\S+)/gim;
18:
19: async function detailsFromContext({ context }) {
20: if (!context || !context.payload || ...
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller.yml</path> <type>file</type> <content> 1: name: CI Status Poller 2: 3: on: 4: schedule: 5: - cron: "*/5 * * * *" 6: workflow_dispatch: 7: inputs: 8: attempt: 9: description: Self-dispatch attempt counter (internal use) 10: required: false 11: default: "0" 12: 13: permissions: 14: contents: read 15: issues: read 16: 17: jobs: 18: check-ci: 19: runs-on: ubuntu-latest 20: environment: production 21: # Skip entirely (no runner provisioned) when there's nothing to check. 22: # Set to "true" by ci-pending.yml, reset to "false" here when done. 23: # Always allow workflow_dispatch for manual recovery. 24: if: vars.CI_POLLER_HAS_PENDING == 'true' || github.event_name == 'workflow_dispatch' 25: concurrency: 26: group: ci-status-poller 27: cancel-in-progress: false 28: steps: 29: # sentry-internal-app token for label changes on ...
# Repository roles for an organization You can customize access to each repository in your organization by assigning granular roles, giving people access to the features and tasks they need. ## Repository roles for organizations You can give organization members, outside collaborators, and teams of people different levels of access to repositories owned by an organization by assigning them to roles. Choose the role that best fits each person or team's function in your project without giving people more access to the project than they need. ...
Found 53 matches /home/byk/Code/getsentry/publish/.github/workflows/publish.yml: Line 18: # When accepted is added to a publish issue: Line 19: # - Add ci-pending (and remove ci-failed if retrying) Line 23: # The publish job below requires ci-ready, so it will not fire until the Line 24: # poller flips ci-pending → ci-ready (which also prevents publishing without Line 25: # CI verification in the auto-approve race). Line 31: github.event.label.name == 'accepted' Line 76: --remove-label "accepted" Line 82: # Reset to a clean ci-pending state: Line 84: # - Remove ci-ready (retries after a publish failure — if we leave Line 85: # ci-ready, the poller's later --add-label won't generate a Line 87: # - Add ci-pending Line 90: - name: Mark ci-pending Line 97: --remove-label "ci-ready" \ Line 98: --add-label "ci-pending" Line 142: # Publish when ci-ready is ...
<path>/home/byk/Code/getsentry/publish/.github/workflows/auto-approve.yml</path>
<type>file</type>
<content>
1: name: auto-approve non-sdks
2: on:
3: issues:
4: types: [opened]
5:
6: permissions:
7: contents: read
8:
9: jobs:
10: auto-approve:
11: runs-on: ubuntu-latest
12: if: |
13: (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
14: startsWith(github.event.issue.title, 'publish: ')
15: steps:
16: - uses: actions/checkout@v4
17: with:
18: sparse-checkout: auto-approve-repos.txt
19: sparse-checkout-cone-mode: false
20: - name: Get auth token
21: id: token
22: uses: actions/create-github-app-token@v3
23: with:
24: client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
25: private-key: ${{ secrets.SENTRY_INTERNAL_APP_PRIVATE_KEY }}
26: - name: Auto-approve if repo is in the list
27: env:
28: GH_TOKEN: ${{ ...
yarn run v1.22.22 $ vitest run src/modules/__tests__/approval-authorizer.js src/publish/__tests__/authorize-approval.js [1m[46m RUN [49m[22m [36mv4.0.17 [39m[90m/home/byk/Code/getsentry/publish[39m [32m✓[39m src/publish/__tests__/authorize-approval.js [2m([22m[2m3 tests[22m[2m)[22m[32m 14[2mms[22m[39m [32m✓[39m src/modules/__tests__/approval-authorizer.js [2m([22m[2m14 tests[22m[2m)[22m[32m 20[2mms[22m[39m [2m Test Files [22m [1m[32m2 passed[39m[22m[90m (2)[39m [2m Tests [22m [1m[32m17 passed[39m[22m[90m (17)[39m [2m Start at [22m 15:55:28 [2m Duration [22m 334ms[2m (transform 57ms, setup 0ms, import 141ms, tests 33ms, environment 0ms)[22m Done in 0.84s.
<path>/home/byk/Code/getsentry/publish/auto-approve-repos.txt</path> <type>file</type> <content> 1: getsentry/arroyo 2: getsentry/auto-type-annotate 3: getsentry/devenv 4: getsentry/infra-event-notifier 5: getsentry/jest-sentry-environment 6: getsentry/json-schema-diff 7: getsentry/js-source-scopes 8: getsentry/objectstore/clients 9: getsentry/ophio 10: getsentry/pdb 11: getsentry/pyo3-python-tracing-subscriber 12: getsentry/pytest-sentry 13: getsentry/relay/py 14: getsentry/responses 15: getsentry/rust-proguard 16: getsentry/rust-sourcemap 17: getsentry/rust-usage-accountant 18: getsentry/script-runner 19: getsentry/sentry-api-schema 20: getsentry/sentry-forked-djangorestframework-stubs 21: getsentry/sentry-forked-django-stubs 22: getsentry/sentry-forked-jsonnet 23: getsentry/sentry-infra-tools 24: getsentry/sentry-kafka-management 25: getsentry/sentry-kafka-schemas 26: getsentry/sentry-protos 27: getsentry/sentry-redis-tools 28: getsentry/service-registry 29: getsentry/skrooge 30: ...
Found 56 matches /home/byk/Code/getsentry/publish/.lore.md: Line 13: * **approval-authorizer legacy titles**: Trap: requiring only \`publish: getsentry/repo@version\` looks safer, but Publish accepts legacy unqualified titles such as \`publish: sentry@version\`, including valid release paths. Fix: use Publish’s exact title grammar for authorization, map qualified and unqualified forms to the \`getsentry\` owner, and reject malformed or unsupported titles before querying GitHub. A broader authorization regex looks flexible, but it can authorize an issue Publish later cannot parse. Line 16: * **auto-approve.yml label actor**: Trap: creating an internal-app token to add \`accepted\` looks appropriate because it has label permission, but GitHub records the app as the label-event actor rather than \`getsantry\[bot]\`. An approval gate that authorizes \`github.actor\` can reject legitimate allowlisted automation or check the app’s target-repository role instead. ...
yarn run v1.22.22 $ vitest run [1m[46m RUN [49m[22m [36mv4.0.17 [39m[90m/home/byk/Code/getsentry/publish[39m [32m✓[39m src/modules/__tests__/update-issue.js [2m([22m[2m5 tests[22m[2m)[22m[32m 13[2mms[22m[39m [32m✓[39m src/modules/__tests__/post-workflow-details.js [2m([22m[2m1 test[22m[2m)[22m[32m 19[2mms[22m[39m [32m✓[39m src/modules/__tests__/approval-authorizer.js [2m([22m[2m14 tests[22m[2m)[22m[32m 33[2mms[22m[39m [32m✓[39m src/publish/__tests__/authorize-approval.js [2m([22m[2m3 tests[22m[2m)[22m[32m 14[2mms[22m[39m [32m✓[39m src/modules/__tests__/process-end-state.js [2m([22m[2m4 tests[22m[2m)[22m[32m 61[2mms[22m[39m [32m✓[39m src/modules/__tests__/details-from-context.js [2m([22m[2m4 tests[22m[2m)[22m[32m 9[2mms[22m[39m [32m✓[39m src/libs/__tests__/github.js [2m([22m[2m2 tests[22m[2m)[22m[32m 6[2mms[22m[39m [2m Test Files [22m [1m[32m7 passed[39m[22m[90m (7)[39m [2m ...
# REST API endpoints for collaborators
Use the REST API to manage collaborators for a repository.
> [!NOTE]
> Most endpoints use `Authorization: Bearer <YOUR-TOKEN>` and `Accept: application/vnd.github+json` headers, plus `X-GitHub-Api-Version: 2026-03-10`. Curl examples below omit these standard headers for brevity.
## List repository collaborators
```
GET /repos/{owner}/{repo}/collaborators
```
For organization-owned repositories, the list of collaborators includes outside collaborators, organization members that are direct collaborators, organization members with access through team memberships, organization members with access through default organization permissions, and organization owners.
The permissions hash returned in the response contains the base role permissions of the collaborator. The role_name is the highest role assigned to the collaborator after considering all sources of grants, including: repo, teams, organization, and enterprise. ...
Final security review
MUST-FIX — Critical — Title TOCTOU permits authorization for repository A and publishing repository B. Authorization consumes the accepted-event title, but only persists authorized; it never binds that title or repository to later stages. The poller and publish job consume the mutable current title. An actor authorized for A can retitle their issue to B before CI completes.
.github/workflows/publish.yml:62-65,90-98,174-176; .github/workflows/ci-poller.yml:80,90-100; src/publish/authorize-approval.js:46-53
MUST-FIX — High — Failures before authorization leave accepted attached. A failed checkout or internal-app token creation skips all later steps under GitHub Actions’ implicit success() condition. The rejection step also depends on the failed app token, so it cannot remove the label. This violates the required fail-closed cleanup behavior.
.github/workflows/publish.yml:35-45,67-80
MUST-FIX — Medium — The auto-approval bypass cannot reliably identify getsantry[bot]. auto-approve.yml adds the label with the internal-app token, so the subsequent labeled event identifies that app, not the originating bot. It also accepts sentry-release-bot[bot]. The authorization code then either rejects legitimate allowlisted automation or authorizes based on the app’s repository role rather than exact trusted provenance.
.github/workflows/auto-approve.yml:13,20-34; .github/workflows/publish.yml:62-65; src/modules/approval-authorizer.js:2,17-21
CONCERN — Low — The authorization title grammar is broader than the publish parser. It can approve titles that publishing later rejects, creating divergent behavior and future bypass risk if either parser changes. Use one shared, exact parser.
src/modules/approval-authorizer.js:3-4; src/modules/details-from-context.js:24-26
PASS — Target permission checks use GitHub’s effective repository role and allow only write, maintain, or ...
Date: Aug 27, 2026 * 🟡 (15:45) GitHub organization repository-role documentation states roles from least to most access: `Read` (view/discuss), `Triage` (manage issues/discussions/PRs without write), `Write` (push), `Maintain` (manage repository without sensitive/destructive actions), and `Admin` (full access including security management/deletion). Only `Admin` can manage individual, team, and …
Date: Aug 27, 2026 * 🔴 (15:43) User asserted that `workflow_dispatch` must always be allowed for manual recovery. * 🟡 (15:43) `.github/workflows/publish.yml` defines a cron schedule `*/5 * * * *` (every 5 minutes) and an input with default value `"0"`. * 🟡 (15:43) `.github/workflows/publish.yml` contains a `check-ci` job with configured concurrency. * 🟡 (15:43) The workflow’s CI-check logic u…
Date: Aug 27, 2026 * 🟡 [requested-security-review] (15:39) User requested a FINAL security review of the current uncommitted release-approval authorization gate in `/home/byk/Code/getsentry/publish`; instructed not to edit. Required threat-model checks: public workflow must never expose security-as-code; unauthorized actors with publish triage must not approve arbitrary target releases; GitHub A…