DashboardpublishSession 0HhxYmKC9XzC

Session 0HhxYmKC9XzC

Full ID: 0HhxYmKC9XzCymoiO
Messages: 20
Distillations: 3
Time range: 2026-08-27 15:39:11 — 2026-08-27 15:50:06

Conversation (20 messages)

Review the FINAL current uncommitted implementation for the release-approval authorization gate in /home/byk/Code/getsentry/publish. Do not edit. Focus on correctness, GitHub Actions event semantics, workflow failure/cleanup paths, supported title parsing, and tests. The intended policy is: human actor must currently have write/maintain/admin effective access to the target getsentry repository; getsantry[bot] auto-approval is permitted only for exact entries in auto-approve-repos.txt; failed checks fail closed and remove accepted. Report findings with severity and exact file:line. Classify each PASS/CONCERN/MUST-FIX and end exactly MERGE or DO-NOT-MERGE.

gateway · 2026-08-27 15:39:11
result
<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>
gateway · 2026-08-27 15:39:28
result
 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: ...
gateway · 2026-08-27 15:39:38
result
/home/byk/Code/getsentry/publish/src/publish/__tests__/authorize-approval.js
gateway · 2026-08-27 15:39:46
result
<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 ...
gateway · 2026-08-27 15:39:52
result
<path>/home/byk/Code/getsentry/publish/src/modules/approval-authorizer.js</path>
<type>file</type>
<content>
1: const ALLOWED_ROLE_NAMES = new Set(["write", "maintain", "admin"]);
2: const AUTO_APPROVER = "getsantry[bot]";
3: const PUBLISH_TITLE_REGEX =
4:   /^publish: (?:getsentry\/)?(?<repository>[^/@]+)(?<path>\/[^@]+)?@[^@]+$/;
5: 
6: async function authorizeApproval({
7:   actor,
8:   issueTitle,
9:   getPermission,
10:   autoApprovedRepositories = new Set(),
11: }) {
12:   const title = issueTitle.match(PUBLISH_TITLE_REGEX);
13: 
14:   if (!title) {
15:     return { authorized: false, repository: null };
16:   }
17: 
18:   const repository = `getsentry/${title.groups.repository}`;
19:   const releasePath = `${repository}${title.groups.path || ""}`;
20: 
21:   if (actor === AUTO_APPROVER && autoApprovedRepositories.has(releasePath)) {
22:     return { authorized: true, repository };
23:   }
24: 
25:   const { role_name } = await getPermission({
26:     owner: "getsentry",
27:     ...
gateway · 2026-08-27 15:39:59
result
<path>/home/byk/Code/getsentry/publish/src/publish/authorize-approval.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const { authorizeApproval } = require("../modules/approval-authorizer.js");
3: 
4: function getAutoApprovedRepositories() {
5:   return new Set(
6:     fs
7:       .readFileSync("auto-approve-repos.txt", "utf8")
8:       .split(/\r?\n/)
9:       .filter(Boolean)
10:   );
11: }
12: 
13: async function getPermission({ owner, repository, username }) {
14:   const response = await fetch(
15:     `https://api.github.com/repos/${encodeURIComponent(
16:       owner
17:     )}/${encodeURIComponent(repository)}/collaborators/${encodeURIComponent(
18:       username
19:     )}/permission`,
20:     {
21:       headers: {
22:         Accept: "application/vnd.github+json",
23:         Authorization: `Bearer ${process.env.APPROVAL_TOKEN}`,
24:         "X-GitHub-Api-Version": "2026-03-10",
25:       },
26:     }
27:   );
28: 
29:   if (!response.ok) {
30:     ...
gateway · 2026-08-27 15:40:06
result
<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: ...
gateway · 2026-08-27 15:40:12
result
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/approval-authorizer.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2: 
3: const { authorizeApproval } = require("../approval-authorizer.js");
4: 
5: const issueTitle = "publish: getsentry/sentry-javascript@10.0.0";
6: 
7: describe("authorizeApproval", () => {
8:   test("allows the auto-approval bot for an allowlisted release without a target repository lookup", async () => {
9:     const getPermission = vi.fn();
10: 
11:     await expect(
12:       authorizeApproval({
13:         actor: "getsantry[bot]",
14:         issueTitle,
15:         getPermission,
16:         autoApprovedRepositories: new Set(["getsentry/sentry-javascript"]),
17:       })
18:     ).resolves.toEqual({
19:       authorized: true,
20:       repository: "getsentry/sentry-javascript",
21:     });
22: 
23:     expect(getPermission).not.toHaveBeenCalled();
24:   });
25: 
26:   test("rejects the auto-approval bot ...
gateway · 2026-08-27 15:40:18
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/authorize-approval.js</path>
<type>file</type>
<content>
1: import { afterEach, describe, expect, test, vi } from "vitest";
2: import fs from "fs";
3: const {
4:   getAutoApprovedRepositories,
5:   main,
6: } = require("../authorize-approval.js");
7: 
8: const originalEnvironment = { ...process.env };
9: 
10: afterEach(() => {
11:   process.env = { ...originalEnvironment };
12:   vi.restoreAllMocks();
13:   vi.resetModules();
14: });
15: 
16: async function runAuthorization({ actor, issueTitle, response }) {
17:   process.env.GITHUB_OUTPUT = "/tmp/github-output";
18:   process.env.APPROVAL_TOKEN = "release-bot-token";
19:   process.env.APPROVAL_ACTOR = actor;
20:   process.env.APPROVAL_ISSUE_TITLE = issueTitle;
21: 
22:   const appendFileSync = vi
23:     .spyOn(fs, "appendFileSync")
24:     .mockImplementation(() => {});
25:   global.fetch = vi.fn().mockResolvedValue(response);
26: 
27:   await main();
28:   await ...
gateway · 2026-08-27 15:40:28
result
<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 || ...
gateway · 2026-08-27 15:41:00
result
Found 55 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 optional release paths. Fix: match Publish’s title grammar and map both qualified and unqualified forms explicitly to the \`getsentry\` owner before checking permissions. Reject malformed or non-getsentry titles without querying GitHub.

  Line 16: * **publish.yml approval cleanup**: Trap: letting the permission lookup fail normally looks fail-closed because the job fails, but it leaves the unsafe \`accepted\` label attached and allows CI polling to proceed. Fix: create the internal-app token before authorization, run the authorizer with \`continue-on-error\`, and remove \`accepted\` whenever its outcome fails or \`authorized\` is not true; only then fail the job. ...
gateway · 2026-08-27 15:41:32
result
<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: ${{ ...
gateway · 2026-08-27 15:42:25
result
<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 ...
gateway · 2026-08-27 15:44:51
result
yarn run v1.22.22
$ vitest run

 RUN  v4.0.17 /home/byk/Code/getsentry/publish

 ✓ src/modules/__tests__/update-issue.js (5 tests) 23ms
 ✓ src/modules/__tests__/approval-authorizer.js (14 tests) 34ms
 ✓ src/publish/__tests__/authorize-approval.js (3 tests) 24ms
 ✓ src/modules/__tests__/process-end-state.js (4 tests) 61ms
 ✓ src/modules/__tests__/post-workflow-details.js (1 test) 25ms
 ✓ src/modules/__tests__/details-from-context.js (4 tests) 8ms
 ✓ src/libs/__tests__/github.js (2 tests) 5ms

 Test Files  7 passed (7)
    ...
gateway · 2026-08-27 15:45:38
result
# Contexts reference

Find information about contexts available in GitHub Actions workflows, including available properties, access methods, and usage examples.

## Available contexts

| Context name | Type     | Description                                                                                                                                          |
| ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `github`     | `object` | Information about the workflow run. For more information, see [`github` context](#github-context).                                                   |
| `env`        | `object` | Contains variables set in a workflow, job, or step. For more information, see [`env` context](#env-context). ...
gateway · 2026-08-27 15:46:29
result
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/details-from-context.js</path>
<type>file</type>
<content>
1: import { test, expect } from "vitest";
2: 
3: const { detailsFromContext } = require("../details-from-context.js");
4: 
5: const inputsArgs = {
6:   context: {
7:     repo: { owner: "getsentry", repo: "publish" },
8:     payload: {
9:       issue: {
10:         number: "223",
11:         title: "publish: getsentry/sentry@21.3.1",
12:         body: `
13: Requested by: @BYK
14: 
15: Merge target: custom-branch
16: 
17: Quick links:
18: - [View changes](https://github.com/getsentry/sentry/compare/21.3.0...refs/heads/releases/21.3.1)
19: - [View check runs](https://github.com/getsentry/sentry/commit/7e5ca7ed5581552de066e2a8bc295b8306be38ac/checks/)
20: 
21: Assign the **accepted** label to this issue to approve the release. ...
gateway · 2026-08-27 15:48:11
result
yarn run v1.22.22
$ eslint src .github --ignore-pattern '!.github'

/home/byk/Code/getsentry/publish/.github/workflows/cocoapods-keepalive.yml
  1:7  error  Must use plain style scalar  yml/plain-scalar

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
error Command failed with exit code 1.
gateway · 2026-08-27 15:48:40
result
{ authorized: true, repository: 'getsentry/relay' }
TypeError
{ authorized: true, repository: 'getsentry/sentry-javascript' }
gateway · 2026-08-27 15:50:05

Findings:

  • MUST-FIX — Critical.github/workflows/publish.yml:64, .github/workflows/ci-poller.yml:92 — Authorization uses the title snapshot from the accepted event, while the poller later reads the current issue title. An approver for repo A can add accepted, then change the title to repo B before polling. Repo B releases without an access check for it.

  • MUST-FIX — High.github/workflows/publish.yml:63, .github/workflows/auto-approve.yml:28 — Auto-approve adds accepted with the internal-app token, so the labeled event actor is that app, not getsantry[bot]. The gate rejects valid allowlisted bot approvals or authorizes them based on the internal app’s target-repo role.

  • MUST-FIX — Highsrc/modules/approval-authorizer.js:21-31 — A non-allowlisted getsantry[bot] falls through to the permission lookup and is authorized when GitHub returns write, maintain, or admin. The bot must be rejected unless its release path exactly matches the allowlist.

  • MUST-FIX — Highsrc/modules/approval-authorizer.js:4, src/modules/details-from-context.js:25 — The authorization parser accepts titles Publish cannot parse. For example, the allowlisted publish: getsentry/relay/py@bad version authorizes, but the Publish parser throws. The regex must share Publish’s title grammar and fail closed for unsupported titles.

  • MUST-FIX — Medium.github/workflows/publish.yml:35-45, .github/workflows/publish.yml:67 — Checkout or the internal-app token can fail before the only rejection step. GitHub skips that step after an earlier failure, leaving accepted attached. This violates the required cleanup on failed checks.

  • CONCERNsrc/modules/__tests__/approval-authorizer.js:26-45 — Tests cover a non-allowlisted bot only with role_name: "none"; they miss the bypass with write. They also miss parser divergence, title mutation after approval, auto-approve’s app-label actor, and pre-authorization cleanup failures. ...

gpt-5.6-terra · 2026-08-27 15:50:06 · ~568 tokens

Distillations (3)

Gen 0 2026-08-27 15:53:51 · 1328 tokens

Date: Aug 27, 2026 * 🟡 (15:45) `yarn test` / `vitest run` completed successfully in `/home/byk/Code/getsentry/publish`: 7 test files and 33 tests passed. Suites: `src/modules/__tests__/update-issue.js` (5), `approval-authorizer.js` (14), `src/publish/__tests__/authorize-approval.js` (3), `process-end-state.js` (4), `post-workflow-details.js` (1), `details-from-context.js` (4), and `src/libs/__te…

Gen 0 2026-08-27 15:50:14 · 397 tokens

Date: Aug 27, 2026 * 🔴 (15:44) User stated that `workflow_dispatch` must always be allowed for manual recovery. * 🟡 (15:44) Shared CI poller workflow excerpt shows a cron trigger of `*/5 * * * *`, a `check-ci` job, and concurrency group `ci-status-poller`. * 🟡 (15:44) CI poller workflow comments indicate `sentry-release-bot` token is used for cross-repository API access, while `sentry-internal…

Gen 0 2026-08-27 15:48:32 · 2155 tokens

Date: Aug 27, 2026 * 🔴 [requested-review] (15:39) User requested a final review only (no edits) of the current uncommitted release-approval authorization gate implementation in `/home/byk/Code/getsentry/publish`, focused on correctness, GitHub Actions event semantics, workflow failure/cleanup paths, supported title parsing, and tests. Requested severity plus exact `file:line` for every finding; …