@sentry/junior-github 0.98.0 → 0.99.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/SETUP.md +3 -3
  2. package/dist/index.js +31 -157
  3. package/package.json +2 -2
package/SETUP.md CHANGED
@@ -98,7 +98,7 @@ Use `additionalUserScopes` only when a human-identity integration flow requires
98
98
  ## 3) Runtime behavior
99
99
 
100
100
  - When either GitHub skill is active, authenticated `gh` and `git` commands cause the runtime to inject GitHub credentials automatically for the current turn.
101
- - The plugin classifies GitHub traffic from the forwarded HTTP request. Reads use `installation-read`, while `GET /user` uses `user-read`. Workflow dispatches use the repository-scoped `installation-actions-write` grant. Allowlisted issue and pull request mutations use their own repository-scoped installation grants. Git smart-HTTP pushes use `installation-pr-branch-write`. Unknown REST writes and GraphQL mutations are denied.
101
+ - The plugin classifies GitHub traffic from the forwarded HTTP request. Reads use `installation-read`, while `GET /user` uses `user-read`. Allowlisted App-owned mutations and Git smart-HTTP pushes share the repository-scoped `installation-write` grant. Unknown REST writes and GraphQL mutations are denied.
102
102
  - `user-read` and the remaining explicitly human `user-write` operations require the actor, or an explicitly delegated user subject, to authorize the GitHub App through the private OAuth flow. Junior-owned issue, pull request, and branch operations do not fall back to user OAuth.
103
103
  - Headless resource-event turns use the `resource-event` system actor and may receive the same repository-scoped installation grants. This lets Junior respond to subscribed pull request events by committing and pushing fixes without inheriting a subscriber's OAuth credential.
104
104
  - Git commits use Junior as author and committer. Resolvable human run actors are credited once with `Co-Authored-By` trailers.
@@ -133,14 +133,14 @@ The plugin uses installation credentials for read-only GitHub traffic, workflow
133
133
  Committing and pushing code uses more than one GitHub surface:
134
134
 
135
135
  - Creating the local Git commit does not call GitHub. Junior sets the GitHub App bot as author and committer and credits resolvable human actors with `Co-Authored-By` trailers.
136
- - Pushing a branch with Git smart HTTP (`git push`) uses the repository-scoped `installation-pr-branch-write` grant and requires `Contents: write`. If `Workflows: write` is configured, it is included so workflow-file changes can be pushed.
136
+ - Pushing a branch with Git smart HTTP (`git push`) uses the repository-scoped `installation-write` grant and requires `Contents: write`. If `Workflows: write` is configured, it is included so workflow-file changes can be pushed.
137
137
  - The smart-HTTP classifier does not distinguish Junior-managed branches or independently detect force updates or ref deletion. Use GitHub branch protection and limit the App installation to repositories where Junior may push.
138
138
  - REST Git database and ref writes are denied by the current write allowlist. Use Git smart HTTP (`git push`) for branch updates instead.
139
139
  - Opening the PR after the branch exists is separate: `github_createPullRequest` needs pull-request write permission, but it should not create or push commits itself.
140
140
 
141
141
  Fork creation is not part of the default PR path and is denied by the current write allowlist. Do not grant `Administration: write` for routine PR creation; push a branch explicitly and create the PR with `github_createPullRequest` instead.
142
142
 
143
- GitHub App permission scoping is a safety rail, not a hard sandbox boundary. It helps prevent accidental write scope and wrong-repo mutations, and the host runtime still decides when to mint credentials. Credential injection is provider-domain scoped for sandbox traffic to `api.github.com` and `github.com` during turns with a signed credential context. Keep repo context explicit, and let the plugin choose the required grant for the outbound request.
143
+ Repository scoping and the egress allowlist are the write boundaries. Credential injection is provider-domain scoped for sandbox traffic to `api.github.com` and `github.com` during turns with a signed credential context. Keep repo context explicit, and let the plugin choose the grant for the outbound request.
144
144
 
145
145
  Be careful with mixed-surface PR commands. Use the allowlisted REST endpoints
146
146
  rather than GraphQL-backed `gh pr` mutation commands. PR-native title, body,
package/dist/index.js CHANGED
@@ -848,21 +848,8 @@ var USER_REFRESH_TIMEOUT_MS = 2e4;
848
848
  var GITHUB_GRAPHQL_RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
849
849
  var HTTP_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
850
850
  var USER_TOKEN_GRANTS = /* @__PURE__ */ new Set(["user-read", "user-write"]);
