@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.
- package/SETUP.md +3 -3
- package/dist/index.js +31 -157
- 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`.
|
|
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-
|
|
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
|
-
|
|
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
|
|
852
|
-
"GitHub
|
|
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
|
|
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 "
|
|
1778
|
+
return "installation-write";
|
|
1797
1779
|
}
|
|
1798
1780
|
if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/issues$/.test(pathname)) {
|
|
1799
|
-
return "
|
|
1781
|
+
return "installation-write";
|
|
1800
1782
|
}
|
|
1801
1783
|
if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/issues\/[^/]+\/comments$/.test(pathname)) {
|
|
1802
|
-
return "
|
|
1784
|
+
return "installation-write";
|
|
1803
1785
|
}
|
|
1804
1786
|
if (method === "PATCH" && /^\/repos\/[^/]+\/[^/]+\/issues\/[^/]+$/.test(pathname)) {
|
|
1805
|
-
return "
|
|
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 "
|
|
1792
|
+
return "installation-write";
|
|
1811
1793
|
}
|
|
1812
1794
|
if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/pulls$/.test(pathname)) {
|
|
1813
|
-
return "
|
|
1795
|
+
return "installation-write";
|
|
1814
1796
|
}
|
|
1815
1797
|
if (method === "PATCH" && /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+$/.test(pathname)) {
|
|
1816
|
-
return "
|
|
1798
|
+
return "installation-write";
|
|
1817
1799
|
}
|
|
1818
1800
|
if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+\/ready_for_review$/.test(pathname)) {
|
|
1819
|
-
return "
|
|
1801
|
+
return "installation-write";
|
|
1820
1802
|
}
|
|
1821
1803
|
if ((method === "POST" || method === "DELETE") && /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+\/requested_reviewers$/.test(pathname)) {
|
|
1822
|
-
return "
|
|
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 "
|
|
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
|
-
|
|
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.
|
|
2000
|
-
"installation-
|
|
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
|
|
2015
|
-
if (
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
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-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
28
|
+
"@sentry/junior-plugin-api": "0.99.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^25.9.1",
|