DashboardpublishSession 1Qc6uYMGjA8J

Session 1Qc6uYMGjA8J

Full ID: 1Qc6uYMGjA8JPChkR
Messages: 33
Distillations: 5
Time range: 2026-08-29 13:09:27 — 2026-08-29 13:31:00

Conversation (33 messages)

Perform a read-only, substantive security review of the current working tree's release approval gate. Focus on privilege boundaries, GitHub token identities, workflow expression/shell hazards, TOCTOU races, forged labels/comments/events, API failure paths, cleanup, and auto-approve provenance. Review .github/workflows/publish.yml, .github/workflows/ci-poller.yml, src/modules/approval-attestation.js, src/modules/approval-authorizer.js, src/publish/{authorize-approval,current-accepted-event,record-ci-ready-attestation,validate-approval-attestation}.js, and tests. Do not edit. Return a substantive report with file:line evidence. Classify each finding PASS, CONCERN, or MUST-FIX and end exactly MERGE or DO-NOT-MERGE. Do not return an empty response.

gateway · 2026-08-29 13:09:27
result
/home/byk/Code/getsentry/publish/src/publish/__tests__/validate-approval-attestation.js
/home/byk/Code/getsentry/publish/src/publish/__tests__/authorize-approval.js
/home/byk/Code/getsentry/publish/src/modules/__tests__/approval-attestation.js
/home/byk/Code/getsentry/publish/src/modules/__tests__/approval-authorizer.js
gateway · 2026-08-29 13:10:37
result
/home/byk/Code/getsentry/publish/src/publish/__tests__/current-accepted-event.js
/home/byk/Code/getsentry/publish/src/publish/__tests__/record-ci-ready-attestation.js
gateway · 2026-08-29 13:10:41
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-29 13:10:45
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/publish.yml</path>
<type>file</type>
<content>
241:       # Setting the target repo branch will cause the craft config (.craft.yml) to be taken from the checked out branch
242:       # By default, we check out the default branch of the repo.
243:       # If you need to maintain diverging craft configs on different branches, add your repo and the merge target branch
244:       # (i.e. the branch craft will merge the release branch into) into the if condition below.
245:       - name: Set target repo checkout branch
246:         # Note: Branches registered here MUST BE protected in the target repo!
247:         if: |
248:           fromJSON(steps.inputs.outputs.result).repo == 'sentry-migr8' && fromJSON(steps.inputs.outputs.result).merge_target == 'tmp-merge-target' ||
249:           fromJSON(steps.inputs.outputs.result).repo == 'sentry-javascript' && fromJSON(steps.inputs.outputs.result).merge_target == 'v10' ||
250:           ...
gateway · 2026-08-29 13:10:48
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: write
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:       - name: Get publish code
30:         uses: ...
gateway · 2026-08-29 13:10:51
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller.yml</path>
<type>file</type>
<content>
361:               echo "  CI failed (commit status)! Swapping ci-pending → ci-failed and removing accepted."
362:               gh issue edit "$number" -R "$GITHUB_REPOSITORY" \
363:                 --remove-label "ci-pending" \
364:                 --remove-label "accepted" \
365:                 --add-label "ci-failed"
366: 
367:               failed_contexts=$(echo "$status_json" \
368:                 | jq -r '[.statuses[] | select(.state == "failure" or .state == "error")] | map(if .target_url and .target_url != "" and .target_url != "null" then "- [" + .context + "](" + .target_url + ") (" + .state + ")" else "- " + .context + " (" + .state + ")" end) | join("\n")')
369:               comment_body="CI **commit status** checks failed for ${repo}@${version} (\`${sha:0:8}\`). Publishing is blocked."$'\n\n'"Failed status checks:"$'\n'"${failed_contexts}"$'\n\n'"[View check ...
gateway · 2026-08-29 13:12:11
result
<path>/home/byk/Code/getsentry/publish/src/modules/approval-attestation.js</path>
<type>file</type>
<content>
1: const APPROVAL_ATTESTATION_PREFIX = "<!-- publish-approval ";
2: const CI_READY_ATTESTATION_PREFIX = "<!-- publish-ci-ready ";
3: const ATTESTATION_SUFFIX = " -->";
4: 
5: function createAttestation(prefix, value) {
6:   return `${prefix}${Buffer.from(JSON.stringify(value)).toString(
7:     "base64url"
8:   )}${ATTESTATION_SUFFIX}`;
9: }
10: 
11: function createApprovalAttestation({ actor, eventId, title }) {
12:   return createAttestation(APPROVAL_ATTESTATION_PREFIX, {
13:     actor,
14:     eventId: String(eventId),
15:     title,
16:   });
17: }
18: 
19: function createCiReadyAttestation({ acceptedEvent, ciReadyActor, title }) {
20:   return createAttestation(CI_READY_ATTESTATION_PREFIX, {
21:     acceptedActor: acceptedEvent.actor,
22:     acceptedEventId: String(acceptedEvent.eventId),
23:     ciReadyActor,
24:     title,
25:   });
26: }
27: 
28: function ...
gateway · 2026-08-29 13:12:45
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 { parsePublishTitle } = require("./details-from-context.js");
4: 
5: async function authorizeApproval({
6:   actor,
7:   issueTitle,
8:   getPermission,
9:   autoApprovedRepositories = new Set(),
10: }) {
11:   const title = parsePublishTitle(issueTitle);
12: 
13:   if (!title) {
14:     return { authorized: false, repository: null };
15:   }
16: 
17:   const repository = `getsentry/${title.repo}`;
18:   const releasePath = `${repository}${title.path || ""}`;
19: 
20:   if (actor === AUTO_APPROVER && autoApprovedRepositories.has(releasePath)) {
21:     return { authorized: true, repository };
22:   }
23: 
24:   if (actor === AUTO_APPROVER) {
25:     return { authorized: false, repository };
26:   }
27: 
28:   const { role_name } = await getPermission({
29:  ...
gateway · 2026-08-29 13:12:53
result
<path>/home/byk/Code/getsentry/publish/src/publish/authorize-approval.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3:   createApprovalAttestation,
4:   currentAcceptedEvent,
5: } = require("../modules/approval-attestation.js");
6: const { authorizeApproval } = require("../modules/approval-authorizer.js");
7: 
8: function getAutoApprovedRepositories() {
9:   return new Set(
10:     fs
11:       .readFileSync("auto-approve-repos.txt", "utf8")
12:       .split(/\r?\n/)
13:       .filter(Boolean)
14:   );
15: }
16: 
17: async function getPermission({ owner, repository, username }) {
18:   const response = await getGitHubResponse(
19:     `repos/${encodeURIComponent(owner)}/${encodeURIComponent(
20:       repository
21:     )}/collaborators/${encodeURIComponent(username)}/permission`
22:   );
23: 
24:   if (!response.ok) {
25:     throw new Error(
26:       `Could not retrieve ${username}'s permission for ${owner}/${repository}: GitHub returned ...
gateway · 2026-08-29 13:12:58
result
<path>/home/byk/Code/getsentry/publish/src/publish/current-accepted-event.js</path>
<type>file</type>
<content>
1: const { currentAcceptedEvent } = require("../modules/approval-attestation.js");
2: const { getAllPages } = require("./validate-approval-attestation.js");
3: 
4: async function getCurrentAcceptedEvent({
5:   getIssueEvents,
6:   issueNumber,
7:   repository,
8: }) {
9:   const event = currentAcceptedEvent(
10:     await getIssueEvents({ repository, issueNumber })
11:   );
12: 
13:   if (!event) {
14:     throw new Error("The issue has no current accepted label event");
15:   }
16: 
17:   return event;
18: }
19: 
20: async function main() {
21:   for (const name of [
22:     "APPROVAL_TOKEN",
23:     "APPROVAL_ISSUE_NUMBER",
24:     "APPROVAL_ISSUE_REPOSITORY",
25:   ]) {
26:     if (!process.env[name]) {
27:       throw new Error(`No "${name}" environment variable found`);
28:     }
29:   }
30: 
31:   const event = await getCurrentAcceptedEvent({
32:     getIssueEvents: ({ ...
gateway · 2026-08-29 13:13:02
result
<path>/home/byk/Code/getsentry/publish/src/publish/record-ci-ready-attestation.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3:   createCiReadyAttestation,
4:   currentAcceptedEvent,
5: } = require("../modules/approval-attestation.js");
6: const {
7:   getAllPages,
8:   getGitHubResponse,
9:   getIssue,
10: } = require("./validate-approval-attestation.js");
11: 
12: async function getAuthenticatedLogin() {
13:   const response = await getGitHubResponse("user");
14: 
15:   if (!response.ok) {
16:     throw new Error(
17:       `Could not retrieve the authenticated user: GitHub returned ${response.status}`
18:     );
19:   }
20: 
21:   const { login } = await response.json();
22: 
23:   if (typeof login !== "string") {
24:     throw new Error("GitHub returned no authenticated user login");
25:   }
26: 
27:   return login;
28: }
29: 
30: async function recordCiReadyAttestation({
31:   expectedAcceptedEvent,
32:   getAuthenticatedLogin,
33:   getIssue,
34:  ...
gateway · 2026-08-29 13:13:07
result
<path>/home/byk/Code/getsentry/publish/src/publish/validate-approval-attestation.js</path>
<type>file</type>
<content>
1: const {
2:   currentAcceptedEvent,
3:   currentCiReadyEvent,
4:   hasApprovalAttestation,
5:   hasCiReadyAttestation,
6: } = require("../modules/approval-attestation.js");
7: 
8: async function getGitHubResponse(path) {
9:   return fetch(`https://api.github.com/${path}`, {
10:     headers: {
11:       Accept: "application/vnd.github+json",
12:       Authorization: `Bearer ${process.env.APPROVAL_TOKEN}`,
13:       "X-GitHub-Api-Version": "2026-03-10",
14:     },
15:   });
16: }
17: 
18: async function getIssue({ repository, issueNumber }) {
19:   const response = await getGitHubResponse(
20:     `repos/${repository}/issues/${issueNumber}`
21:   );
22: 
23:   if (!response.ok) {
24:     throw new Error(
25:       `Could not retrieve issue #${issueNumber}: GitHub returned ${response.status}`
26:     );
27:   }
28: 
29:   return response.json();
30: }
31: 
32: async ...
gateway · 2026-08-29 13:13:15
result
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/approval-attestation.js</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "vitest";
2: 
3: const {
4:   createApprovalAttestation,
5:   createCiReadyAttestation,
6:   currentAcceptedEvent,
7:   hasApprovalAttestation,
8:   hasCiReadyAttestation,
9:   parseApprovalAttestation,
10: } = require("../approval-attestation.js");
11: 
12: describe("approval attestations", () => {
13:   test("matches the latest accepted event to a trusted attestation", () => {
14:     const attestation = createApprovalAttestation({
15:       actor: "contractor",
16:       eventId: "200",
17:       title: "publish: getsentry/relay/py@1.2.3",
18:     });
19:     const event = currentAcceptedEvent([
20:       {
21:         actor: { login: "contractor" },
22:         event: "labeled",
23:         id: "100",
24:         label: { name: "accepted" },
25:       },
26:       {
27:         actor: { login: "contractor" },
28:         ...
gateway · 2026-08-29 13:13:22
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-29 13:13:30
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: function jsonResponse(json) {
17:   return { ok: true, json: vi.fn().mockResolvedValue(json) };
18: }
19: 
20: async function runAuthorization({ actor, issueTitle, responses }) {
21:   process.env.GITHUB_OUTPUT = "/tmp/github-output";
22:   process.env.APPROVAL_TOKEN = "release-bot-token";
23:   process.env.APPROVAL_ACTOR = actor;
24:   process.env.APPROVAL_ISSUE_NUMBER = "123";
25:   process.env.APPROVAL_ISSUE_REPOSITORY = "getsentry/publish";
26:   process.env.APPROVAL_ISSUE_TITLE = ...
gateway · 2026-08-29 13:13:36
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/current-accepted-event.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2: 
3: const { getCurrentAcceptedEvent } = require("../current-accepted-event.js");
4: 
5: describe("getCurrentAcceptedEvent", () => {
6:   test("returns the latest accepted-label event", async () => {
7:     await expect(
8:       getCurrentAcceptedEvent({
9:         getIssueEvents: vi.fn().mockResolvedValue([
10:           {
11:             actor: { login: "contractor" },
12:             event: "labeled",
13:             id: "100",
14:             label: { name: "accepted" },
15:           },
16:           {
17:             actor: { login: "contractor" },
18:             event: "labeled",
19:             id: "200",
20:             label: { name: "accepted" },
21:           },
22:         ]),
23:         issueNumber: "123",
24:         repository: "getsentry/publish",
25:       })
26:     ).resolves.toEqual({ ...
gateway · 2026-08-29 13:13:42
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/record-ci-ready-attestation.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2: 
3: const {
4:   parseCiReadyAttestation,
5: } = require("../../modules/approval-attestation.js");
6: const {
7:   recordCiReadyAttestation,
8: } = require("../record-ci-ready-attestation.js");
9: 
10: describe("recordCiReadyAttestation", () => {
11:   test("binds the current approval to the app that will add ci-ready", async () => {
12:     const title = "publish: getsentry/sentry-javascript@10.0.0";
13: 
14:     await expect(
15:       recordCiReadyAttestation({
16:         getAuthenticatedLogin: vi
17:           .fn()
18:           .mockResolvedValue("sentry-internal-app[bot]"),
19:         getIssue: vi.fn().mockResolvedValue({
20:           labels: [{ name: "accepted" }],
21:           title,
22:         }),
23:         getIssueEvents: vi.fn().mockResolvedValue([
24:           {
25:             ...
gateway · 2026-08-29 13:13:46
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/validate-approval-attestation.js</path>
<type>file</type>
<content>
1: import { afterEach, describe, expect, test, vi } from "vitest";
2: 
3: const {
4:   validateApprovalAttestation,
5: } = require("../validate-approval-attestation.js");
6: const {
7:   createApprovalAttestation,
8:   createCiReadyAttestation,
9: } = require("../../modules/approval-attestation.js");
10: 
11: afterEach(() => {
12:   vi.restoreAllMocks();
13: });
14: 
15: function jsonResponse(json) {
16:   return { ok: true, json: vi.fn().mockResolvedValue(json) };
17: }
18: 
19: describe("validateApprovalAttestation", () => {
20:   test("accepts the current accepted event and its trusted attestation", async () => {
21:     const title = "publish: getsentry/sentry-javascript@10.0.0";
22:     const attestation = createApprovalAttestation({
23:       actor: "contractor",
24:       eventId: "100",
25:       title,
26:     });
27:     global.fetch = vi
28:       ...
gateway · 2026-08-29 13:13:51
result
File not found: /home/byk/Code/getsentry/publish/.jj
gateway · 2026-08-29 13:13:58
result
 M .github/workflows/ci-poller.yml
 M .github/workflows/publish.yml
 M AGENTS.md
 M src/libs/__tests__/github.js
 M src/libs/github.js
 M src/modules/__tests__/details-from-context.js
 M src/modules/details-from-context.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-attestation.js
?? src/modules/__tests__/approval-authorizer.js
?? src/modules/approval-attestation.js
?? src/modules/approval-authorizer.js
?? src/publish/__tests__/
?? src/publish/authorize-approval.js
?? src/publish/current-accepted-event.js
?? src/publish/record-ci-ready-attestation.js
?? src/publish/validate-approval-attestation.js
diff --git a/.github/workflows/ci-poller.yml b/.github/workflows/ci-poller.yml
index a3db430..a557c2d 100644
--- a/.github/workflows/ci-poller.yml
+++ ...
gateway · 2026-08-29 13:14:03
result
No files found
gateway · 2026-08-29 13:14:10
result
No files found
gateway · 2026-08-29 13:14:19
result
<path>/home/byk/Code/getsentry/publish/.github/workflows</path>
<type>directory</type>
<entries>
auto-approve.yml
ci-poller.yml
cocoapods-keepalive.yml
publish.yml
test.yml

(5 entries)
</entries>
gateway · 2026-08-29 13:14:30
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-29 13:14:36
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: const PUBLISH_TITLE_REGEX =
19:   /^publish: ...
gateway · 2026-08-29 13:14:39
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-29 13:17:13
result
<path>/home/byk/Code/getsentry/publish/src/publish/inputs.js</path>
<type>file</type>
<content>
1: const github = require("@actions/github");
2: const core = require("@actions/core");
3: const { detailsFromContext } = require("../modules/details-from-context");
4: 
5: async function inputs() {
6:   const result = await detailsFromContext({
7:     context: github.context,
8:   });
9:   core.setOutput("result", result);
10: }
11: 
12: inputs();

(End of file - total 12 lines)
</content>
gateway · 2026-08-29 13:18:23
result
Found 7 matches
/home/byk/Code/getsentry/publish/docs/rfc.md:
  Line 10: 3. We need SDK releases to be approved by a manager or senior engineer per our SOC/ISO compliance policy

  Line 23: 6. The owners of the release repo adds an "accepted" tag to trigger the actual release

  Line 25: 8. Special case: OSS CalVer releases are done automatically and get automatically approved unless there is a release blocker.

  Line 30: 2. Should release repo owners still need to create an issue to trigger a release, even if they mark it as approved themselves? (or should we always require a second person to approve, like a code review)


/home/byk/Code/getsentry/publish/docs/key_rotation_vault_changes.md:
  Line 13: The actual publish via Craft however still requires access to the credentials. For that process we need to ensure that craft doesn't have to run on employees' machines but somewhere, where access tokens cannot be accessed but approved engineers can click publish. ...
gateway · 2026-08-29 13:18:32
result
<path>/home/byk/Code/getsentry/publish/docs/rfc.md</path>
<type>file</type>
<content>
1: # [RFC] Central Publish Repository
2: 
3: This design document is written to address the concerns mentioned in [Key Rotation & Vault Changes](key_rotation_vault_changes.md). Implementation pending feedback from engineering team members, specifically the SDK team.
4: 
5: # Problem Statement
6: 
7: 1. We need to be able to quickly recycle all publishing tokens (NPM, PyPI, Cargo, Docker, etc.) at will without disrupting our releases or our developer flow
8: 2. We need to limit the publishing tokens to authorized personnel only and not leak them in any way
9:     1. GitHub secrets is not enough as it lets anyone with write access to the repo to access the secrets and scuttle them out, even in a brand new PR. Example: [https://github.com/getsentry/sentry/pull/21930](https://github.com/getsentry/sentry/pull/21930)
10: 3. ...
gateway · 2026-08-29 13:18:44
result
yarn run v1.22.22
$ vitest run src/modules/__tests__/approval-attestation.js src/modules/__tests__/approval-authorizer.js src/publish/__tests__/authorize-approval.js src/publish/__tests__/current-accepted-event.js src/publish/__tests__/record-ci-ready-attestation.js src/publish/__tests__/validate-approval-attestation.js

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

 ✓ src/publish/__tests__/validate-approval-attestation.js (7 tests) 51ms
 ✓ src/modules/__tests__/approval-authorizer.js (14 tests) 26ms
 ✓ src/publish/__tests__/current-accepted-event.js (2 tests) 37ms
 ✓ src/modules/__tests__/approval-attestation.js (4 tests) 36ms
 ✓ src/publish/__tests__/authorize-approval.js (3 tests) ...
gateway · 2026-08-29 13:24:06
result
<path>/home/byk/Code/getsentry/publish/src/modules/update-issue.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3:   TARGETS_SECTION_PARSER_REGEX,
4:   TARGETS_PARSER_REGEX,
5: } = require("./details-from-context");
6: 
7: async function updateTargets({ octokit, version, publishRepo, issue_number }) {
8:   const CRAFT_STATE_FILE_PATH = `${process.env.GITHUB_WORKSPACE}/__repo__/.craft-publish-${version}.json`;
9: 
10:   if (!fs.existsSync(CRAFT_STATE_FILE_PATH)) {
11:     return;
12:   }
13: 
14:   const issueRequest = octokit.rest.issues.get({
15:     ...publishRepo,
16:     issue_number,
17:   });
18: 
19:   const craftStateRequest = fs.promises
20:     .readFile(CRAFT_STATE_FILE_PATH, { encoding: "utf-8" })
21:     .then((data) => JSON.parse(data));
22: 
23:   const [{ data: issue }, craftState] = await Promise.all([
24:     issueRequest,
25:     craftStateRequest,
26:   ]);
27: 
28:   const newIssueBody = transformIssueBody(craftState, issue.body);
29: ...
gateway · 2026-08-29 13:30:58

Security review

Targeted tests pass: 33/33 across the six approval-attestation/authorization test files.

MUST-FIX — Approval does not bind the actual release request

src/modules/approval-attestation.js:11-25 signs only actor, accepted-event ID, and title. src/publish/authorize-approval.js:111-136 reads only the issue title; src/publish/validate-approval-attestation.js:63-99 validates only title, labels, events, and comments.

This leaves the issue body and dry-run label mutable after approval:

  • ci-poller.yml:149-156 extracts the CI SHA from the mutable body.
  • src/modules/details-from-context.js:36-64 derives dry-run, merge target, and targets from mutable issue state.
  • src/publish/inputs.js:6-9 later uses the ci-ready event snapshot, which can contain attacker-modified body/labels.

A Publish-repo issue editor can change the checked SHA to a known-green commit, change targets or merge target, or remove dry-run after a valid approval. The poller then validates CI for the altered SHA and Craft publishes the altered request. Bind a canonical request digest—including relevant body-derived fields, dry-run state, and checked SHA—to both attestations and validate it at every transition. Tests cover changed titles (validate-approval-attestation.js:58-95) but not this mutation.

MUST-FIX — The gate permits self-approval

src/modules/approval-authorizer.js:1,28-34 authorizes any target-repository write, maintain, or admin collaborator. src/publish/authorize-approval.js:111-120 fetches no issue author, so it cannot reject an actor approving their own request.

A user with Publish issue-label rights and target-repository write access can create a release issue and apply accepted themselves. This violates the required second-person approval boundary. Fetch and bind the requester identity, reject self-approval outside the explicit automatic path, and use a designated approver policy rather than generic write. ...

gpt-5.6-terra · 2026-08-29 13:31:00 · ~1.5K tokens

Distillations (5)

Gen 0 2026-08-29 13:33:41 · 1897 tokens

Date: Aug 29, 2026 * 🟡 (13:30) User provided `src/modules/update-issue.js` (77 lines) from `/home/byk/Code/getsentry/publish`. `updateTargets({ octokit, version, publishRepo, issue_number })` reads `${process.env.GITHUB_WORKSPACE}/__repo__/.craft-publish-${version}.json` only if it exists; concurrently fetches the issue and parses the Craft state JSON; then calls `transformIssueBody(craftState, …

Gen 0 2026-08-29 13:29:53 · 315 tokens

Date: Aug 29, 2026 * 🟡 (13:24) Targeted Vitest command `yarn run v1.22.22` / `vitest run src/modules/__tests__/approval-attestation.js src/modules/__tests__/approval-authorizer.js src/publish/__tests__/authorize-approval.js src/publish/__tests__/current-accepted-event.js src/publish/__tests__/record-ci-ready-attestation.js src/publish/__tests__/validate-approval-attestation.js` passed in `/home/…

Gen 0 2026-08-29 13:29:45 · 2720 tokens

Date: Aug 29, 2026 * 🟡 (13:13) `src/publish/validate-approval-attestation.js:8-52` defines authenticated GitHub REST helpers using `APPROVAL_TOKEN`, `Accept: application/vnd.github+json`, and `X-GitHub-Api-Version: 2026-03-10`; `getIssue()` fetches `repos/${repository}/issues/${issueNumber}`, and `getAllPages()` fetches `events` or `comments` with `per_page=100&page=${page}` until a page has few…

Gen 0 2026-08-29 13:14:47 · 1408 tokens

Date: Aug 29, 2026 * 🟡 (13:12) `.github/workflows/ci-poller.yml:361-370` handles failed commit-status checks by swapping `ci-pending` to `ci-failed`, removing `accepted`, and posting a comment identifying failed contexts, target URLs when available, `${repo}@${version}`, short SHA `${sha:0:8}`, check-runs URL, and retry instruction to re-add `accepted`. * 🟡 (13:12) `.github/workflows/ci-poller.…

Gen 0 2026-08-29 13:14:18 · 1916 tokens

Date: Aug 29, 2026 * 🟡 [requested-security-review] (13:09) User requested a read-only, substantive security review of the current working tree’s release approval gate; explicitly requested no edits. Scope: privilege boundaries, GitHub token identities, workflow expression/shell hazards, TOCTOU races, forged labels/comments/events, API failure paths, cleanup, and auto-approve provenance. * 🟡 (13…