🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

mirror-github.paniser.workers.dev/github/github-mcp-server

Package Overview
Dependencies
Versions
77
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mirror-github.paniser.workers.dev/github/github-mcp-server - go Package Compare versions

Comparing version
v1.2.1-0.20260610220934-918a42f05a34
to
v1.3.0
pkg/github/__toolsnaps__/get_file_blame.snap

Sorry, the diff of this file is not supported yet

+154
package github
import (
"context"
"github.com/github/github-mcp-server/pkg/ifc"
"github.com/google/go-github/v87/github"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// setIFCLabel writes the given IFC security label into a tool result's _meta
// under the "ifc" key, allocating the Meta map if necessary.
func setIFCLabel(r *mcp.CallToolResult, label ifc.SecurityLabel) {
if r.Meta == nil {
r.Meta = mcp.Meta{}
}
r.Meta["ifc"] = label
}
// attachStaticIFCLabel attaches a fixed IFC label to a successful tool result
// when IFC labels are enabled. It is used by tools whose label does not depend
// on any repository visibility lookup (e.g. security alerts, global
// advisories, team membership, notification subjects).
//
// Error results are left untouched, and the label is omitted entirely when the
// IFC feature flag is disabled.
func attachStaticIFCLabel(ctx context.Context, deps ToolDependencies, r *mcp.CallToolResult, label ifc.SecurityLabel) *mcp.CallToolResult {
if r == nil || r.IsError || !deps.IsFeatureEnabled(ctx, FeatureFlagIFCLabels) {
return r
}
setIFCLabel(r, label)
return r
}
// attachRepoVisibilityIFCLabel attaches an IFC label derived from a single
// repository's visibility to a successful tool result when IFC labels are
// enabled. The concrete label is produced by labelFn, which receives whether
// the repository is private.
//
// The repository visibility is resolved via FetchRepoIsPrivate. Consistent
// with the other IFC-labeled tools, if the visibility lookup fails the label
// is omitted rather than risking a misclassification. Error results and the
// disabled-feature case are left untouched.
func attachRepoVisibilityIFCLabel(
ctx context.Context,
deps ToolDependencies,
client *github.Client,
owner, repo string,
r *mcp.CallToolResult,
labelFn func(isPrivate bool) ifc.SecurityLabel,
) *mcp.CallToolResult {
if r == nil || r.IsError || !deps.IsFeatureEnabled(ctx, FeatureFlagIFCLabels) {
return r
}
isPrivate, err := FetchRepoIsPrivate(ctx, client, owner, repo)
if err != nil {
return r
}
setIFCLabel(r, labelFn(isPrivate))
return r
}
// ifcSearchPostProcessOption returns a searchOption that attaches IFC labels to
// a multi-repository search result. The feature-flag check is centralized here
// (mirroring the attach* helpers above) rather than in each search tool
// handler: when IFC labels are disabled it returns a no-op option, so callers
// can pass it unconditionally to searchHandler.
func ifcSearchPostProcessOption(ctx context.Context, deps ToolDependencies) searchOption {
if !deps.IsFeatureEnabled(ctx, FeatureFlagIFCLabels) {
return func(*searchConfig) {}
}
return withSearchPostProcess(searchIssuesIFCPostProcess(deps))
}
// attachRepoVisibilityIFCLabelLazy is like attachRepoVisibilityIFCLabel but
// resolves the REST client itself, only when IFC labels are enabled. It is used
// by tools whose handler holds a GraphQL client (or no client yet) and would
// otherwise have to acquire a REST client solely to compute the label. The
// feature-flag check is centralized here so callers can invoke it
// unconditionally; if the client cannot be obtained or the visibility lookup
// fails, the label is omitted rather than risking a misclassification.
func attachRepoVisibilityIFCLabelLazy(
ctx context.Context,
deps ToolDependencies,
owner, repo string,
r *mcp.CallToolResult,
labelFn func(isPrivate bool) ifc.SecurityLabel,
) *mcp.CallToolResult {
if r == nil || r.IsError || !deps.IsFeatureEnabled(ctx, FeatureFlagIFCLabels) {
return r
}
client, err := deps.GetClient(ctx)
if err != nil {
return r
}
return attachRepoVisibilityIFCLabel(ctx, deps, client, owner, repo, r, labelFn)
}
// attachJoinedIFCLabel attaches an IFC label computed by joining a set of
// per-item visibilities (true == private for repositories, true == public for
// gists) when IFC labels are enabled. joinFn is the lattice join for the
// relevant item kind (e.g. ifc.LabelSearchIssues or ifc.LabelGistList). The
// visibility slice is cheap to build from an already-fetched response, so
// callers may construct it unconditionally and let this helper own the
// feature-flag gate.
func attachJoinedIFCLabel(
ctx context.Context,
deps ToolDependencies,
r *mcp.CallToolResult,
visibilities []bool,
joinFn func([]bool) ifc.SecurityLabel,
) *mcp.CallToolResult {
if r == nil || r.IsError || !deps.IsFeatureEnabled(ctx, FeatureFlagIFCLabels) {
return r
}
setIFCLabel(r, joinFn(visibilities))
return r
}
// newRepoVisibilityIFCLabeler returns a closure that attaches a repo-visibility
// IFC label to a tool result, for handlers that have several return paths and
// want to label each one. The returned function owns the feature-flag gate (so
// callers invoke it unconditionally) and caches the repository visibility
// lookup across calls, so a handler that returns from many branches only pays
// for one FetchRepoIsPrivate call. A failed visibility lookup is not cached, so
// a later return path can retry; on persistent failure the label is omitted
// rather than risking a misclassification.
func newRepoVisibilityIFCLabeler(
ctx context.Context,
deps ToolDependencies,
client *github.Client,
owner, repo string,
labelFn func(isPrivate bool) ifc.SecurityLabel,
) func(*mcp.CallToolResult) *mcp.CallToolResult {
var (
known bool
isPrivate bool
)
return func(r *mcp.CallToolResult) *mcp.CallToolResult {
if r == nil || r.IsError || !deps.IsFeatureEnabled(ctx, FeatureFlagIFCLabels) {
return r
}
if !known {
p, err := FetchRepoIsPrivate(ctx, client, owner, repo)
if err != nil {
return r
}
isPrivate = p
known = true
}
setIFCLabel(r, labelFn(isPrivate))
return r
}
}
+2
-2

@@ -1,2 +0,2 @@

FROM node:26-alpine@sha256:7c6af15abe4e3de859690e7db171d0d711bf37d27528eddfe625b2fe89e097f8 AS ui-build
FROM node:26-alpine@sha256:144769ec3f32e8ee36b3cfde91e82bee25d9367b20f31a151f3f7eea3a2a8541 AS ui-build
WORKDIR /app

@@ -10,3 +10,3 @@ COPY ui/package*.json ./ui/

FROM golang:1.25.10-alpine@sha256:8d22e29d960bc50cd025d93d5b7c7d220b1ee9aa7a239b3c8f55a57e987e8d45 AS build
FROM golang:1.25.11-alpine@sha256:cd2fb3559df6e13bc93b7f0734a4eabe1d21e7b64eec211ed90784f00a17a56a AS build
ARG VERSION="dev"

@@ -13,0 +13,0 @@

@@ -290,2 +290,15 @@ # Feature Flags

### `file_blame`
- **get_file_blame** - Get file blame information
- **Required OAuth Scopes**: `repo`
- `after`: Cursor for pagination. Use the cursor from the previous response. (string, optional)
- `end_line`: Optional 1-based ending line of the window of interest. Must be >= start_line when both are provided. (number, optional)
- `owner`: Repository owner (username or organization) (string, required)
- `path`: Path to the file in the repository, relative to the repository root (string, required)
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
- `ref`: Git reference (branch, tag, or commit SHA). Defaults to the repository's default branch (HEAD). (string, optional)
- `repo`: Repository name (string, required)
- `start_line`: Optional 1-based starting line of the window of interest. Only ranges overlapping [start_line, end_line] are returned, clamped to the window. (number, optional)
<!-- END AUTOMATED FEATURE FLAG TOOLS -->

@@ -110,2 +110,15 @@ # Insiders Features

### `file_blame`
- **get_file_blame** - Get file blame information
- **Required OAuth Scopes**: `repo`
- `after`: Cursor for pagination. Use the cursor from the previous response. (string, optional)
- `end_line`: Optional 1-based ending line of the window of interest. Must be >= start_line when both are provided. (number, optional)
- `owner`: Repository owner (username or organization) (string, required)
- `path`: Path to the file in the repository, relative to the repository root (string, required)
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
- `ref`: Git reference (branch, tag, or commit SHA). Defaults to the repository's default branch (HEAD). (string, optional)
- `repo`: Repository name (string, required)
- `start_line`: Optional 1-based starting line of the window of interest. Only ranges overlapping [start_line, end_line] are returned, clamped to the window. (number, optional)
<!-- END AUTOMATED INSIDERS TOOLS -->

@@ -112,0 +125,0 @@

@@ -8,4 +8,5 @@ package errors

"testing"
"time"
"github.com/google/go-github/v87/github"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/stretchr/testify/assert"

@@ -464,1 +465,228 @@ "github.com/stretchr/testify/require"

}
// requireErrorText asserts that result is a non-nil MCP tool error and returns its text content.
func requireErrorText(t *testing.T, result *mcp.CallToolResult) string {
t.Helper()
require.NotNil(t, result)
require.True(t, result.IsError)
require.NotEmpty(t, result.Content)
text, ok := result.Content[0].(*mcp.TextContent)
require.True(t, ok, "expected *mcp.TextContent, got %T", result.Content[0])
return text.Text
}
// assertContextHasError asserts that exactly one error is stored in ctx and it matches expectedErr.
//
//nolint:revive // t must be first for test helpers; context-as-argument doesn't apply here
func assertContextHasError(t *testing.T, ctx context.Context, expectedErr error) {
t.Helper()
apiErrors, err := GetGitHubAPIErrors(ctx)
require.NoError(t, err)
require.Len(t, apiErrors, 1)
assert.Equal(t, expectedErr, apiErrors[0].Err)
}
func TestNewGitHubAPIErrorResponse_RateLimits(t *testing.T) {
t.Run("RateLimitError produces clean message with retry time", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
resetTime := time.Now().Add(30 * time.Minute)
rateLimitErr := &github.RateLimitError{
Rate: github.Rate{Reset: github.Timestamp{Time: resetTime}},
Response: &http.Response{StatusCode: 403},
Message: "API rate limit exceeded",
}
resp := &github.Response{Response: rateLimitErr.Response}
// Capture expected duration before the call so both use the same time.Until snapshot
expectedRetryIn := time.Until(resetTime).Round(time.Second)
// When we create an API error response for a rate limit error
result := NewGitHubAPIErrorResponse(ctx, "search code", resp, rateLimitErr)
// Then the message should be clean and actionable (no raw URLs or status codes)
text := requireErrorText(t, result)
assert.Contains(t, text, fmt.Sprintf("GitHub API rate limit exceeded. Retry after %v.", expectedRetryIn))
assert.NotContains(t, text, "https://")
assert.NotContains(t, text, "403")
// And the original error should still be stored in context for middleware
assertContextHasError(t, ctx, rateLimitErr)
})
t.Run("AbuseRateLimitError with RetryAfter produces clean message with wait time", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
retryAfter := 47 * time.Second
abuseErr := &github.AbuseRateLimitError{
Response: &http.Response{StatusCode: 403},
Message: "You have exceeded a secondary rate limit.",
RetryAfter: &retryAfter,
}
resp := &github.Response{Response: abuseErr.Response}
// When we create an API error response for a secondary rate limit error
result := NewGitHubAPIErrorResponse(ctx, "create issue", resp, abuseErr)
// And the message should include the specific retry duration
text := requireErrorText(t, result)
assert.Contains(t, text, "GitHub secondary rate limit exceeded. Retry after 47s.")
assert.NotContains(t, text, "https://")
assert.NotContains(t, text, "403")
// And the original error should still be stored in context for middleware
assertContextHasError(t, ctx, abuseErr)
})
t.Run("AbuseRateLimitError without RetryAfter produces clean message without wait time", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
abuseErr := &github.AbuseRateLimitError{
Response: &http.Response{StatusCode: 403},
Message: "You have exceeded a secondary rate limit.",
RetryAfter: nil,
}
resp := &github.Response{Response: abuseErr.Response}
// When we create an API error response for a secondary rate limit error without retry info
result := NewGitHubAPIErrorResponse(ctx, "create issue", resp, abuseErr)
// And the message should be clean and actionable
text := requireErrorText(t, result)
assert.Contains(t, text, "GitHub secondary rate limit exceeded. Wait before retrying.")
assert.NotContains(t, text, "https://")
assert.NotContains(t, text, "403")
// And the original error should still be stored in context for middleware
assertContextHasError(t, ctx, abuseErr)
})
t.Run("AbuseRateLimitError with sub-second RetryAfter falls back to wait message", func(t *testing.T) {
ctx := ContextWithGitHubErrors(context.Background())
// 200ms rounds to 0s, so should fall back to the generic wait message
retryAfter := 200 * time.Millisecond
abuseErr := &github.AbuseRateLimitError{
Response: &http.Response{StatusCode: 403},
Message: "You have exceeded a secondary rate limit.",
RetryAfter: &retryAfter,
}
resp := &github.Response{Response: abuseErr.Response}
result := NewGitHubAPIErrorResponse(ctx, "create issue", resp, abuseErr)
text := requireErrorText(t, result)
assert.Contains(t, text, "GitHub secondary rate limit exceeded. Wait before retrying.")
})
t.Run("RateLimitError with reset time in the past falls back to wait message", func(t *testing.T) {
ctx := ContextWithGitHubErrors(context.Background())
resetTime := time.Now().Add(-5 * time.Second) // already passed
rateLimitErr := &github.RateLimitError{
Rate: github.Rate{Reset: github.Timestamp{Time: resetTime}},
Response: &http.Response{StatusCode: 403},
Message: "API rate limit exceeded",
}
resp := &github.Response{Response: rateLimitErr.Response}
result := NewGitHubAPIErrorResponse(ctx, "search code", resp, rateLimitErr)
text := requireErrorText(t, result)
assert.Contains(t, text, "GitHub API rate limit exceeded. Wait before retrying.")
})
t.Run("RateLimitError with sub-second reset time falls back to wait message", func(t *testing.T) {
ctx := ContextWithGitHubErrors(context.Background())
// 250ms in the future: still positive, but rounds to 0s, so should fall back
resetTime := time.Now().Add(250 * time.Millisecond)
rateLimitErr := &github.RateLimitError{
Rate: github.Rate{Reset: github.Timestamp{Time: resetTime}},
Response: &http.Response{StatusCode: 403},
Message: "API rate limit exceeded",
}
resp := &github.Response{Response: rateLimitErr.Response}
result := NewGitHubAPIErrorResponse(ctx, "search code", resp, rateLimitErr)
text := requireErrorText(t, result)
assert.Contains(t, text, "GitHub API rate limit exceeded. Wait before retrying.")
})
t.Run("RateLimitError with zero reset time falls back to wait message", func(t *testing.T) {
ctx := ContextWithGitHubErrors(context.Background())
rateLimitErr := &github.RateLimitError{
Rate: github.Rate{}, // zero Reset time
Response: &http.Response{StatusCode: 403},
Message: "API rate limit exceeded",
}
resp := &github.Response{Response: rateLimitErr.Response}
result := NewGitHubAPIErrorResponse(ctx, "search code", resp, rateLimitErr)
text := requireErrorText(t, result)
assert.Contains(t, text, "GitHub API rate limit exceeded. Wait before retrying.")
})
t.Run("wrapped RateLimitError is handled via errors.As", func(t *testing.T) {
ctx := ContextWithGitHubErrors(context.Background())
resetTime := time.Now().Add(20 * time.Minute)
rateLimitErr := &github.RateLimitError{
Rate: github.Rate{Reset: github.Timestamp{Time: resetTime}},
Response: &http.Response{StatusCode: 403},
Message: "API rate limit exceeded",
}
wrappedErr := fmt.Errorf("transport layer: %w", rateLimitErr)
resp := &github.Response{Response: rateLimitErr.Response}
// Capture expected duration before the call so both use the same time.Until snapshot
expectedRetryIn := time.Until(resetTime).Round(time.Second)
result := NewGitHubAPIErrorResponse(ctx, "search code", resp, wrappedErr)
text := requireErrorText(t, result)
assert.Contains(t, text, fmt.Sprintf("GitHub API rate limit exceeded. Retry after %v.", expectedRetryIn))
assert.NotContains(t, text, "https://")
})
t.Run("wrapped AbuseRateLimitError is handled via errors.As", func(t *testing.T) {
ctx := ContextWithGitHubErrors(context.Background())
retryAfter := 30 * time.Second
abuseErr := &github.AbuseRateLimitError{
Response: &http.Response{StatusCode: 403},
Message: "secondary rate limit",
RetryAfter: &retryAfter,
}
wrappedErr := fmt.Errorf("transport layer: %w", abuseErr)
resp := &github.Response{Response: abuseErr.Response}
result := NewGitHubAPIErrorResponse(ctx, "create issue", resp, wrappedErr)
text := requireErrorText(t, result)
assert.Contains(t, text, "GitHub secondary rate limit exceeded. Retry after 30s.")
assert.NotContains(t, text, "https://")
})
t.Run("non-rate-limit GitHub API error passes through the original error message", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
resp := &github.Response{Response: &http.Response{StatusCode: 422}}
originalErr := fmt.Errorf("validation failed")
// When we create an API error response for a non-rate-limit error
result := NewGitHubAPIErrorResponse(ctx, "API call failed", resp, originalErr)
// Then the message should contain the original error text unchanged
text := requireErrorText(t, result)
assert.Contains(t, text, "validation failed")
})
}

