@sentry/junior-github 0.157.0 → 0.159.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/dist/index.js CHANGED
@@ -20,7 +20,8 @@ import {
20
20
  // src/plugin.ts
21
21
  import {
22
22
  defineJuniorPlugin,
23
- EgressPolicyDenied as EgressPolicyDenied2
23
+ EgressPolicyDenied as EgressPolicyDenied2,
24
+ enforceEgressPolicy
24
25
  } from "@sentry/junior-plugin-api";
25
26
 
26
27
  // src/permissions.ts
@@ -4638,6 +4639,36 @@ function shouldInspectGitHubGraphqlResponse(ctx) {
4638
4639
  const contentType = ctx.response.headers.get("content-type");
4639
4640
  return contentType ? /\bjson\b/i.test(contentType) : false;
4640
4641
  }
4642
+ var GITHUB_BODY_WRITES = [
4643
+ {
4644
+ denialMessage: `GitHub issue creation must use the github_createIssue tool so Junior can own idempotency and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
4645
+ graphqlField: "createIssue",
4646
+ method: "POST",
4647
+ operation: "github.issue.create",
4648
+ restPath: /^\/repos\/[^/]+\/[^/]+\/issues$/
4649
+ },
4650
+ {
4651
+ denialMessage: `GitHub pull request creation must use the github_createPullRequest tool so Junior can own idempotency and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
4652
+ graphqlField: "createPullRequest",
4653
+ method: "POST",
4654
+ operation: "github.pull.create",
4655
+ restPath: /^\/repos\/[^/]+\/[^/]+\/pulls$/
4656
+ },
4657
+ {
4658
+ denialMessage: `GitHub issue updates must use the github_updateIssue tool so Junior can own requester attribution and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
4659
+ graphqlField: "updateIssue",
4660
+ method: "PATCH",
4661
+ operation: "github.issue.update",
4662
+ restPath: /^\/repos\/[^/]+\/[^/]+\/issues\/[^/]+$/
4663
+ },
4664
+ {
4665
+ denialMessage: `GitHub pull request updates must use the github_updatePullRequest tool so Junior can own requester attribution and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
4666
+ graphqlField: "updatePullRequest",
4667
+ method: "PATCH",
4668
+ operation: "github.pull.update",
4669
+ restPath: /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+$/
4670
+ }
4671
+ ];
4641
4672
  function githubApiWriteGrantName(method, upstreamUrl) {
4642
4673
  const pathname = upstreamUrl.pathname.toLowerCase();
4643
4674
  if (!isGitHubApiUrl2(upstreamUrl)) {
@@ -4653,26 +4684,19 @@ function githubApiWriteGrantName(method, upstreamUrl) {
4653
4684
  ) || /^\/repos\/[^/]+\/[^/]+\/actions\/jobs\/[^/]+\/rerun$/.test(pathname))) {
4654
4685
  return "installation-write";
4655
4686
  }
4656
- if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/issues$/.test(pathname)) {
4687
+ if (GITHUB_BODY_WRITES.some(
4688
+ (write) => write.method === method && write.restPath.test(pathname)
4689
+ )) {
4657
4690
  return "installation-write";
4658
4691
  }
4659
4692
  if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/issues\/[^/]+\/comments$/.test(pathname)) {
4660
4693
  return "installation-write";
4661
4694
  }
4662
- if (method === "PATCH" && /^\/repos\/[^/]+\/[^/]+\/issues\/[^/]+$/.test(pathname)) {
4663
- return "installation-write";
4664
- }
4665
4695
  if ((method === "POST" || method === "DELETE") && /^\/repos\/[^/]+\/[^/]+\/issues\/[^/]+\/(labels|assignees)(?:\/[^/]+)?$/.test(
4666
4696
  pathname
4667
4697
  )) {
4668
4698
  return "installation-write";
4669
4699
  }
4670
- if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/pulls$/.test(pathname)) {
4671
- return "installation-write";
4672
- }
4673
- if (method === "PATCH" && /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+$/.test(pathname)) {
4674
- return "installation-write";
4675
- }
4676
4700
  if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+\/ready_for_review$/.test(pathname)) {
4677
4701
  return "installation-write";
4678
4702
  }
@@ -4694,17 +4718,6 @@ function githubApiWriteGrantName(method, upstreamUrl) {
4694
4718
  }
4695
4719
  return void 0;
4696
4720
  }
4697
- function isGitHubIssueCreateRestRequest(method, upstreamUrl) {
4698
- return method === "POST" && isGitHubApiUrl2(upstreamUrl) && /^\/repos\/[^/]+\/[^/]+\/issues$/.test(upstreamUrl.pathname.toLowerCase());
4699
- }
4700
- function isGitHubPullCreateRestRequest(method, upstreamUrl) {
4701
- return method === "POST" && isGitHubApiUrl2(upstreamUrl) && /^\/repos\/[^/]+\/[^/]+\/pulls$/.test(upstreamUrl.pathname.toLowerCase());
4702
- }
4703
- function isGitHubResourceUpdateRestRequest(method, upstreamUrl, resource) {
4704
- return method === "PATCH" && isGitHubApiUrl2(upstreamUrl) && new RegExp(`^/repos/[^/]+/[^/]+/${resource}/[^/]+$`).test(
4705
- upstreamUrl.pathname.toLowerCase()
4706
- );
4707
- }
4708
4721
  function isGitHubGraphqlMutation(method, upstreamUrl, bodyText, field) {
4709
4722
  if (method !== "POST" || !isGitHubGraphqlUrl(upstreamUrl)) return false;
4710
4723
  const parsed = parseGitHubGraphqlRequest(bodyText);
@@ -4716,64 +4729,26 @@ function isGitHubGraphqlMutation(method, upstreamUrl, bodyText, field) {
4716
4729
  `\\bmutation\\s+${escapeRegExp2(parsed.operationName)}\\b`
4717
4730
  ).test(parsed.normalized);
4718
4731
  }
