Skip to content

Commit 301f5b5

Browse files
RossTarrantCopilot
andcommitted
fix: correct IFC labels
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6586b84 commit 301f5b5

14 files changed

Lines changed: 140 additions & 123 deletions

Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM node:26-alpine@sha256:3ad34ca6292aec4a91d8ddeb9229e29d9c2f689efd0dd242860889ac71842eba AS ui-build
1+
FROM node:26-alpine@sha256:144769ec3f32e8ee36b3cfde91e82bee25d9367b20f31a151f3f7eea3a2a8541 AS ui-build
22
WORKDIR /app
33
COPY ui/package*.json ./ui/
44
RUN cd ui && npm ci
@@ -7,7 +7,7 @@ COPY ui/ ./ui/
77
RUN mkdir -p ./pkg/github/ui_dist && \
88
cd ui && npm run build
99

10-
FROM golang:1.25.11-alpine@sha256:8d95af53d0d58e1759ddb4028285d9b1239067e4fbf4f544618cad0f60fbc354 AS build
10+
FROM golang:1.25.11-alpine@sha256:cd2fb3559df6e13bc93b7f0734a4eabe1d21e7b64eec211ed90784f00a17a56a AS build
1111
ARG VERSION="dev"
1212

1313
# Set the working directory
@@ -30,7 +30,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \
3030
-o /bin/github-mcp-server ./cmd/github-mcp-server
3131

3232
# Make a stage to run the app
33-
FROM gcr.io/distroless/base-debian12@sha256:e7e678c88c59e70e105a46549bb3fbfb3d732ee3b4afd3a19fdab2e15afaa6b3
33+
FROM gcr.io/distroless/base-debian12@sha256:58695f439f772a00009c8f6be4c183f824c1f556d74b313c30900f167e4772f8
3434

3535
# Add required MCP server annotation
3636
LABEL io.modelcontextprotocol.server.name="io.github.github/github-mcp-server"

pkg/errors/error_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/http"
77
"testing"
88
"time"
9+
910
"github.com/google/go-github/v87/github"
1011
"github.com/modelcontextprotocol/go-sdk/mcp"
1112
"github.com/stretchr/testify/assert"
@@ -687,4 +688,3 @@ func TestNewGitHubAPIErrorResponse_RateLimits(t *testing.T) {
687688
assert.Contains(t, text, "validation failed")
688689
})
689690
}
690-

pkg/github/discussions.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ func ListDiscussions(t translations.TranslationHelperFunc) inventory.ServerTool
276276
result := utils.NewToolResultText(string(out))
277277
// Discussion content is user-authored (untrusted); confidentiality
278278
// follows repo visibility.
279-
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, owner, repo, result, ifc.LabelListIssues)
279+
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, owner, repo, result, ifc.LabelRepoUserContent)
280280
return result, nil, nil
281281
},
282282
)
@@ -384,7 +384,7 @@ func GetDiscussion(t translations.TranslationHelperFunc) inventory.ServerTool {
384384
result := utils.NewToolResultText(string(out))
385385
// Discussion content is user-authored (untrusted); confidentiality
386386
// follows repo visibility.
387-
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, params.Owner, params.Repo, result, ifc.LabelListIssues)
387+
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, params.Owner, params.Repo, result, ifc.LabelRepoUserContent)
388388
return result, nil, nil
389389
},
390390
)
@@ -592,7 +592,7 @@ func GetDiscussionComments(t translations.TranslationHelperFunc) inventory.Serve
592592
result := utils.NewToolResultText(string(out))
593593
// Discussion comments are user-authored (untrusted); confidentiality
594594
// follows repo visibility.
595-
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, params.Owner, params.Repo, result, ifc.LabelListIssues)
595+
result = attachRepoVisibilityIFCLabelLazy(ctx, deps, params.Owner, params.Repo, result, ifc.LabelRepoUserContent)
596596
return result, nil, nil
597597
},
598598
)

pkg/github/gists.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,7 @@ func ListGists(t translations.TranslationHelperFunc) inventory.ServerTool {
101101
}
102102