@@ -5,4 +5,6 @@ package errors

"context"
stderrors "errors"
"fmt"
"net/http"
"time"

@@ -163,2 +165,31 @@ "github.com/github/github-mcp-server/pkg/utils"

}
var rateLimitErr *github.RateLimitError
if stderrors.As(err, &rateLimitErr) {
resetTime := rateLimitErr.Rate.Reset.Time
if !resetTime.IsZero() {
retryIn := time.Until(resetTime).Round(time.Second)
if retryIn > 0 {
return utils.NewToolResultError(fmt.Sprintf(
"%s: GitHub API rate limit exceeded. Retry after %v.", message, retryIn))
}
}
return utils.NewToolResultError(fmt.Sprintf(
"%s: GitHub API rate limit exceeded. Wait before retrying.", message))
}
var abuseErr *github.AbuseRateLimitError
if stderrors.As(err, &abuseErr) {
if abuseErr.RetryAfter != nil {
retryAfter := abuseErr.RetryAfter.Round(time.Second)
if retryAfter > 0 {
return utils.NewToolResultError(fmt.Sprintf(
"%s: GitHub secondary rate limit exceeded. Retry after %v.",
message, retryAfter))
}
}
return utils.NewToolResultError(fmt.Sprintf(
"%s: GitHub secondary rate limit exceeded. Wait before retrying.", message))
}
return utils.NewToolResultErrorFromErr(message, err)