851
- var CONTENTS_WRITE_REQUIREMENTS = [
852
- "GitHub App Contents: write on the target repository"
853
- ];
854
- var ACTIONS_WRITE_REQUIREMENTS = [
855
- "GitHub App Actions: write on the target repository"
856
- ];
857
- var ISSUES_WRITE_REQUIREMENTS = [
858
- "GitHub App Issues: write on the target repository"
859
- ];
860
- var PULL_REQUESTS_WRITE_REQUIREMENTS = [
861
- "GitHub App Pull requests: write on the target repository"
862
- ];
863
- var PULL_REVIEW_WRITE_REQUIREMENTS = [
864
- ...PULL_REQUESTS_WRITE_REQUIREMENTS,
865
- "requesting GitHub user permission to review the pull request"
851
+ var USER_WRITE_REQUIREMENTS = [
852
+ "requesting GitHub user permission to perform this operation"
866
853
  ];
867
854
  var GitHubUserRefreshRejectedError = class extends Error {
868
855
  constructor(message) {
@@ -1586,11 +1573,6 @@ async function issueInstallationCredential(options) {
1586
1573
  }
1587
1574
  const appJwt = createAppJwt(appId, options.privateKeyEnv);
1588
1575
  const permissions = options.permissions ?? await options.loadPermissions?.({ appJwt, installationId });
1589
- if (!permissions) {
1590
- throw new GitHubPluginSetupError(
1591
- "GitHub installation credential permissions are not configured."
1592
- );
1593
- }
1594
1576
  const accessTokenResponse = await githubRequest(
1595
1577
  "https://api.github.com",
1596
1578
  `/app/installations/${installationId}/access_tokens`,
@@ -1598,7 +1580,7 @@ async function issueInstallationCredential(options) {
1598
1580
  method: "POST",
1599
1581
  token: appJwt,
1600
1582
  body: {
1601
- permissions,
1583
+ ...permissions ? { permissions } : {},
1602
1584
  ...options.repositories ? { repositories: options.repositories } : {}
1603
1585
  }
1604
1586
  }
@@ -1785,7 +1767,7 @@ function shouldInspectGitHubGraphqlResponse(ctx) {
1785
1767
  const contentType = ctx.response.headers.get("content-type");
1786
1768
  return contentType ? /\bjson\b/i.test(contentType) : false;
1787
1769
  }
1788
- function githubApiWriteReason(method, upstreamUrl) {
1770
+ function githubApiWriteGrantName(method, upstreamUrl) {
1789
1771
  const pathname = upstreamUrl.pathname.toLowerCase();
1790
1772
  if (!isGitHubApiUrl(upstreamUrl)) {
1791
1773
  return void 0;
@@ -1793,56 +1775,38 @@ function githubApiWriteReason(method, upstreamUrl) {
1793
1775
  if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/actions\/workflows\/[^/]+\/dispatches$/.test(
1794
1776
  pathname
1795
1777
  )) {
1796
- return "github.actions-workflow-dispatch";
1778
+ return "installation-write";
1797
1779
  }
1798
1780
  if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/issues$/.test(pathname)) {
1799
- return "github.issue-create";
1781
+ return "installation-write";
1800
1782
  }
1801
1783
  if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/issues\/[^/]+\/comments$/.test(pathname)) {
1802
- return "github.issues-write";
1784
+ return "installation-write";
1803
1785
  }
1804
1786
  if (method === "PATCH" && /^\/repos\/[^/]+\/[^/]+\/issues\/[^/]+$/.test(pathname)) {
1805
- return "github.issues-write";
1787
+ return "installation-write";
1806
1788
  }
1807
1789
  if ((method === "POST" || method === "DELETE") && /^\/repos\/[^/]+\/[^/]+\/issues\/[^/]+\/(labels|assignees)(?:\/[^/]+)?$/.test(
1808
1790
  pathname
1809
1791
  )) {
1810
- return "github.issues-write";
1792
+ return "installation-write";
1811
1793
  }
1812
1794
  if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/pulls$/.test(pathname)) {
1813
- return "github.pull-create";
1795
+ return "installation-write";
1814
1796
  }
1815
1797
  if (method === "PATCH" && /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+$/.test(pathname)) {
1816
- return "github.pull-requests-write";
1798
+ return "installation-write";
1817
1799
  }
1818
1800
  if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+\/ready_for_review$/.test(pathname)) {
1819
- return "github.pull-requests-write";
1801
+ return "installation-write";
1820
1802
  }
1821
1803
  if ((method === "POST" || method === "DELETE") && /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+\/requested_reviewers$/.test(pathname)) {
1822
- return "github.pull-requests-write";
1804
+ return "installation-write";
1823
1805
  }
1824
1806
  if (/^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+\/reviews(?:\/[^/]+(?:\/(events|dismissals))?)?$/.test(
1825
1807
  pathname
1826
1808
  ) && !HTTP_READ_METHODS.has(method)) {
1827
- return "github.pull-review-write";
1828
- }
1829
- if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/forks$/.test(pathname)) {
1830
- return "github.fork-create";
1831
- }
1832
- if (/^\/repos\/[^/]+\/[^/]+\/contents(?:\/|$)/.test(pathname) && (method === "PUT" || method === "DELETE")) {
1833
- return pathname.includes("/.github/workflows/") ? "github.workflows-write" : "github.contents-write";
1834
- }
1835
- if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/git\/(blobs|trees|commits)$/.test(pathname)) {
1836
- return "github.contents-write";
1837
- }
1838
- if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/git\/refs$/.test(pathname)) {
1839
- return "github.contents-write";
1840
- }
1841
- if ((method === "PATCH" || method === "DELETE") && /^\/repos\/[^/]+\/[^/]+\/git\/refs\/.+/.test(pathname)) {
1842
- return "github.contents-write";
1843
- }
1844
- if (method === "PUT" && /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+\/merge$/.test(pathname)) {
1845
- return "github.contents-write";
1809
+ return "user-write";
1846
1810
  }
