Skip to content

feat: Allow extra permissions on tools.github.github-app token for org-level API access#23600

Merged
pelikhan merged 6 commits intomainfrom
copilot/fix-fetch-members-org-app-token
Mar 31, 2026
Merged

feat: Allow extra permissions on tools.github.github-app token for org-level API access#23600
pelikhan merged 6 commits intomainfrom
copilot/fix-fetch-members-org-app-token

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 30, 2026

The compiler only forwarded job-level permissions: to the minted GitHub App token, with no mechanism to add org-level scopes (e.g. members: read) that are invalid as GitHub Actions permissions but valid for GitHub App tokens. Org-scoped API calls like GET /orgs/{org}/teams/{slug}/members returned null silently even with a correctly-configured app.

Changes

  • GitHubAppConfig: Added Permissions map[string]string field
  • parseAppConfig: Parses permissions map from frontmatter; logs debug warnings for non-string values and non-map permissions entries instead of silently dropping them
  • generateGitHubMCPAppTokenMintingStep: Applies app.Permissions on top of job-level permissions after conversion — nested entries win for overlapping scopes; levels are normalized (trimmed + lowercased); emits user-visible warnings for unrecognized scope names or invalid levels
  • main_workflow_schema.json: Added permissions property to github_app definition with all 30 GitHub App-only permission scopes enumerated explicitly (additionalProperties: false). Both permission map definitions are extracted into named $defs (github_actions_permissions, github_app_permissions) and referenced via $ref. "write" is included in the enum so editors offer it as a completion, but is rejected at compile time with an informative error. The description clearly states that permissions only takes effect for tools.github.github-app.
  • validateGitHubMCPAppPermissionsNoWrite: Compiler validation that rejects all invalid permission levels (not just the literal "write") after normalization, with a structured error listing write-specific scopes and other invalid scopes separately.
  • warnGitHubAppPermissionsUnsupportedContexts: Emits a compile-time warning when permissions is set on safe-outputs.github-app, on.github-app, or the top-level github-app fallback, where it has no effect.
  • Tests: Three cases — extra org permissions are merged into the minted token, nested "none" overrides a job-level GitHub App-only scope, and any invalid level (including "write") in tools.github.github-app.permissions is rejected with an informative error.

Usage

tools:
  github:
    toolsets: [orgs, users]
    github-app:
      app-id: ${{ vars.APP_ID }}
      private-key: ${{ secrets.APP_PRIVATE_KEY }}
      repositories: ["*"]
      permissions:
        members: read
        organization-administration: read

The compiled token minting step will now include permission-members: read alongside the standard job-level permission-* fields.

Specifying an invalid level (e.g. "write") for any scope under tools.github.github-app.permissions produces a compiler error:

Invalid permission levels in tools.github.github-app.permissions.
Each permission level must be exactly "read" or "none".

"write" is not allowed: write operations must be performed via safe-outputs.

The following scopes were declared with "write" access:

  - members

The following scopes have invalid permission levels:

  - members (level: write)

Change the permission level to "read" for read-only access, or "none" to disable the scope.

…issions

Allow users to specify org-level permissions (e.g., members: read) under
tools.github.github-app.permissions. These are merged into the minted
GitHub App installation access token with nested permissions winning over
job-level permissions for overlapping scopes.