@@ -165,0 +196,0 @@ }

@@ -15,2 +15,3 @@ package github

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/ifc"
"github.com/github/github-mcp-server/pkg/inventory"

@@ -358,2 +359,10 @@ "github.com/github/github-mcp-server/pkg/scopes"

// attachIFC adds the IFC label to a successful Actions result when
// IFC labels are enabled. Workflow definitions, runs, jobs,
// artifacts and logs echo attacker-influenceable run output, so
// integrity is untrusted; confidentiality follows repo visibility.
attachIFC := func(r *mcp.CallToolResult) *mcp.CallToolResult {
return attachRepoVisibilityIFCLabel(ctx, deps, client, owner, repo, r, ifc.LabelActionsResult)
}
var resourceIDInt int64

@@ -381,9 +390,13 @@ var parseErr error

case actionsMethodListWorkflows:
return listWorkflows(ctx, client, owner, repo, pagination)
result, payload, err := listWorkflows(ctx, client, owner, repo, pagination)
return attachIFC(result), payload, err
case actionsMethodListWorkflowRuns:
return listWorkflowRuns(ctx, client, args, owner, repo, resourceID, pagination)
result, payload, err := listWorkflowRuns(ctx, client, args, owner, repo, resourceID, pagination)
return attachIFC(result), payload, err
case actionsMethodListWorkflowJobs:
return listWorkflowJobs(ctx, client, args, owner, repo, resourceIDInt, pagination)
result, payload, err := listWorkflowJobs(ctx, client, args, owner, repo, resourceIDInt, pagination)
return attachIFC(result), payload, err
case actionsMethodListWorkflowArtifacts:
return listWorkflowArtifacts(ctx, client, owner, repo, resourceIDInt, pagination)
result, payload, err := listWorkflowArtifacts(ctx, client, owner, repo, resourceIDInt, pagination)
return attachIFC(result), payload, err
default:

@@ -471,2 +484,10 @@ return utils.NewToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil

// attachIFC adds the IFC label to a successful Actions result when
// IFC labels are enabled. Workflow runs, jobs, artifacts, usage,
// and log URLs reflect attacker-influenceable run output, so
// integrity is untrusted; confidentiality follows repo visibility.
attachIFC := func(r *mcp.CallToolResult) *mcp.CallToolResult {
return attachRepoVisibilityIFCLabel(ctx, deps, client, owner, repo, r, ifc.LabelActionsResult)
}
var resourceIDInt int64

@@ -487,13 +508,19 @@ var parseErr error

case actionsMethodGetWorkflow:
return getWorkflow(ctx, client, owner, repo, resourceID)
result, payload, err := getWorkflow(ctx, client, owner, repo, resourceID)
return attachIFC(result), payload, err
case actionsMethodGetWorkflowRun:
return getWorkflowRun(ctx, client, owner, repo, resourceIDInt)
result, payload, err := getWorkflowRun(ctx, client, owner, repo, resourceIDInt)
return attachIFC(result), payload, err
case actionsMethodGetWorkflowJob:
return getWorkflowJob(ctx, client, owner, repo, resourceIDInt)
result, payload, err := getWorkflowJob(ctx, client, owner, repo, resourceIDInt)
return attachIFC(result), payload, err
case actionsMethodDownloadWorkflowArtifact:
return downloadWorkflowArtifact(ctx, client, owner, repo, resourceIDInt)
result, payload, err := downloadWorkflowArtifact(ctx, client, owner, repo, resourceIDInt)
return attachIFC(result), payload, err
case actionsMethodGetWorkflowRunUsage:
return getWorkflowRunUsage(ctx, client, owner, repo, resourceIDInt)
result, payload, err := getWorkflowRunUsage(ctx, client, owner, repo, resourceIDInt)
return attachIFC(result), payload, err
case actionsMethodGetWorkflowRunLogsURL:
return getWorkflowRunLogsURL(ctx, client, owner, repo, resourceIDInt)
result, payload, err := getWorkflowRunLogsURL(ctx, client, owner, repo, resourceIDInt)
return attachIFC(result), payload, err
default:

@@ -727,8 +754,18 @@ return utils.NewToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil

// attachIFC adds the IFC label to a successful result when IFC
// labels are enabled. Job logs echo attacker-influenceable run
// output, so integrity is untrusted; confidentiality follows repo
// visibility.
attachIFC := func(r *mcp.CallToolResult) *mcp.CallToolResult {
return attachRepoVisibilityIFCLabel(ctx, deps, client, owner, repo, r, ifc.LabelActionsResult)
}
if failedOnly && runID > 0 {
// Handle failed-only mode: get logs for all failed jobs in the workflow run
return handleFailedJobLogs(ctx, client, owner, repo, int64(runID), returnContent, tailLines, deps.GetContentWindowSize())
result, payload, err := handleFailedJobLogs(ctx, client, owner, repo, int64(runID), returnContent, tailLines, deps.GetContentWindowSize())
return attachIFC(result), payload, err
} else if jobID > 0 {
// Handle single job mode
return handleSingleJobLogs(ctx, client, owner, repo, int64(jobID), returnContent, tailLines, deps.GetContentWindowSize())
result, payload, err := handleSingleJobLogs(ctx, client, owner, repo, int64(jobID), returnContent, tailLines, deps.GetContentWindowSize())
return attachIFC(result), payload, err
}

@@ -735,0 +772,0 @@

@@ -10,2 +10,3 @@ package github

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/ifc"
"github.com/github/github-mcp-server/pkg/inventory"

@@ -92,3 +93,8 @@ "github.com/github/github-mcp-server/pkg/scopes"

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// Code scanning alerts are access-restricted regardless of repo
// visibility and embed attacker-influenceable code snippets, so the
// label is always private-untrusted.
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelSecurityAlert())
return result, nil, nil
},

@@ -213,5 +219,10 @@ )

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// Code scanning alerts are access-restricted regardless of repo
// visibility and embed attacker-influenceable code snippets, so the
// label is always private-untrusted.
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelSecurityAlert())
return result, nil, nil
},
)
}

@@ -202,3 +202,5 @@ package github