4719
- function assertGitHubWriteAllowed(input) {
4732
+ function applyGitHubEgressPolicy(input) {
4720
4733
  assertGitHubPullRequestApprovalDenied({
4721
4734
  ...input.bodyText !== void 0 ? { bodyText: input.bodyText } : {},
4722
4735
  method: input.method,
4723
4736
  upstreamUrl: input.upstreamUrl
4724
4737
  });
4725
- if (input.operation === "github.issue.create") return;
4726
- if (input.operation === "github.issue.update") return;
4727
- if (input.operation === "github.pull.create") return;
4728
- if (input.operation === "github.pull.update") return;
4729
- if (isGitHubIssueCreateRestRequest(input.method, input.upstreamUrl) || isGitHubGraphqlMutation(
4730
- input.method,
4731
- input.upstreamUrl,
4732
- input.bodyText,
4733
- "createIssue"
4734
- )) {
4735
- throw new EgressPolicyDenied2(
4736
- `GitHub issue creation must use the github_createIssue tool so Junior can own idempotency and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`
4737
- );
4738
- }
4739
- if (isGitHubPullCreateRestRequest(input.method, input.upstreamUrl) || isGitHubGraphqlMutation(
4740
- input.method,
4741
- input.upstreamUrl,
4742
- input.bodyText,
4743
- "createPullRequest"
4744
- )) {
4745
- throw new EgressPolicyDenied2(
4746
- `GitHub pull request creation must use the github_createPullRequest tool so Junior can own idempotency and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`
4747
- );
4748
- }
4749
- if (isGitHubResourceUpdateRestRequest(
4750
- input.method,
4751
- input.upstreamUrl,
4752
- "issues"
4753
- ) || isGitHubGraphqlMutation(
4754
- input.method,
4755
- input.upstreamUrl,
4756
- input.bodyText,
4757
- "updateIssue"
4758
- )) {
4759
- throw new EgressPolicyDenied2(
4760
- `GitHub issue updates must use the github_updateIssue tool so Junior can own requester attribution and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`
4761
- );
4762
- }
4763
- if (isGitHubResourceUpdateRestRequest(
4764
- input.method,
4765
- input.upstreamUrl,
4766
- "pulls"
4767
- ) || isGitHubGraphqlMutation(
4768
- input.method,
4769
- input.upstreamUrl,
4770
- input.bodyText,
4771
- "updatePullRequest"
4772
- )) {
4773
- throw new EgressPolicyDenied2(
4774
- `GitHub pull request updates must use the github_updatePullRequest tool so Junior can own requester attribution and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`
4775
- );
4776
- }
4738
+ const pathname = input.upstreamUrl.pathname.toLowerCase();
4739
+ const write = GITHUB_BODY_WRITES.find(
4740
+ (candidate) => candidate.method === input.method && isGitHubApiUrl2(input.upstreamUrl) && candidate.restPath.test(pathname) || isGitHubGraphqlMutation(
4741
+ input.method,
4742
+ input.upstreamUrl,
4743
+ input.bodyText,
4744
+ candidate.graphqlField
4745
+ )
4746
+ );
4747
+ if (!write) return;
4748
+ enforceEgressPolicy({
4749
+ allowed: input.operation === write.operation,
4750
+ denialMessage: write.denialMessage
4751
+ });
4777
4752
  }
4778
4753
  function grantForAccess(access, reason, name, leaseScope) {
4779
4754
  return {
@@ -4796,7 +4771,7 @@ function repositoryLeaseScope(upstreamUrl) {
4796
4771
  async function githubGrantForEgress(ctx) {
4797
4772
  const method = ctx.request.method.toUpperCase();
4798
4773
  const upstreamUrl = new URL(ctx.request.url);
4799
- assertGitHubWriteAllowed({
4774
+ applyGitHubEgressPolicy({
4800
4775
  ...ctx.request.bodyText !== void 0 ? { bodyText: ctx.request.bodyText } : {},
4801
4776
  method,
4802
4777
  ...ctx.request.operation ? { operation: ctx.request.operation } : {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-github",
3
- "version": "0.157.0",
3
+ "version": "0.159.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -31,7 +31,7 @@
31
31
  "@sinclair/typebox": "^0.34.49",
32
32
  "drizzle-orm": "^0.45.2",
33
33
  "zod": "^4.4.3",
34
- "@sentry/junior-plugin-api": "0.157.0"
34
+ "@sentry/junior-plugin-api": "0.159.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^25.9.1",
@@ -81,3 +81,5 @@ If PR creation or update is blocked, report the exact failed command/tool call a
81
81
  When PR creation returns a subscribable resource hint, subscribe to suggested review/CI events. Report only actionable feedback addressed, build failures fixed, fully green/ready state, or merge.
82
82
 
83
83
  Return: repo, branch, PR URL/number, checks and results, pre-existing failures, and anything not run with the reason.
84
+
85
+ When you mention a pull request in a user-facing reply, always include a direct link. Prefer the full PR URL or a Markdown link such as `[#123](https://github.com/owner/repo/pull/123)`. `owner/repo#number` is also fine. Do not leave bare `PR #123` text without a URL or repo.