1847
1811
  return void 0;
1848
1812
  }
@@ -1915,22 +1879,7 @@ function assertGitHubWriteAllowed(input) {
1915
1879
  }
1916
1880
  }
1917
1881
  function grantRequirements(reason) {
1918
- if (reason === "github.actions-workflow-dispatch") {
1919
- return ACTIONS_WRITE_REQUIREMENTS;
1920
- }
1921
- if (reason === "github.git-write" || reason === "github.contents-write") {
1922
- return CONTENTS_WRITE_REQUIREMENTS;
1923
- }
1924
- if (reason === "github.issue-create" || reason === "github.issues-write") {
1925
- return ISSUES_WRITE_REQUIREMENTS;
1926
- }
1927
- if (reason === "github.pull-review-write") {
1928
- return PULL_REVIEW_WRITE_REQUIREMENTS;
1929
- }
1930
- if (reason === "github.pull-create" || reason === "github.pull-requests-write") {
1931
- return PULL_REQUESTS_WRITE_REQUIREMENTS;
1932
- }
1933
- return void 0;
1882
+ return reason === "github.user-write" ? USER_WRITE_REQUIREMENTS : void 0;
1934
1883
  }
1935
1884
  function grantForAccess(access, reason, name, leaseScope) {
1936
1885
  const requirements = grantRequirements(reason);
@@ -1951,37 +1900,6 @@ function repositoryLeaseScope(upstreamUrl) {
1951
1900
  }
1952
1901
  return githubRepositoryLeaseScope(repository);
1953
1902
  }