assert.Equal(t, "trusted", ifcMap["integrity"])
assert.Equal(t, "public", ifcMap["confidentiality"])
// get_me returns the caller's private repo/gist counts, which are not
// part of the public profile, so confidentiality is private.
assert.Equal(t, "private", ifcMap["confidentiality"])
})

@@ -205,0 +207,0 @@ }

@@ -109,8 +109,3 @@ package github

result := MarshalledTextResult(minimalUser)
if deps.IsFeatureEnabled(ctx, FeatureFlagIFCLabels) {
if result.Meta == nil {
result.Meta = mcp.Meta{}
}
result.Meta["ifc"] = ifc.LabelGetMe()
}
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelGetMe())
return result, nil, nil

@@ -225,3 +220,8 @@ },

return MarshalledTextResult(organizations), nil, nil
result := MarshalledTextResult(organizations)
// Team membership is maintained by GitHub and cannot be forged by
// outside contributors (trusted). Org team rosters are visible only
// to org members, so confidentiality is private.
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelTeam())
return result, nil, nil
},

@@ -297,5 +297,10 @@ )

return MarshalledTextResult(members), nil, nil
result := MarshalledTextResult(members)
// Team membership is maintained by GitHub and cannot be forged by
// outside contributors (trusted). A team's member roster is visible
// only to org members, so confidentiality is private.
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelTeam())
return result, nil, nil
},
)
}

@@ -11,2 +11,3 @@ package github

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/ifc"
"github.com/github/github-mcp-server/pkg/inventory"

@@ -93,3 +94,8 @@ "github.com/github/github-mcp-server/pkg/scopes"

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// Dependabot alerts are access-restricted regardless of repo
// visibility and embed attacker-influenceable advisory text, so the
// label is always private-untrusted.
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelSecurityAlert())
return result, nil, nil
},

@@ -202,3 +208,8 @@ )

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// Dependabot alerts are access-restricted regardless of repo
// visibility and embed attacker-influenceable advisory text, so the
// label is always private-untrusted.
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelSecurityAlert())
return result, nil, nil
},

@@ -205,0 +216,0 @@ )

@@ -9,2 +9,3 @@ package github

"github.com/github/github-mcp-server/pkg/ifc"
"github.com/github/github-mcp-server/pkg/inventory"

@@ -276,3 +277,7 @@ "github.com/github/github-mcp-server/pkg/scopes"

}
return utils.NewToolResultText(string(out)), nil, nil
result := utils.NewToolResultText(string(out))
// Discussion content is user-authored (untrusted); confidentiality
// follows repo visibility.
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, owner, repo, result, ifc.LabelListIssues)
return result, nil, nil
},

@@ -381,3 +386,7 @@ )

return utils.NewToolResultText(string(out)), nil, nil
result := utils.NewToolResultText(string(out))
// Discussion content is user-authored (untrusted); confidentiality
// follows repo visibility.
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, params.Owner, params.Repo, result, ifc.LabelListIssues)
return result, nil, nil
},

@@ -586,3 +595,7 @@ )

return utils.NewToolResultText(string(out)), nil, nil
result := utils.NewToolResultText(string(out))
// Discussion comments are user-authored (untrusted); confidentiality
// follows repo visibility.
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, params.Owner, params.Repo, result, ifc.LabelListIssues)
return result, nil, nil
},

@@ -1091,5 +1104,9 @@ )

}
return utils.NewToolResultText(string(out)), nil, nil
result := utils.NewToolResultText(string(out))
// Discussion categories are repo-defined structural metadata
// (trusted); confidentiality follows repo visibility.
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, owner, repo, result, ifc.LabelRepoMetadata)
return result, nil, nil
},
)
}

@@ -165,6 +165,6 @@ package github

{
name: "insiders mode enables internal-only flags",
name: "insiders mode does not auto-enable ifc labels",
enabledFeatures: nil,
insidersMode: true,
expectedFlags: []string{FeatureFlagIFCLabels},
unexpectedFlags: []string{FeatureFlagIFCLabels},
},

@@ -171,0 +171,0 @@ {

@@ -19,2 +19,7 @@ package github

// FeatureFlagFileBlame is the feature flag name for the get_file_blame tool,
// which exposes git blame information for a file. It is gated so the extra tool
// is not advertised by default, keeping the tool surface small unless opted in.
const FeatureFlagFileBlame = "file_blame"
// AllowedFeatureFlags is the allowlist of feature flags that can be enabled

@@ -31,2 +36,3 @@ // by users via --features CLI flag or X-MCP-Features HTTP header.

FeatureFlagPullRequestsGranular,
FeatureFlagFileBlame,
}

@@ -41,4 +47,4 @@

FeatureFlagCSVOutput,
FeatureFlagIFCLabels,
FeatureFlagIssueFields,
FeatureFlagFileBlame,
}

@@ -45,0 +51,0 @@

@@ -11,2 +11,3 @@ package github

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/ifc"
"github.com/github/github-mcp-server/pkg/inventory"

@@ -103,3 +104,11 @@ "github.com/github/github-mcp-server/pkg/scopes"

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// Gist contents are user-authored (untrusted); confidentiality is
// the IFC join of each gist's own public/secret flag.
visibilities := make([]bool, 0, len(gists))
for _, g := range gists {
visibilities = append(visibilities, g.GetPublic())
}
result = attachJoinedIFCLabel(ctx, deps, result, visibilities, ifc.LabelGistList)
return result, nil, nil
},

@@ -162,3 +171,7 @@ )

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// Gist contents are user-authored (untrusted); confidentiality
// derives from the gist's own public/secret flag.
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelGist(gist.GetPublic()))
return result, nil, nil
},

@@ -165,0 +178,0 @@ )

@@ -10,2 +10,3 @@ package github

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/ifc"
"github.com/github/github-mcp-server/pkg/inventory"

@@ -175,5 +176,11 @@ "github.com/github/github-mcp-server/pkg/scopes"

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// The repository tree exposes committed file structure; in public
// repos anyone can land content via a PR (untrusted), in private
// repos only collaborators can (trusted). Confidentiality follows
// repo visibility.
result = attachRepoVisibilityIFCLabel(ctx, deps, client, owner, repo, result, ifc.LabelCommitContents)
return result, nil, nil
},
)
}

@@ -72,2 +72,3 @@ package github

GetReposPullsByOwnerByRepoByPullNumber = "GET /repos/{owner}/{repo}/pulls/{pull_number}"
GetReposPullsCommitsByOwnerByRepoByPullNumber = "GET /repos/{owner}/{repo}/pulls/{pull_number}/commits"
GetReposPullsFilesByOwnerByRepoByPullNumber = "GET /repos/{owner}/{repo}/pulls/{pull_number}/files"

@@ -74,0 +75,0 @@ GetReposPullsReviewsByOwnerByRepoByPullNumber = "GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews"

@@ -11,2 +11,3 @@ package github

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/ifc"
"github.com/github/github-mcp-server/pkg/inventory"

@@ -159,3 +160,13 @@ "github.com/github/github-mcp-server/pkg/scopes"

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// Issue field definitions are repo/org structural metadata
// (trusted). When scoped to a specific repo, confidentiality
// follows that repo's visibility; for an org-level lookup (no
// repo) it is conservatively treated as private.
if repo == "" {
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelRepoMetadata(true))
} else {
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, owner, repo, result, ifc.LabelRepoMetadata)
}
return result, nil, nil
})

@@ -162,0 +173,0 @@ st.FeatureFlagEnable = FeatureFlagIssueFields

@@ -10,2 +10,3 @@ package github

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/ifc"
"github.com/github/github-mcp-server/pkg/inventory"

@@ -109,3 +110,7 @@ "github.com/github/github-mcp-server/pkg/scopes"

return utils.NewToolResultText(string(out)), nil, nil
result := utils.NewToolResultText(string(out))
// Labels are structural repo metadata defined by collaborators
// (trusted); confidentiality follows repo visibility.
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, owner, repo, result, ifc.LabelRepoMetadata)
return result, nil, nil
},

@@ -209,3 +214,7 @@ )

return utils.NewToolResultText(string(out)), nil, nil
result := utils.NewToolResultText(string(out))
// Labels are structural repo metadata defined by collaborators
// (trusted); confidentiality follows repo visibility.
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, owner, repo, result, ifc.LabelRepoMetadata)
return result, nil, nil
},

@@ -212,0 +221,0 @@ )

@@ -126,2 +126,10 @@ package github

// MinimalPullRequestCommit is the trimmed output type for commits listed on a pull request.
type MinimalPullRequestCommit struct {
SHA string `json:"sha"`
HTMLURL string `json:"html_url,omitempty"`
Message string `json:"message,omitempty"`
Author *MinimalCommitAuthor `json:"author,omitempty"`
}
// MinimalCommit is the trimmed output type for commit objects.