103103
result := utils.NewToolResultText(string(r))
104-
// Gist contents are user-authored (untrusted); confidentiality is
105-
// the IFC join of each gist's own public/secret flag.
106-
visibilities := make([]bool, 0, len(gists))
107-
for _, g := range gists {
108-
visibilities = append(visibilities, g.GetPublic())
109-
}
110-
result = attachJoinedIFCLabel(ctx, deps, result, visibilities, ifc.LabelGistList)
104+
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelGistList())
111105
return result, nil, nil
112106
},
113107
)
@@ -167,9 +161,7 @@ func GetGist(t translations.TranslationHelperFunc) inventory.ServerTool {
167161
}
168162

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

pkg/github/ifc_labels.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,11 @@ func attachRepoVisibilityIFCLabelLazy(
9797
}
9898

9999
// attachJoinedIFCLabel attaches an IFC label computed by joining a set of
100-
// per-item visibilities (true == private for repositories, true == public for
101-
// gists) when IFC labels are enabled. joinFn is the lattice join for the
102-
// relevant item kind (e.g. ifc.LabelSearchIssues or ifc.LabelGistList). The
103-
// visibility slice is cheap to build from an already-fetched response, so
104-
// callers may construct it unconditionally and let this helper own the
105-
// feature-flag gate.
100+
// per-item visibilities (true == private) when IFC labels are enabled. joinFn
101+
// is the lattice join for the relevant item kind (e.g. ifc.LabelSearchIssues or
102+
// ifc.LabelGistList). The visibility slice is cheap to build from an
103+
// already-fetched response, so callers may construct it unconditionally and let
104+
// this helper own the feature-flag gate.
106105
func attachJoinedIFCLabel(
107106
ctx context.Context,
108107
deps ToolDependencies,

pkg/github/issues.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,7 @@ Options are:
804804
// attachIFC adds the IFC label to a successful tool result when
805805
// IFC labels are enabled. If the visibility lookup fails the
806806
// label is omitted rather than misclassifying the result.
807-
attachIFC := newRepoVisibilityIFCLabeler(ctx, deps, client, owner, repo, ifc.LabelListIssues)
807+
attachIFC := newRepoVisibilityIFCLabeler(ctx, deps, client, owner, repo, ifc.LabelRepoUserContent)
808808

809809
switch method {
810810
case "get":

pkg/github/issues_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ func Test_IssueRead_IFC_InsidersMode(t *testing.T) {
356356
assert.Equal(t, "public", ifcMap["confidentiality"])
357357
})
358358

359-
t.Run("insiders mode enabled on private repo with get_comments emits private untrusted", func(t *testing.T) {
359+
t.Run("insiders mode enabled on private repo with get_comments emits private trusted", func(t *testing.T) {
360360
deps := BaseDeps{
361361
Client: mustNewGHClient(t, makeMockClient(true, 0)),
362362
featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
@@ -370,7 +370,7 @@ func Test_IssueRead_IFC_InsidersMode(t *testing.T) {
370370

371371
require.NotNil(t, result.Meta)
372372
ifcMap := unmarshalIFC(t, result.Meta["ifc"])
373-
assert.Equal(t, "untrusted", ifcMap["integrity"])
373+
assert.Equal(t, "trusted", ifcMap["integrity"])
374374
assert.Equal(t, "private", ifcMap["confidentiality"])
375375
})
376376

@@ -2852,7 +2852,7 @@ func Test_ListIssues_IFC_InsidersMode(t *testing.T) {
28522852
assert.Equal(t, "public", ifcMap["confidentiality"])
28532853
})
28542854

2855-
t.Run("insiders mode enabled on private repo emits private untrusted label", func(t *testing.T) {
2855+
t.Run("insiders mode enabled on private repo emits private trusted label", func(t *testing.T) {
28562856
matcher := githubv4mock.NewQueryMatcher(query, vars, makeResponse(true))
28572857
gqlClient := githubv4.NewClient(githubv4mock.NewMockedHTTPClient(matcher))
28582858
deps := BaseDeps{
@@ -2875,7 +2875,7 @@ func Test_ListIssues_IFC_InsidersMode(t *testing.T) {
28752875
var ifcMap map[string]any
28762876
require.NoError(t, json.Unmarshal(ifcJSON, &ifcMap))
28772877

2878-
assert.Equal(t, "untrusted", ifcMap["integrity"])
2878+
assert.Equal(t, "trusted", ifcMap["integrity"])
28792879
assert.Equal(t, "private", ifcMap["confidentiality"])
28802880
})
28812881
}

pkg/github/projects.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,9 @@ Use this tool to list projects for a user or organization, or list project field
232232
// labels are enabled. Project titles, item content, field
233233
// definitions, and status updates are user-authored free text
234234
// (untrusted); confidentiality is conservatively private since the
235-
// project's public flag is not available across every sub-result.
235+
// project's privacy is not available across every sub-result.
236236
attachIFC := func(r *mcp.CallToolResult) *mcp.CallToolResult {
237-
return attachStaticIFCLabel(ctx, deps, r, ifc.LabelProject(false))
237+
return attachStaticIFCLabel(ctx, deps, r, ifc.LabelProject(true))
238238
}
239239

240240
switch method {
@@ -349,9 +349,9 @@ Use this tool to get details about individual projects, project fields, and proj
349349
// attachIFC adds the IFC label to a successful result when IFC
350350
// labels are enabled. Project data is user-authored free text
351351
// (untrusted); confidentiality is conservatively private since the
352-
// project's public flag is not available across every sub-result.
352+
// project's privacy is not available across every sub-result.
353353
attachIFC := func(r *mcp.CallToolResult) *mcp.CallToolResult {
354-
return attachStaticIFCLabel(ctx, deps, r, ifc.LabelProject(false))
354+
return attachStaticIFCLabel(ctx, deps, r, ifc.LabelProject(true))
355355
}
356356

357357
// Handle get_project_status_update early — it only needs status_update_id

pkg/github/pullrequests.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ Possible options:
115115
// visibility lookup fails the label is omitted rather than
116116
// misclassifying the result.
117117
attachIFC := func(r *mcp.CallToolResult) *mcp.CallToolResult {
118-
return attachRepoVisibilityIFCLabel(ctx, deps, client, owner, repo, r, ifc.LabelListIssues)
118+
return attachRepoVisibilityIFCLabel(ctx, deps, client, owner, repo, r, ifc.LabelRepoUserContent)
119119
}
120120

121121
switch method {
@@ -1339,7 +1339,7 @@ func ListPullRequests(t translations.TranslationHelperFunc) inventory.ServerTool
13391339
result := utils.NewToolResultText(string(r))
13401340
// Pull request titles/bodies are user-authored (untrusted);
13411341
// confidentiality follows repo visibility.
1342-
result = attachRepoVisibilityIFCLabel(ctx, deps, client, owner, repo, result, ifc.LabelListIssues)
1342+
result = attachRepoVisibilityIFCLabel(ctx, deps, client, owner, repo, result, ifc.LabelRepoUserContent)
13431343
return result, nil, nil
13441344
})
13451345
}

pkg/github/repositories.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2121,9 +2121,10 @@ func ListStarredRepositories(t translations.TranslationHelperFunc) inventory.Ser
21212121
result := utils.NewToolResultText(string(r))
21222122
// A starred-repository listing exposes repository data across many
21232123
// repos; reuse the multi-repo join shared with search_repositories
2124-
// (untrusted integrity; confidentiality private if any matched repo
2125-
// is private). Visibility is read directly from the response, so no
2126-
// extra API call is needed.
2124+
// (public-only results stay public-untrusted, mixed-visibility
2125+
// results become private-untrusted, all-private results become
2126+
// private-trusted). Visibility is read directly from the response,
2127+
// so no extra API call is needed.
21272128
visibilities := make([]bool, 0, len(minimalRepos))
21282129
for _, mr := range minimalRepos {
21292130
visibilities = append(visibilities, mr.Private)

0 commit comments

Comments
 (0)