1954
- function installationGrantForWrite(reason, upstreamUrl) {
1955
- const leaseScope = repositoryLeaseScope(upstreamUrl);
1956
- if (reason === "github.actions-workflow-dispatch") {
1957
- return grantForAccess(
1958
- "write",
1959
- reason,
1960
- "installation-actions-write",
1961
- leaseScope
1962
- );
1963
- }
1964
- if (reason === "github.issue-create" || reason === "github.issues-write") {
1965
- return grantForAccess(
1966
- "write",
1967
- reason,
1968
- "installation-issues-write",
1969
- leaseScope
1970
- );
1971
- }
1972
- if (reason === "github.pull-create" || reason === "github.pull-requests-write") {
1973
- return grantForAccess(
1974
- "write",
1975
- reason,
1976
- "installation-pull-requests-write",
1977
- leaseScope
1978
- );
1979
- }
1980
- if (reason === "github.pull-review-write") {
1981
- return grantForAccess("write", reason, "user-write", leaseScope);
1982
- }
1983
- return void 0;
1984
- }
1985
1903
  async function githubGrantForEgress(ctx) {
1986
1904
  const method = ctx.request.method.toUpperCase();
1987
1905
  const upstreamUrl = new URL(ctx.request.url);
@@ -1996,8 +1914,8 @@ async function githubGrantForEgress(ctx) {
1996
1914
  if (smartHttpAccess === "write") {
1997
1915
  return grantForAccess(
1998
1916
  "write",
1999
- "github.git-write",
2000
- "installation-pr-branch-write",
1917
+ "github.installation-write",
1918
+ "installation-write",
2001
1919
  repositoryLeaseScope(upstreamUrl)
2002
1920
  );
2003
1921
  }
@@ -2011,14 +1929,13 @@ async function githubGrantForEgress(ctx) {
2011
1929
  if (userReadReason) {
2012
1930
  return grantForAccess("read", userReadReason, "user-read");
2013
1931
  }
2014
- const writeReason = githubApiWriteReason(method, upstreamUrl);
2015
- if (writeReason) {
2016
- const grant = installationGrantForWrite(writeReason, upstreamUrl);
2017
- if (grant) {
2018
- return grant;
2019
- }
2020
- throw new EgressPolicyDenied(
2021
- `GitHub write operation ${writeReason} is not enabled for Junior credentials.`
1932
+ const writeGrantName = githubApiWriteGrantName(method, upstreamUrl);
1933
+ if (writeGrantName) {
1934
+ return grantForAccess(
1935
+ "write",
1936
+ writeGrantName === "user-write" ? "github.user-write" : "github.installation-write",
1937
+ writeGrantName,
1938
+ repositoryLeaseScope(upstreamUrl)
2022
1939
  );
2023
1940
  }
2024
1941
  const graphqlAccess = githubGraphqlAccess(
@@ -2046,38 +1963,6 @@ async function githubGrantForEgress(ctx) {
2046
1963
  }
2047
1964
  return grantForAccess(access, "github.api-read", "installation-read");
2048
1965
  }
2049
- function configuredWritePermission(appPermissions, permission) {
2050
- const level = appPermissions?.[permission];
2051
- if (level !== void 0 && level !== "write" && level !== "admin") {
2052
- throw new GitHubPluginSetupError(
2053
- `githubPlugin appPermissions.${permission} must allow write access for Junior-owned GitHub resources.`
2054
- );
2055
- }
2056
- return {
2057
- metadata: "read",
2058
- ...permission === "pull_requests" ? { contents: "read" } : {},
2059
- [permission]: "write"
2060
- };
2061
- }
2062
- function configuredBranchWritePermissions(appPermissions) {
2063
- const contents = appPermissions?.contents;
2064
- if (contents !== void 0 && contents !== "write" && contents !== "admin") {
2065
- throw new GitHubPluginSetupError(
2066
- "githubPlugin appPermissions.contents must allow write access for Junior-managed pull request branches."
2067
- );
2068
- }
2069
- const workflows = appPermissions?.workflows;
2070
- if (workflows !== void 0 && workflows !== "write" && workflows !== "admin") {
2071
- throw new GitHubPluginSetupError(
2072
- "githubPlugin appPermissions.workflows must allow write access when configured."
2073
- );
2074
- }
2075
- return {
2076
- contents: "write",
2077
- metadata: "read",
2078
- ...workflows === "write" || workflows === "admin" ? { workflows: "write" } : {}
2079
- };
2080
- }
2081
1966
  function githubPlugin(options = {}) {
2082
1967
  const botNameEnv = options.botNameEnv ?? "GITHUB_APP_BOT_NAME";
2083
1968
  const botEmailEnv = options.botEmailEnv ?? "GITHUB_APP_BOT_EMAIL";
@@ -2208,23 +2093,7 @@ function githubPlugin(options = {}) {
2208
2093
  ...appReadPermissions ? { permissions: appReadPermissions } : { loadPermissions: loadReadPermissions }
2209
2094
  });
2210
2095
  }
2211
- if (ctx.grant.name === "installation-actions-write" || ctx.grant.name === "installation-issues-write" || ctx.grant.name === "installation-pull-requests-write") {
2212
- const repository = githubRepositoryFromLeaseScope(
2213
- ctx.grant.leaseScope
2214
- );
2215
- const permission = ctx.grant.name === "installation-actions-write" ? "actions" : ctx.grant.name === "installation-issues-write" ? "issues" : "pull_requests";
2216
- return await issueInstallationCredential({
2217
- appIdEnv,
2218
- privateKeyEnv,
2219
- installationIdEnv,
2220
- permissions: configuredWritePermission(
2221
- appPermissions,
2222
- permission
2223
- ),
2224
- repositories: [repository.name]
2225
- });
2226
- }
2227
- if (ctx.grant.name === "installation-pr-branch-write") {
2096
+ if (ctx.grant.name === "installation-write") {
2228
2097
  const repository = githubRepositoryFromLeaseScope(
2229
2098
  ctx.grant.leaseScope
2230
2099
  );
@@ -2232,7 +2101,12 @@ function githubPlugin(options = {}) {
2232
2101
  appIdEnv,
2233
2102
  privateKeyEnv,
2234
2103
  installationIdEnv,
2235
- permissions: configuredBranchWritePermissions(appPermissions),
2104
+ ...appPermissions ? {
2105
+ permissions: {
2106
+ ...appPermissions,
2107
+ metadata: "read"
2108
+ }
2109
+ } : {},
2236
2110
  repositories: [repository.name]
2237
2111
  });
2238
2112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-github",
3
- "version": "0.98.0",
3
+ "version": "0.99.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -25,7 +25,7 @@
25
25
  "dependencies": {
26
26
  "@sinclair/typebox": "^0.34.49",
27
27
  "zod": "^4.4.3",
28
- "@sentry/junior-plugin-api": "0.98.0"
28
+ "@sentry/junior-plugin-api": "0.99.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^25.9.1",