@@ -1613,2 +1621,40 @@ type MinimalCommit struct {

func convertToMinimalPullRequestCommits(commits []*github.RepositoryCommit) []MinimalPullRequestCommit {
result := make([]MinimalPullRequestCommit, 0, len(commits))
for _, commit := range commits {
if commit == nil {
continue
}
minimalCommit := MinimalPullRequestCommit{
SHA: commit.GetSHA(),
HTMLURL: commit.GetHTMLURL(),
}
if commit.Commit != nil {
minimalCommit.Message = commit.Commit.GetMessage()
minimalCommit.Author = convertToMinimalCommitAuthor(commit.Commit.Author)
}
result = append(result, minimalCommit)
}
return result
}
func convertToMinimalCommitAuthor(author *github.CommitAuthor) *MinimalCommitAuthor {
if author == nil {
return nil
}
minimalAuthor := &MinimalCommitAuthor{
Name: author.GetName(),
Email: author.GetEmail(),
}
if author.Date != nil {
minimalAuthor.Date = author.Date.Format(time.RFC3339)
}
return minimalAuthor
}
// convertToMinimalBranch converts a GitHub API Branch to MinimalBranch

@@ -1615,0 +1661,0 @@ func convertToMinimalBranch(branch *github.Branch) MinimalBranch {

@@ -12,2 +12,3 @@ package github

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/ifc"
"github.com/github/github-mcp-server/pkg/inventory"

@@ -390,3 +391,9 @@ "github.com/github/github-mcp-server/pkg/scopes"

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// A notification subject points at an issue, PR, comment, or
// discussion whose content is user-authored (untrusted). It is
// delivered to a specific recipient and may reference private
// repositories, so confidentiality is private.
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelNotificationDetails())
return result, nil, nil
},

@@ -393,0 +400,0 @@ )

@@ -13,2 +13,3 @@ package github

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/ifc"
"github.com/github/github-mcp-server/pkg/inventory"

@@ -231,5 +232,15 @@ "github.com/github/github-mcp-server/pkg/scopes"

// attachIFC adds the IFC label to a successful result when IFC
// labels are enabled. Project titles, item content, field
// definitions, and status updates are user-authored free text
// (untrusted); confidentiality is conservatively private since the
// project's public flag is not available across every sub-result.
attachIFC := func(r *mcp.CallToolResult) *mcp.CallToolResult {
return attachStaticIFCLabel(ctx, deps, r, ifc.LabelProject(false))
}
switch method {
case projectsMethodListProjects:
return listProjects(ctx, client, args, owner, ownerType)
result, payload, err := listProjects(ctx, client, args, owner, ownerType)
return attachIFC(result), payload, err
default:

@@ -250,5 +261,7 @@ // All other methods require project_number and ownerType detection

case projectsMethodListProjectFields:
return listProjectFields(ctx, client, args, owner, ownerType)
result, payload, err := listProjectFields(ctx, client, args, owner, ownerType)
return attachIFC(result), payload, err
case projectsMethodListProjectItems:
return listProjectItems(ctx, client, args, owner, ownerType)
result, payload, err := listProjectItems(ctx, client, args, owner, ownerType)
return attachIFC(result), payload, err
case projectsMethodListProjectStatusUpdates:

@@ -259,3 +272,4 @@ gqlClient, err := deps.GetGQLClient(ctx)

}
return listProjectStatusUpdates(ctx, gqlClient, args, owner, ownerType)
result, payload, err := listProjectStatusUpdates(ctx, gqlClient, args, owner, ownerType)
return attachIFC(result), payload, err
default:

@@ -339,2 +353,10 @@ return utils.NewToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil

// attachIFC adds the IFC label to a successful result when IFC
// labels are enabled. Project data is user-authored free text
// (untrusted); confidentiality is conservatively private since the
// project's public flag is not available across every sub-result.
attachIFC := func(r *mcp.CallToolResult) *mcp.CallToolResult {
return attachStaticIFCLabel(ctx, deps, r, ifc.LabelProject(false))
}
// Handle get_project_status_update early — it only needs status_update_id

@@ -350,3 +372,4 @@ if method == projectsMethodGetProjectStatusUpdate {

}
return getProjectStatusUpdate(ctx, gqlClient, statusUpdateID)
result, payload, err := getProjectStatusUpdate(ctx, gqlClient, statusUpdateID)
return attachIFC(result), payload, err
}

@@ -384,3 +407,4 @@

case projectsMethodGetProject:
return getProject(ctx, client, owner, ownerType, projectNumber)
result, payload, err := getProject(ctx, client, owner, ownerType, projectNumber)
return attachIFC(result), payload, err
case projectsMethodGetProjectField:

@@ -391,3 +415,4 @@ fieldID, err := RequiredBigInt(args, "field_id")

}
return getProjectField(ctx, client, owner, ownerType, projectNumber, fieldID)
result, payload, err := getProjectField(ctx, client, owner, ownerType, projectNumber, fieldID)
return attachIFC(result), payload, err
case projectsMethodGetProjectItem:

@@ -402,3 +427,4 @@ itemID, err := RequiredBigInt(args, "item_id")

}
return getProjectItem(ctx, client, owner, ownerType, projectNumber, itemID, fields)
result, payload, err := getProjectItem(ctx, client, owner, ownerType, projectNumber, itemID, fields)
return attachIFC(result), payload, err
default:

@@ -405,0 +431,0 @@ return utils.NewToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil

@@ -166,5 +166,3 @@ package github

callResult := utils.NewToolResultText(string(r))
if deps.IsFeatureEnabled(ctx, FeatureFlagIFCLabels) {
attachSearchRepositoriesIFCLabel(result.Repositories, callResult)
}
attachSearchRepositoriesIFCLabel(ctx, deps, result.Repositories, callResult)
return callResult, nil, nil

@@ -176,9 +174,11 @@ },

// attachSearchRepositoriesIFCLabel joins per-repository IFC labels across
// every matched repository and attaches the result to callResult. Visibility
// is read directly from the search response — no extra API call. The join
// math is shared with search_issues via ifc.LabelSearchIssues: integrity is
// always untrusted; confidentiality is private if any matched repository is
// private, otherwise public.
func attachSearchRepositoriesIFCLabel(repos []*github.Repository, callResult *mcp.CallToolResult) {
if callResult == nil || callResult.IsError {
// every matched repository and attaches the result to callResult when IFC
// labels are enabled. Visibility is read directly from the search response —
// no extra API call. The join math is shared with search_issues via
// ifc.LabelSearchIssues: integrity is always untrusted; confidentiality is
// private if any matched repository is private, otherwise public. The
// feature-flag check is centralized here (mirroring the attach* helpers in
// ifc_labels.go) so the handler can call this unconditionally.
func attachSearchRepositoriesIFCLabel(ctx context.Context, deps ToolDependencies, repos []*github.Repository, callResult *mcp.CallToolResult) {
if callResult == nil || callResult.IsError || !deps.IsFeatureEnabled(ctx, FeatureFlagIFCLabels) {
return

@@ -192,6 +192,3 @@ }

if callResult.Meta == nil {
callResult.Meta = mcp.Meta{}
}
callResult.Meta["ifc"] = ifc.LabelSearchIssues(visibilities)
setIFCLabel(callResult, ifc.LabelSearchIssues(visibilities))
}

@@ -310,3 +307,14 @@

return utils.NewToolResultText(string(r)), nil, nil
callResult := utils.NewToolResultText(string(r))
// Code search spans repositories and exposes file contents
// (untrusted). Confidentiality is the IFC join across every matched
// repository's visibility, read directly from the search response.
visibilities := make([]bool, 0, len(result.CodeResults))
for _, code := range result.CodeResults {
if code.Repository != nil {
visibilities = append(visibilities, code.Repository.GetPrivate())
}
}
callResult = attachJoinedIFCLabel(ctx, deps, callResult, visibilities, ifc.LabelSearchIssues)
return callResult, nil, nil
},

@@ -399,3 +407,7 @@ )

}
return utils.NewToolResultText(string(r)), nil, nil
callResult := utils.NewToolResultText(string(r))
// User and organization search returns public profile information that is
// authored by the account holders themselves, so it is public-untrusted.
callResult = attachStaticIFCLabel(ctx, deps, callResult, ifc.PublicUntrusted())
return callResult, nil, nil
}

@@ -588,5 +600,16 @@