Fixes: Can not fetch members from org when using an app token

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/8134923b-eeed-4351-bd5e-531c7dc8f3ce

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix fetch members from org when using an app token feat: Allow extra permissions on tools.github.github-app token for org-level API access Mar 30, 2026
Copilot AI requested a review from pelikhan March 30, 2026 23:17
"description": "Optional extra permissions to merge into the minted token. These override the job-level permissions for the token (nested wins). Use this to add org-level permissions (e.g., members: read) that are not valid GitHub Actions scopes but are supported by GitHub Apps.",
"additionalProperties": {
"type": "string",
"enum": ["read", "write", "none"]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot write not allowed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a879f39write is removed from the allowed values for github_app.permissions. Only "read" and "none" are now accepted.

"permissions": {
"type": "object",
"description": "Optional extra permissions to merge into the minted token. These override the job-level permissions for the token (nested wins). Use this to add org-level permissions (e.g., members: read) that are not valid GitHub Actions scopes but are supported by GitHub Apps.",
"additionalProperties": {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot reuse list of permissions already present in the JSON format

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a879f39 — replaced additionalProperties with an explicit properties map enumerating all 30 GitHub App-only permission scopes (matching the pattern already used in the main permissions object). additionalProperties: false prevents unknown scopes.

… read/none only

- Enumerate all GitHub App-only scopes explicitly instead of using additionalProperties
- Only allow 'read' and 'none' values (not 'write')
- Update override test to use a valid GitHub App-only scope (vulnerability-alerts)"

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/3e32b3bc-7c8b-4f55-a88f-3d940a2ae8e6

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI requested a review from pelikhan March 30, 2026 23:30
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot refactor permissions type in JSON schema and reuse where appropriate

- Add $defs.github_actions_permissions (the full GitHub Actions permissions object)
- Add $defs.github_app_permissions (the GitHub App-only permissions object)
- Replace inline definitions with $ref references at both usage points

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/8f5e79a1-f47d-4d4a-bfd6-0e5f8edd76cc

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 30, 2026

@copilot refactor permissions type in JSON schema and reuse where appropriate

Done in a6d3df9. Both permissions map definitions are now extracted into named $defs entries:

  • $defs.github_actions_permissions — the full GitHub Actions permissions object (previously inline in permissions.oneOf[1])
  • $defs.github_app_permissions — the GitHub App-only permissions object (previously inline in github_app.permissions)

Both usage points now use $ref to reference those definitions.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
  • https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1
    • Triggering command: /usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha heckout/git/ref/tags/v5 /tmp/go-build2527936695/b242/vet.cfg /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/link (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v3
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha g_.a (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v5
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha /tmp/TestHashConsistency_GoAndJavaScript1136913559/001/test-frontmatter-with-nested-objects.md -tests /usr/bin/git (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel x_amd64/vet /usr/bin/git *.json&#39; &#39;!../../git --local 64/pkg/tool/linu--show-toplevel git rev-�� --show-toplevel 64/pkg/tool/linux_amd64/vet 64/bin/node --local .cfg 64/pkg/tool/linu--show-toplevel infocmp (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha nt/action/git/ref/tags/v999.999.999 /tmp/go-build2527936695/b044/vet.cfg 7936695/b346/vet.cfg (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha .prop.prop.prop.prop.prop.prop.prop.prop.prop.prop.prop.prop.prop.prop.prop.prop.prop.prop.prop.git x_amd64/vet /usr/bin/git .js&#39; --ignore-pagit --local x_amd64/vet git rev-�� --show-toplevel x_amd64/vet /opt/hostedtoolcache/node/24.14.0/x64/bin/node get son 64/pkg/tool/linu--show-toplevel /opt/hostedtoolcache/node/24.14.0/x64/bin/node (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v8
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha sistency_GoAndJavaScript1136913559/001/test-empty-frontmatter.md /tmp/go-build2527936695/b223/vet.cfg 7936695/b305/vet.cfg (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha -unreachable=false /tmp/go-build2527936695/b049/vet.cfg 7936695/b354/vet.cfg (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha ithub/workflows/artifacts-summary.md /tmp/go-build2527936695/b056/vet.cfg 0/x64/bin/node (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha -unreachable=false /tmp/go-build2527936695/b036/vet.cfg 7936695/b339/vet.cfg (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha sistency_GoAndJavaScript1136913559/001/test-complex-frontmatter-with-tools.md /tmp/go-build2527936695/b050/vet.cfg 0/x64/bin/node (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v4 --jq .object.sha rite &#39;scripts/**/*.js&#39; --ignore-path .prettierig-errorsas .cfg 64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v0.1.2
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v0.1.2 --jq .object.sha -bool -buildtags 7936695/b325/vet.cfg -errorsas -ifaceassert -nilfunc /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -uns�� -unreachable=false /tmp/go-build2527936695/b198/vet.cfg /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet h ../../../.pretgit (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.0.0 --jq .object.sha te &#39;scripts/**/*.js&#39; --ignore-path .prettierigno-errorsas .cfg 64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.2.3
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.2.3 --jq .object.sha te &#39;scripts/**/*.js&#39; --ignore-path .prettierignore --log-level=e!../../../pkg/workflow/js/**/*.jgit .cfg 64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v0.47.4
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v0.47.4 --jq .object.sha --show-toplevel (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha (http block)
  • https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999
    • Triggering command: /usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha (http block)

If you need me to access, download, or install something from one of these locations, you can either:

},
"codespaces": {
"type": "string",
"enum": ["read", "none"],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot add the "write" entry but have a compiler checks that refuses it as an informative error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 3335321. "write" is now present in the enum for all scopes in $defs.github_app_permissions (so editors offer it as a completion and schema validation passes), and a new compiler check validateGitHubMCPAppPermissionsNoWrite rejects it with an informative error listing the offending scopes. Example error:

"write" is not allowed in tools.github.github-app.permissions.
GitHub App-only scopes in this section must be declared as "read" or "none".
Write operations must be performed via safe-outputs, not through declared permissions.

The following scopes were declared with "write" access:

  - members

Change the permission level to "read" for read-only access, or remove the entry.

… compile time

- Add "write" to all enum values in $defs.github_app_permissions so editors
  can offer it as a completion and users get a clear compiler error instead of
  a silent schema validation pass-through
- Add validateGitHubMCPAppPermissionsNoWrite() to github_app_permissions_validation.go
  with an informative error listing the offending scopes
- Wire the new validator into compiler.go alongside the existing GitHub App
  permission checks
- Add TestGitHubMCPAppTokenExtraPermissionsWriteRejected to cover the error path

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/17aa2920-f546-4f22-b7aa-992c63901959

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 31, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw --local x_amd64/vet git rev-�� --show-toplevel x_amd64/vet /usr/bin/git &#39;**/*.ts&#39; &#39;**/*.git pull.rebase x_amd64/vet git (http block)
  • https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1
    • Triggering command: /usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha -unreachable=false /tmp/go-build2787008080/b264/vet.cfg /tmp/go-build2787008080/b001/workflow.test (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v3
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha g_.a (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v5
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha get --local de/node/bin/bash credential.usern/opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/compile (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel -tests ache/node/24.14.0/x64/bin/node (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel x_amd64/vet /usr/bin/git *.json&#39; &#39;!../../git (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha -unreachable=false /tmp/go-build2787008080/b004/vet.cfg 7008080/b343/vet.cfg (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha -unreachable=false /tmp/go-build2787008080/b056/vet.cfg /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha -aw-actions/git/ref/tags/v1.0.0 x_amd64/vet /usr/bin/git .js&#39; --ignore-pagit copilot/fix-fetcrev-parse x_amd64/vet git rev-�� --show-toplevel x_amd64/vet /usr/bin/git --noprofile (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v8
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha -bool -buildtags 0/x64/bin/node -errorsas -ifaceassert -nilfunc 0/x64/bin/node -uns�� -unreachable=false /tmp/go-build2787008080/b196/vet.cfg /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet h ../../../.pretgit (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha -unreachable=false /tmp/go-build2787008080/b042/vet.cfg 7008080/b354/vet.cfg (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha -unreachable=false /tmp/go-build2787008080/b049/vet.cfg 7008080/b344/vet.cfg (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha sistency_GoAndJavaScript969607709/001/test-frontmatter-with-env-template-expressions.md /tmp/go-build2787008080/b037/vet.cfg ache/node/24.14.0/x64/bin/node (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v4 --jq .object.sha (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v0.1.2
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v0.1.2 --jq .object.sha -bool -buildtags 7008080/b340/vet.cfg -errorsas -ifaceassert -nilfunc /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -uns�� -unreachable=false /tmp/go-build2787008080/b200/vet.cfg /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet h ../../../.pretgit (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.0.0 --jq .object.sha te &#39;scripts/**/*.js&#39; --ignore-path .prettierignore --log-level=e!../../../pkg/workflow/js/**/*.jgit .cfg 64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.2.3
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.2.3 --jq .object.sha 7231/001/stability-test.md .cfg 64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v0.47.4
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v0.47.4 --jq .object.sha --show-toplevel (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha (http block)
  • https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999
    • Triggering command: /usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from pelikhan March 31, 2026 00:04
@pelikhan pelikhan marked this pull request as ready for review March 31, 2026 00:04
Copilot AI review requested due to automatic review settings March 31, 2026 00:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Enables tools.github.github-app to request additional GitHub App-only permission scopes (not representable in GitHub Actions permissions:) so org-level API calls can succeed when using the minted GitHub App token.

Changes:

  • Added permissions map support to GitHub App config parsing and schema validation.
  • Merged tools.github.github-app.permissions into the permissions passed to actions/create-github-app-token, with warnings for unknown scopes.
  • Added compiler validation + tests to reject "write" values in tools.github.github-app.permissions.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pkg/workflow/safe_outputs_app_config.go Extends GitHubAppConfig + parsing to include an optional permissions map.
pkg/workflow/compiler_github_mcp_steps.go Merges extra github-app.permissions into the permissions forwarded to the GitHub MCP App token minting step.
pkg/workflow/github_app_permissions_validation.go Adds compiler validation to reject "write" in tools.github.github-app.permissions.
pkg/workflow/compiler.go Wires the new validation into the compiler validation pipeline.
pkg/workflow/github_mcp_app_token_test.go Adds tests for merging extra permissions, overrides, and write rejection.
pkg/parser/schemas/main_workflow_schema.json Adds permissions to the github_app schema and introduces $defs for GitHub Actions vs GitHub App permission maps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +75 to +77
appConfig.Permissions[key] = valStr
}
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseAppConfig silently drops non-string entries under github-app.permissions (and also ignores a non-object permissions value) without any warning or error. This can lead to confusing behavior where a user configures permissions but they are simply not applied. Consider logging a warning (including the key/type) or returning a validation error when permissions is present but not a map[string]string.

Suggested change
appConfig.Permissions[key] = valStr
}
}
appConfig.Permissions[key] = valStr
} else {
safeOutputsAppLog.Printf("Ignoring github-app.permissions[%q]: expected string value, got %T", key, val)
}
}
} else {
safeOutputsAppLog.Printf("Ignoring github-app.permissions: expected map[string]string-compatible object, got %T", perms)

Copilot uses AI. Check for mistakes.
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(msg))
continue
}
permissions.Set(scope, PermissionLevel(val))
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra permissions from github-app.permissions are applied with PermissionLevel(val) without validating that val is an allowed level (expected "read" or "none"; "write" is rejected elsewhere). If schema validation is bypassed (e.g. non-workflow files or imported JSON), this can generate invalid permission-* inputs and fail at runtime. Consider validating/normalizing the level here (trim/lowercase) and failing compilation for unknown values.

Suggested change
permissions.Set(scope, PermissionLevel(val))
level := strings.ToLower(strings.TrimSpace(val))
if level != "read" && level != "none" {
msg := fmt.Sprintf("Unknown permission level %q for scope %q in tools.github.github-app.permissions. Valid levels are: read, none.", val, key)
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(msg))
continue
}
permissions.Set(scope, PermissionLevel(level))

Copilot uses AI. Check for mistakes.
Comment on lines +128 to +150
var writeScopes []string
for scope, level := range app.Permissions {
if level == string(PermissionWrite) {
writeScopes = append(writeScopes, scope)
}
}
if len(writeScopes) == 0 {
return nil
}
sort.Strings(writeScopes)

var lines []string
lines = append(lines, `"write" is not allowed in tools.github.github-app.permissions.`)
lines = append(lines, "GitHub App-only scopes in this section must be declared as \"read\" or \"none\".")
lines = append(lines, "Write operations must be performed via safe-outputs, not through declared permissions.")
lines = append(lines, "")
lines = append(lines, "The following scopes were declared with \"write\" access:")
lines = append(lines, "")
for _, s := range writeScopes {
lines = append(lines, " - "+s)
}
lines = append(lines, "")
lines = append(lines, "Change the permission level to \"read\" for read-only access, or remove the entry.")
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validateGitHubMCPAppPermissionsNoWrite only checks for the literal string "write". If the permissions map contains other invalid values (e.g. "Read", "none ", or any unexpected string) they will pass validation and later be emitted into permission-* inputs. Consider normalizing values (trim/lowercase) and validating that each level is exactly "read" or "none" (with a dedicated error message) rather than only rejecting "write".

Suggested change
var writeScopes []string
for scope, level := range app.Permissions {
if level == string(PermissionWrite) {
writeScopes = append(writeScopes, scope)
}
}
if len(writeScopes) == 0 {
return nil
}
sort.Strings(writeScopes)
var lines []string
lines = append(lines, `"write" is not allowed in tools.github.github-app.permissions.`)
lines = append(lines, "GitHub App-only scopes in this section must be declared as \"read\" or \"none\".")
lines = append(lines, "Write operations must be performed via safe-outputs, not through declared permissions.")
lines = append(lines, "")
lines = append(lines, "The following scopes were declared with \"write\" access:")
lines = append(lines, "")
for _, s := range writeScopes {
lines = append(lines, " - "+s)
}
lines = append(lines, "")
lines = append(lines, "Change the permission level to \"read\" for read-only access, or remove the entry.")
var (
invalidScopes []string
writeScopes []string
)
for scope, level := range app.Permissions {
normalized := strings.ToLower(strings.TrimSpace(level))
switch normalized {
case "read", "none":
// valid levels
continue
default:
// Track all invalid scopes
invalidScopes = append(invalidScopes, scope+" (level: "+level+")")
// Track scopes that explicitly requested write access (in any casing/whitespace)
if normalized == strings.ToLower(string(PermissionWrite)) {
writeScopes = append(writeScopes, scope)
}
}
}
if len(invalidScopes) == 0 {
return nil
}
sort.Strings(invalidScopes)
sort.Strings(writeScopes)
var lines []string
lines = append(lines, "Invalid permission levels in tools.github.github-app.permissions.")
lines = append(lines, "Each GitHub App permission level must be exactly \"read\" or \"none\" (case-sensitive, no extra whitespace).")
if len(writeScopes) > 0 {
lines = append(lines, "")
lines = append(lines, `"write" is not allowed in tools.github.github-app.permissions.`)
lines = append(lines, "Write operations must be performed via safe-outputs, not through declared permissions.")
lines = append(lines, "")
lines = append(lines, "The following scopes were declared with \"write\" access:")
for _, s := range writeScopes {
lines = append(lines, " - "+s)
}
}
lines = append(lines, "")
lines = append(lines, "The following scopes were declared with invalid permission levels:")
for _, s := range invalidScopes {
lines = append(lines, " - "+s)
}
lines = append(lines, "")
lines = append(lines, "Change the permission level to \"read\" for read-only access, or set it to \"none\" / remove the entry.")

Copilot uses AI. Check for mistakes.
Comment on lines +9581 to +9583
},
"permissions": {
"$ref": "#/$defs/github_app_permissions"
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema adds a permissions property to the shared $defs.github_app object, which is referenced by multiple features (e.g. safe-outputs.github-app, checkout.github-app, qmd search github-app). The implementation in this PR only merges github-app.permissions into the GitHub MCP token minting step, so users may think permissions applies in those other contexts when it does not. Consider scoping this schema change to tools.github.github-app only, or emitting a compile-time warning/error when permissions is provided in unsupported github-app contexts.

Suggested change
},
"permissions": {
"$ref": "#/$defs/github_app_permissions"

Copilot uses AI. Check for mistakes.
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot review comments

- parseAppConfig: log warnings for non-string permission values and non-map
  permissions entries instead of silently dropping them
- compiler_github_mcp_steps: normalize permission level (trim+lowercase) and
  emit a warning for any level that isn't "read" or "none" before skipping it
- validateGitHubMCPAppPermissionsNoWrite: broaden validation to reject all
  invalid levels (not just "write") after normalization; list both write-specific
  and generic invalid scopes with separate explanations in the error message
- warnGitHubAppPermissionsUnsupportedContexts: new function that emits a
  compile-time warning when permissions is set in safe-outputs.github-app,
  on.github-app, or the top-level github-app fallback (where it has no effect);
  called from compiler.go after the no-write check
- schema: keep permissions in $defs.github_app (allOf+additionalProperties:false
  conflict prevents scoping via allOf) but update its description to clarify
  it only takes effect for tools.github.github-app; scoping enforced at runtime
  via the new warning function
- test: update write-rejection assertion strings to match the new error format

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/293c96f3-a7d6-461b-88c3-8ded806c935c

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 31, 2026

@copilot review comments

Addressed all four review comments in 77fbde1:

  1. parseAppConfig silent drops — now logs debug warnings for non-string permission values and non-map permissions entries.

  2. Permission level validation in the MCP step — levels are now normalized (trim+lowercase) before use; unknown levels (anything that isn't read or none) emit a user-visible warning and are skipped rather than being forwarded as-is.

  3. validateGitHubMCPAppPermissionsNoWrite coverage — broadened to reject all invalid levels (not just the literal string "write") after normalization; the error now lists write-specific scopes and other invalid scopes in separate sections.

  4. Schema permissions scope — scoping via allOf is blocked by additionalProperties: false on $defs.github_app, so I kept permissions in the shared def but (a) updated its description to clearly state it only takes effect for tools.github.github-app, and (b) added warnGitHubAppPermissionsUnsupportedContexts which emits a compile-time warning if permissions is set on safe-outputs.github-app, on.github-app, or the top-level github-app fallback where it has no effect.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw .cfg 64/pkg/tool/linu--show-toplevel git rev-�� --show-toplevel 64/pkg/tool/linu-tests /usr/bin/git tions-lock.json git (http block)
  • https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1
    • Triggering command: /usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha bility_SameInputSameOutput3539080643/001/stability-test.md --write /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/compile !../../../pkg/wogit --ignore-path ../../../.pretti--show-toplevel /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/compile -o /tmp/go-build3036163859/b001/_pkg_.a -trimpath /usr/bin/git -p main -lang=go1.25 git (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v3
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha g_.a -buildtags k/gh-aw/gh-aw/actions/setup/js/node_modules/.bin/node -errorsas flow_test -nilfunc git k/gh�� CXcqx38s4 -tests bin/node (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v5
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha re --log-level=e!../../../pkg/workflow/js/**/*.json .cfg 64/pkg/tool/linu../../../.prettierignore (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel -goversion /opt/hostedtoolcache/node/24.14.0/x64/bin/node -c=4 -nolocalimports -importcfg node /tmp�� /tmp/TestHashStability_SameInputSameOutput3539080643/001/stability-test.md /home/REDACTED/work/gh-aw/gh-aw/pkg/cli/actionlint.go /usr/bin/git rite &#39;**/*.cjs&#39; git .cfg 64/pkg/tool/linu--show-toplevel git (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet /usr/bin/git ../pkg/workflow/git 1319197/b214/vetrev-parse ache/go/1.25.0/x--show-toplevel git rev-�� --show-toplevel ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet n-dir/node (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha sistency_GoAndJavaScript4195213183/001/test-simple-frontmatter.md --write /opt/hostedtoolcache/uv/0.11.2/x86_64/node **/*.ts **/*.json --ignore-path node /opt�� run format:pkg-json /bin/sh tierignore h-members-org-aprev-parse run-script/lib/n--show-toplevel /bin/sh (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha &#34;prettier&#34; --write &#39;**/*.cjs&#39; &#39;**/*.ts&#39; &#39;**/*.json&#39; --ignore-path ../../../.prettierignore git /home/REDACTED/work/gh-aw/node_modules/.bin/sh ../pkg/workflow/git --global x_amd64/vet sh -c npx prettier --write &#39;../../../**/*.json&#39; &#39;!../../../pkg/workflow/js/**/*.json&#39; --ignore-path x_amd64/vet /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/compile h-members-org-apgit --local x_amd64/vet /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/compile (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha -aw-actions/git/ref/tags/v1.2.3 ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet /usr/bin/git th .prettierignogit 1319197/b079/vetrev-parse ode_modules/.bin--show-toplevel git rev-�� --show-toplevel ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet /usr/bin/git se 1319197/b232/vetrev-parse ache/go/1.25.0/x--show-toplevel git (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v8
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha npx prettier --write &#39;**/*.cjs&#39; &#39;**/*.ts&#39; &#39;**/*.json&#39; --ignore-path ../../../.pr**/*.json owner /home/node_modules/.bin/node 0 -j ACCEPT node /hom�� --write scripts/**/*.js /home/REDACTED/go/bin/node .prettierignore --log-level=errorev-parse x_amd64/vet node (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha prettier --write /home/REDACTED/work/gh-aw/gh-aw/actions/setup/node_modules/.bin/sh **/*.ts **/*.json --ignore-path sh -c npx prettier --write &#39;../../../**/*.json&#39; &#39;!../../../pkg/workflow/js/**/*.json&#39; --ignore-path x_amd64/vet /usr/bin/git ormat:cjs --silegit --local x_amd64/vet git (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha ithub/workflows/archie.md git /home/REDACTED/node_modules/.bin/sh ../pkg/workflow/git --global x_amd64/vet sh -c npx prettier --write &#39;../../../**/*.json&#39; &#39;!../../../pkg/workflow/js/**/*.json&#39; --ignore-path x_amd64/vet /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/cgo h-members-org-apgit --local x_amd64/vet /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/cgo (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha sistency_GoAndJavaScript4195213183/001/test-complex-frontmatter-with-tools.md --write ache/node/24.14.0/x64/bin/node **/*.ts **/*.json --ignore-path node t-10�� bility_SameInputSameOutput3539080643/001/stability-test.md format:pkg-json ache/node/24.14.0/x64/bin/node h-members-org-apgit --local x_amd64/vet git (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha sistency_GoAndJavaScript4195213183/001/test-simple-frontmatter.md --write /home/REDACTED/go/bin/node **/*.ts **/*.json --ignore-path node /opt�� run format:pkg-json /usr/bin/git tierignore atted/golang/pkgrev-parse x_amd64/vet git (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v4 --jq .object.sha .js&#39; --ignore-path .prettierignore --log-level=e!../../../pkg/workflow/js/**/*.json /tmp/go-build1851319197/b046/vet.cfg tnet/tools/bash (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v0.1.2
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v0.1.2 --jq .object.sha --write **/*.cjs ache/node/24.14.0/x64/bin/node **/*.json --ignore-path ../../../.pretti--show-toplevel node t-28�� sistency_GoAndJavaScript4195213183/001/test-inlined-imports-enabled-with-env-template-expressiongit scripts/**/*.js /bin/sh .prettierignore --log-level=errorev-parse x_amd64/vet /bin/sh (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.0.0 --jq .object.sha .js&#39; --ignore-path .prettierignore --log-level=error /tmp/go-build1851319197/b055/vet.cfg ules/.bin/node (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.2.3
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.2.3 --jq .object.sha .js&#39; --ignore-path .prettierignore --log-level=error /tmp/go-build1851319197/b079/vet.cfg tions/setup/js/node_modules/.bin/node (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v0.47.4
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v0.47.4 --jq .object.sha --show-toplevel 64/pkg/tool/linux_amd64/vet ache/node/24.14.0/x64/bin/node -template-expresgit .cfg (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha *.json&#39; &#39;!../../**/*.ts committer.name 64/pkg/tool/linu--ignore-path (http block)
  • https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999
    • Triggering command: /usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha *.json&#39; &#39;!../../**/*.ts committer.name 64/pkg/tool/linu--ignore-path (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@pelikhan pelikhan merged commit 19d082e into main Mar 31, 2026
85 checks passed
@pelikhan pelikhan deleted the copilot/fix-fetch-members-org-app-token branch March 31, 2026 00:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can not fetch members from org when using an app token

3 participants