return utils.NewToolResultText(string(r)), nil, nil
callResult := utils.NewToolResultText(string(r))
// Commit search spans repositories and exposes commit content
// (untrusted). Confidentiality is the IFC join across every matched
// repository's visibility, read directly from the search response.
visibilities := make([]bool, 0, len(result.Commits))
for _, commit := range result.Commits {
if commit.Repository != nil {
visibilities = append(visibilities, commit.Repository.GetPrivate())
}
}
callResult = attachJoinedIFCLabel(ctx, deps, callResult, visibilities, ifc.LabelSearchIssues)
return callResult, nil, nil
},
)
}

@@ -11,2 +11,3 @@ package github

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/ifc"
"github.com/github/github-mcp-server/pkg/inventory"

@@ -93,3 +94,8 @@ "github.com/github/github-mcp-server/pkg/scopes"

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// Secret scanning alerts are access-restricted regardless of repo
// visibility and surface the matched secret material itself, so the
// label is always private-untrusted.
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelSecurityAlert())
return result, nil, nil
},

@@ -204,5 +210,10 @@ )

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// Secret scanning alerts are access-restricted regardless of repo
// visibility and surface the matched secret material itself, so the
// label is always private-untrusted.
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelSecurityAlert())
return result, nil, nil
},
)
}

@@ -13,2 +13,3 @@ package github

"github.com/google/jsonschema-go/jsonschema"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/stretchr/testify/assert"

@@ -374,2 +375,128 @@ "github.com/stretchr/testify/require"

// Test_ListRepositorySecurityAdvisories_IFC_FeatureFlag verifies the IFC label
// attached to list_repository_security_advisories. The label is only present
// when the ifc_labels feature flag is enabled, and — critically — confidentiality
// is public only when the repository is public AND every returned advisory is
// published. Draft/triage/closed advisories are not world-readable even on a
// public repo, so a result containing one must be labeled private. This guards
// against the under-classification raised in PR review.
func Test_ListRepositorySecurityAdvisories_IFC_FeatureFlag(t *testing.T) {
t.Parallel()
toolDef := ListRepositorySecurityAdvisories(translations.NullTranslationHelper)
publishedAdvisory := &github.SecurityAdvisory{
GHSAID: github.Ptr("GHSA-1111-1111-1111"),
Summary: github.Ptr("Published advisory"),
State: github.Ptr("published"),
}
draftAdvisory := &github.SecurityAdvisory{
GHSAID: github.Ptr("GHSA-2222-2222-2222"),
Summary: github.Ptr("Draft advisory"),
State: github.Ptr("draft"),
}
makeMockClient := func(isPrivate bool, advisories []*github.SecurityAdvisory) *http.Client {
return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
GetReposSecurityAdvisoriesByOwnerByRepo: mockResponse(t, http.StatusOK, advisories),
GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, map[string]any{
"name": "repo",
"private": isPrivate,
}),
})
}
reqParams := map[string]any{
"owner": "owner",
"repo": "repo",
}
readIFC := func(t *testing.T, result *mcp.CallToolResult) (map[string]any, bool) {
t.Helper()
if result.Meta == nil {
return nil, false
}
label, ok := result.Meta["ifc"]
if !ok {
return nil, false
}
labelJSON, err := json.Marshal(label)
require.NoError(t, err)
var labelMap map[string]any
require.NoError(t, json.Unmarshal(labelJSON, &labelMap))
return labelMap, true
}
t.Run("feature flag disabled omits ifc label", func(t *testing.T) {
t.Parallel()
deps := BaseDeps{Client: mustNewGHClient(t, makeMockClient(false, []*github.SecurityAdvisory{publishedAdvisory}))}
handler := toolDef.Handler(deps)
request := createMCPRequest(reqParams)
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
require.NoError(t, err)
require.False(t, result.IsError)
assert.Nil(t, result.Meta, "result meta should be nil when IFC labels are disabled")
})
t.Run("public repo with only published advisories is public", func(t *testing.T) {
t.Parallel()
deps := BaseDeps{
Client: mustNewGHClient(t, makeMockClient(false, []*github.SecurityAdvisory{publishedAdvisory})),
featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
}
handler := toolDef.Handler(deps)
request := createMCPRequest(reqParams)
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
require.NoError(t, err)
require.False(t, result.IsError)
label, ok := readIFC(t, result)
require.True(t, ok, "result meta should contain ifc key")
assert.Equal(t, "untrusted", label["integrity"])
assert.Equal(t, "public", label["confidentiality"])
})
t.Run("public repo with a draft advisory is private", func(t *testing.T) {
t.Parallel()
// Reviewer scenario: a draft advisory on a public repo is not
// world-readable, so the label must not be public.
deps := BaseDeps{
Client: mustNewGHClient(t, makeMockClient(false, []*github.SecurityAdvisory{publishedAdvisory, draftAdvisory})),
featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
}
handler := toolDef.Handler(deps)
request := createMCPRequest(reqParams)
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
require.NoError(t, err)
require.False(t, result.IsError)
label, ok := readIFC(t, result)
require.True(t, ok, "result meta should contain ifc key")
assert.Equal(t, "untrusted", label["integrity"])
assert.Equal(t, "private", label["confidentiality"], "draft advisory on public repo must be private")
})
t.Run("private repo is private", func(t *testing.T) {
t.Parallel()
deps := BaseDeps{
Client: mustNewGHClient(t, makeMockClient(true, []*github.SecurityAdvisory{publishedAdvisory})),
featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
}
handler := toolDef.Handler(deps)
request := createMCPRequest(reqParams)
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
require.NoError(t, err)
require.False(t, result.IsError)
label, ok := readIFC(t, result)
require.True(t, ok, "result meta should contain ifc key")
assert.Equal(t, "untrusted", label["integrity"])
assert.Equal(t, "private", label["confidentiality"])
})
}
func Test_ListOrgRepositorySecurityAdvisories(t *testing.T) {

@@ -376,0 +503,0 @@ // Verify tool definition once

@@ -11,2 +11,3 @@ package github

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/ifc"
"github.com/github/github-mcp-server/pkg/inventory"

@@ -207,3 +208,8 @@ "github.com/github/github-mcp-server/pkg/scopes"

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// Global advisories come from the world-readable GitHub Advisory
// Database (public) but contain externally authored prose
// (untrusted).
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelGlobalSecurityAdvisory())
return result, nil, nil
},

@@ -312,3 +318,13 @@ )

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// Repository advisories carry externally authored prose (untrusted).
// Confidentiality follows repo visibility, but draft/triage/closed
// advisories are not world-readable even on a public repo, so the
// result is only public when every returned advisory is published.
allPublished := allAdvisoriesPublished(advisories)
result = attachRepoVisibilityIFCLabel(ctx, deps, client, owner, repo, result,
func(isPrivate bool) ifc.SecurityLabel {
return ifc.LabelRepositorySecurityAdvisory(isPrivate, allPublished)
})
return result, nil, nil
},

@@ -370,3 +386,7 @@ )

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// A global advisory is world-readable (public) but externally
// authored (untrusted).
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelGlobalSecurityAdvisory())
return result, nil, nil
},

@@ -466,5 +486,26 @@ )

return utils.NewToolResultText(string(r)), nil, nil
result := utils.NewToolResultText(string(r))
// Org-wide advisory listings span the organization's repositories
// (including private ones) and are restricted to org members, so
// they are conservatively labeled private-untrusted (isPrivate=true,
// which forces private regardless of publication state).
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelRepositorySecurityAdvisory(true, false))
return result, nil, nil
},
)
}
// allAdvisoriesPublished reports whether every advisory in the slice is in the
// "published" state. Repository security advisories can also be in draft,
// triage, or closed states, none of which are world-readable even on a public
// repository. An empty slice is treated as published (true) since there is no
// non-public content to protect. Used to decide whether a repository advisory
// listing may carry a public confidentiality label.
func allAdvisoriesPublished(advisories []*github.SecurityAdvisory) bool {
for _, advisory := range advisories {
if advisory.GetState() != "published" {
return false
}
}
return true
}

@@ -114,3 +114,3 @@ package github

if UIAssetsAvailable() {
RegisterUIResources(ghServer)
RegisterUIResources(ghServer, cfg.ReadOnly)
}

@@ -117,0 +117,0 @@

@@ -183,2 +183,3 @@ package github

GetCommit(t),
GetFileBlame(t),
ListBranches(t),

@@ -185,0 +186,0 @@ ListTags(t),

@@ -5,2 +5,3 @@ package github

"context"
"slices"
"testing"

@@ -30,3 +31,3 @@

srv := mcp.NewServer(&mcp.Implementation{Name: "test", Version: "0.0.1"}, nil)
RegisterUIResources(srv)
RegisterUIResources(srv, false)

@@ -118,2 +119,45 @@ // Connect an in-memory client/server pair and read each advertised URI.

func TestRegisterUIResources_ReadOnlySkipsWriteResources(t *testing.T) {
t.Parallel()
srv := mcp.NewServer(&mcp.Implementation{Name: "test", Version: "0.0.1"}, nil)
RegisterUIResources(srv, true)
st, ct := mcp.NewInMemoryTransports()
type clientResult struct {
res *mcp.ListResourcesResult
err error
}
clientCh := make(chan clientResult, 1)
go func() {
client := mcp.NewClient(&mcp.Implementation{Name: "test-client"}, nil)
cs, err := client.Connect(context.Background(), ct, nil)
if err != nil {
clientCh <- clientResult{err: err}
return
}
defer func() { _ = cs.Close() }()
res, err := cs.ListResources(context.Background(), nil)
clientCh <- clientResult{res: res, err: err}
}()
ss, err := srv.Connect(context.Background(), st, nil)
require.NoError(t, err)
t.Cleanup(func() { _ = ss.Close() })
got := <-clientCh
require.NoError(t, got.err)
require.NotNil(t, got.res)
names := make([]string, 0, len(got.res.Resources))
for _, res := range got.res.Resources {
names = append(names, res.Name)
}
slices.Sort(names)
assert.Equal(t, []string{"get_me_ui"}, names)
}
// mustEmptyInventory builds an empty inventory for tests that only care about

@@ -120,0 +164,0 @@ // resources/prompts registered outside the inventory (such as the UI resources).

@@ -16,3 +16,3 @@ package github

// https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx
func RegisterUIResources(s *mcp.Server) {
func RegisterUIResources(s *mcp.Server, readOnly bool) {
// Register the get_me UI resource

@@ -50,2 +50,6 @@ s.AddResource(

if readOnly {
return
}
// Register the issue_write UI resource

@@ -52,0 +56,0 @@ s.AddResource(

@@ -97,6 +97,6 @@ package http

{
name: "insiders mode enables internal-only insiders flags",
name: "insiders mode does not auto-enable ifc labels",
flagName: github.FeatureFlagIFCLabels,
insidersMode: true,
wantEnabled: true,
wantEnabled: false,
},

@@ -103,0 +103,0 @@ {

@@ -52,1 +52,243 @@ package ifc

}
func TestLabelRepoMetadata(t *testing.T) {
t.Parallel()
t.Run("public repo metadata is trusted and public", func(t *testing.T) {
t.Parallel()
label := LabelRepoMetadata(false)
assert.Equal(t, IntegrityTrusted, label.Integrity)
assert.Equal(t, ConfidentialityPublic, label.Confidentiality)
})
t.Run("private repo metadata is trusted and private", func(t *testing.T) {
t.Parallel()
label := LabelRepoMetadata(true)
assert.Equal(t, IntegrityTrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
})
}
func TestLabelGetMe(t *testing.T) {
t.Parallel()
// get_me exposes private_gists/total_private_repos/owned_private_repos,
// which are not part of the public profile, so the result is trusted but
// private — never public.
label := LabelGetMe()
assert.Equal(t, IntegrityTrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
}
func TestLabelRelease(t *testing.T) {
t.Parallel()
t.Run("public repo with no draft is trusted and public", func(t *testing.T) {
t.Parallel()
label := LabelRelease(false, false)
assert.Equal(t, IntegrityTrusted, label.Integrity)
assert.Equal(t, ConfidentialityPublic, label.Confidentiality)
})
t.Run("public repo with a draft release is private", func(t *testing.T) {
t.Parallel()
// Draft releases are visible only to push-access users, so a draft on
// a public repo must not be labeled public.
label := LabelRelease(false, true)
assert.Equal(t, IntegrityTrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
})
t.Run("private repo is private regardless of draft", func(t *testing.T) {
t.Parallel()
for _, hasDraft := range []bool{false, true} {
label := LabelRelease(true, hasDraft)
assert.Equal(t, IntegrityTrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
}
})
}
func TestLabelCollaboratorRoster(t *testing.T) {
t.Parallel()
// A collaborator roster requires push access to list, so it is never
// world-readable — always trusted and private.
label := LabelCollaboratorRoster()
assert.Equal(t, IntegrityTrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
}
func TestLabelCommitContents(t *testing.T) {
t.Parallel()
t.Run("public repo commit content is untrusted and public", func(t *testing.T) {
t.Parallel()
label := LabelCommitContents(false)
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, ConfidentialityPublic, label.Confidentiality)
})
t.Run("private repo commit content is trusted and private", func(t *testing.T) {
t.Parallel()
label := LabelCommitContents(true)
assert.Equal(t, IntegrityTrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
})
}
func TestLabelActionsResult(t *testing.T) {
t.Parallel()
t.Run("public repo actions result is untrusted and public", func(t *testing.T) {
t.Parallel()
label := LabelActionsResult(false)
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, ConfidentialityPublic, label.Confidentiality)
})
t.Run("private repo actions result is untrusted and private", func(t *testing.T) {
t.Parallel()
label := LabelActionsResult(true)
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
})
}
func TestLabelSecurityAlert(t *testing.T) {
t.Parallel()
label := LabelSecurityAlert()
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality,
"security alerts are access-restricted regardless of repo visibility")
}
func TestLabelGlobalSecurityAdvisory(t *testing.T) {
t.Parallel()
label := LabelGlobalSecurityAdvisory()
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, ConfidentialityPublic, label.Confidentiality)
}
func TestLabelRepositorySecurityAdvisory(t *testing.T) {
t.Parallel()
t.Run("public repo with all published advisories is untrusted and public", func(t *testing.T) {
t.Parallel()
label := LabelRepositorySecurityAdvisory(false, true)
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, ConfidentialityPublic, label.Confidentiality)
})
t.Run("public repo with an unpublished advisory is untrusted and private", func(t *testing.T) {
t.Parallel()
// draft/triage/closed advisories are not world-readable even on a
// public repo, so confidentiality must be private.
label := LabelRepositorySecurityAdvisory(false, false)
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
})
t.Run("private repo advisory is untrusted and private", func(t *testing.T) {
t.Parallel()
label := LabelRepositorySecurityAdvisory(true, true)
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
})
t.Run("private repo with unpublished advisory is untrusted and private", func(t *testing.T) {
t.Parallel()
label := LabelRepositorySecurityAdvisory(true, false)
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
})
}
func TestLabelGist(t *testing.T) {
t.Parallel()
t.Run("public gist is untrusted and public", func(t *testing.T) {
t.Parallel()
label := LabelGist(true)
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, ConfidentialityPublic, label.Confidentiality)
})
t.Run("secret gist is untrusted and private", func(t *testing.T) {
t.Parallel()
label := LabelGist(false)
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
})
}
func TestLabelGistList(t *testing.T) {
t.Parallel()
tests := []struct {
name string
visibilities []bool // true == public
wantConfidential Confidentiality
}{
{
name: "empty result is treated as public",
wantConfidential: ConfidentialityPublic,
},
{
name: "all public gists stay public",
visibilities: []bool{true, true},
wantConfidential: ConfidentialityPublic,
},
{
name: "any secret gist flips to private",
visibilities: []bool{true, false, true},
wantConfidential: ConfidentialityPrivate,
},
{
name: "all secret gists stay private",
visibilities: []bool{false, false},
wantConfidential: ConfidentialityPrivate,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
label := LabelGistList(tc.visibilities)
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, tc.wantConfidential, label.Confidentiality)
})
}
}
func TestLabelProject(t *testing.T) {
t.Parallel()
t.Run("public project is untrusted and public", func(t *testing.T) {
t.Parallel()
label := LabelProject(true)
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, ConfidentialityPublic, label.Confidentiality)
})
t.Run("private project is untrusted and private", func(t *testing.T) {
t.Parallel()
label := LabelProject(false)
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
})
}
func TestLabelTeam(t *testing.T) {
t.Parallel()
label := LabelTeam()
assert.Equal(t, IntegrityTrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
}
func TestLabelNotificationDetails(t *testing.T) {
t.Parallel()
label := LabelNotificationDetails()
assert.Equal(t, IntegrityUntrusted, label.Integrity)
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
}

@@ -62,4 +62,14 @@ // Package ifc provides Information Flow Control labels for annotating MCP tool outputs.

// LabelGetMe returns the IFC label for the authenticated user's own profile
// (get_me).
//
// Integrity is trusted: this is GitHub-maintained data about the caller's own
// account, not attacker-authored content.
//
// Confidentiality is private. The result includes fields that are NOT part of
// the user's public profile — private_gists, total_private_repos, and
// owned_private_repos — which are visible only to the authenticated user. The
// result therefore must not be treated as world-readable.
func LabelGetMe() SecurityLabel {
return PublicTrusted()
return PrivateTrusted()
}

@@ -102,2 +112,12 @@

// leaked).
//
// Why a single joined label rather than one label per item: a tool result is
// delivered as one opaque payload (a single content block) and the IFC engine
// makes one allow/deny decision per flow at egress. Once the items share a
// buffer in the agent's context they can be copied anywhere together, so the
// only sound bound for the whole result is the meet of every item's label.
// Per-item labels would only become load-bearing if the enforcement engine
// could partition a result and route individual items to different sinks;
// until then they would invite unsafe declassification of a "public" item that
// actually arrived alongside private data.
func LabelSearchIssues(repoVisibilities []bool) SecurityLabel {

@@ -111,1 +131,204 @@ for _, isPrivate := range repoVisibilities {

}
// LabelRepoMetadata returns the IFC label for structural repository metadata
// that only collaborators with write access can define: labels, branches,
// tags, releases, issue types, issue field definitions, discussion
// categories, and the collaborator roster.
//
// Integrity is trusted because, unlike issue/PR/comment bodies, these
// artifacts cannot be authored by arbitrary outsiders — creating a branch,
// tag, release, or label requires push access, so the data reflects decisions
// made by the repository's trusted writers rather than attacker-controllable
// input.
//
// Confidentiality follows repository visibility: public repositories are
// universally readable; private repositories restrict the reader set (the
// opaque "private" marker, resolved client-side at egress time).
func LabelRepoMetadata(isPrivate bool) SecurityLabel {
if isPrivate {
return PrivateTrusted()
}
return PublicTrusted()
}
// LabelRelease returns the IFC label for repository releases (list_releases,
// get_latest_release, get_release_by_tag).
//
// Integrity is trusted: releases are published by collaborators with push
// access, not by arbitrary outsiders.
//
// Confidentiality is public only when the repository is public AND no returned
// release is a draft. Draft releases are visible only to users with push
// access — they are NOT world-readable even on a public repository — so a
// result containing one must be private. hasDraft reflects whether any release
// in the result is a draft; private repositories are always private regardless.
func LabelRelease(isPrivate bool, hasDraft bool) SecurityLabel {
if isPrivate || hasDraft {
return PrivateTrusted()
}
return PublicTrusted()
}
// LabelCollaboratorRoster returns the IFC label for a repository's collaborator
// list (list_repository_collaborators).
//
// Integrity is trusted: the roster is GitHub-maintained membership data, not
// attacker-authored content.
//
// Confidentiality is always private. Listing collaborators requires push
// access to the repository, so the roster is never world-readable — not even
// for public repositories. This mirrors LabelTeam: membership data is
// restricted regardless of the repository's own visibility.
func LabelCollaboratorRoster() SecurityLabel {
return PrivateTrusted()
}
// LabelCommitContents returns the IFC label for committed repository content
// reachable from the default branch and its history: commits, commit diffs,
// and the repository file tree.
//
// It shares the reasoning of LabelGetFileContents. In public repositories any
// outsider can land content via a pull request, so the integrity of committed
// content is untrusted. In private repositories only collaborators can push,
// so committed content is trusted. Confidentiality follows repository
// visibility.
func LabelCommitContents(isPrivate bool) SecurityLabel {
if isPrivate {
return PrivateTrusted()
}
return PublicUntrusted()
}
// LabelActionsResult returns the IFC label for GitHub Actions resources:
// workflow definitions, runs, jobs, artifacts, and job logs.
//
// Integrity is untrusted. Workflow logs echo arbitrary text produced during a
// run — including output derived from pull-request branches, dependency
// downloads, and other attacker-influenceable sources — so log and artifact
// content must be treated as low integrity. Workflow definitions are
// themselves editable through pull requests in public repositories.
//
// Confidentiality follows repository visibility.
func LabelActionsResult(isPrivate bool) SecurityLabel {
if isPrivate {
return PrivateUntrusted()
}
return PublicUntrusted()
}
// LabelSecurityAlert returns the IFC label for security findings: code
// scanning alerts, secret scanning alerts, and Dependabot alerts.
//
// Integrity is untrusted because alert payloads embed attacker-influenceable
// material — the offending code snippet, the matched secret string, or a
// vulnerable dependency's advisory text — none of which the agent should treat
// as a trustworthy instruction source.
//
// Confidentiality is always private. Security alerts are access-restricted by
// GitHub regardless of repository visibility (only users with a security role
// can read them), so the reader set is narrow even for public repositories.
// Secret scanning results additionally surface the secret material itself.
func LabelSecurityAlert() SecurityLabel {
return PrivateUntrusted()
}
// LabelGlobalSecurityAdvisory returns the IFC label for advisories served from
// the public GitHub Advisory Database (global advisories).
//
// The advisory database is world-readable, so confidentiality is public.
// Integrity is untrusted: advisory descriptions are externally authored prose
// and must not be treated as a trusted instruction source.
func LabelGlobalSecurityAdvisory() SecurityLabel {
return PublicUntrusted()
}
// LabelRepositorySecurityAdvisory returns the IFC label for repository- or
// organization-scoped security advisories.
//
// Integrity is untrusted (externally authored advisory prose).
//
// Confidentiality is public only when the repository is public AND every
// advisory in the result is in the "published" state. Repository security
// advisories also exist in draft, triage, and closed states; those are visible
// only to maintainers and are NOT world-readable even on a public repository.
// Treating any non-published advisory as private (allPublished == false)
// prevents misclassifying an unpublished advisory from a public repo as
// public-readable. Private repositories are always private regardless of state.
func LabelRepositorySecurityAdvisory(isPrivate bool, allPublished bool) SecurityLabel {
if isPrivate || !allPublished {
return PrivateUntrusted()
}
return PublicUntrusted()
}
// LabelGist returns the IFC label for gist content.
//
// Integrity is untrusted: gist contents are arbitrary user-authored text.
// Confidentiality derives from the gist's own visibility rather than any
// repository — public gists are universally readable, while secret gists are
// restricted to those who hold the gist URL (modeled with the opaque "private"
// marker).
func LabelGist(isPublic bool) SecurityLabel {
if isPublic {
return PublicUntrusted()
}
return PrivateUntrusted()
}
// LabelGistList returns the IFC label for a list of gists belonging to a user,
// joining the per-gist confidentiality across the result set.
//
// Integrity is untrusted (user-authored content). Confidentiality follows the
// IFC meet: if any gist in the result is secret the joined label is private;
// otherwise public. An empty result is treated as public-untrusted.
//
// See LabelSearchIssues for why list results carry a single joined label
// rather than one label per item.
func LabelGistList(gistVisibilities []bool) SecurityLabel {
for _, isPublic := range gistVisibilities {
if !isPublic {
return PrivateUntrusted()
}
}
return PublicUntrusted()
}
// LabelProject returns the IFC label for a GitHub Project (Projects v2) and its
// items, status updates, and field definitions.
//
// Integrity is untrusted: project titles, item content, and status update
// bodies are user-authored free text. Confidentiality derives from the
// project's own public flag — public projects are universally readable, while
// private projects restrict the reader set.
func LabelProject(isPublic bool) SecurityLabel {
if isPublic {
return PublicUntrusted()
}
return PrivateUntrusted()
}
// LabelTeam returns the IFC label for organization team membership data
// (get_teams, get_team_members).
//
// Integrity is trusted: team membership is maintained by GitHub and cannot be
// forged by outside contributors, so it is not an attacker-controllable
// instruction source.
//
// Confidentiality is private. Organization team rosters and the teams a user
// belongs to are visible only to members of the organization, not to the
// public, so the reader set is restricted (the opaque "private" marker).
func LabelTeam() SecurityLabel {
return PrivateTrusted()
}
// LabelNotificationDetails returns the IFC label for the subject of a single
// notification.
//
// Integrity is untrusted: a notification subject points at an issue, pull
// request, comment, or discussion whose content is user-authored and may carry
// attacker-controlled text. Confidentiality is private because notifications
// are delivered to a specific recipient and may reference private
// repositories; the result cannot be assumed to be publicly readable.
func LabelNotificationDetails() SecurityLabel {
return PrivateUntrusted()
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display