Skip to content

Commit 782c706

Browse files
committed
fix: correct ifc private labels
1 parent b879ca2 commit 782c706

6 files changed

Lines changed: 55 additions & 31 deletions

File tree

pkg/github/issues_test.go

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

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

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

@@ -1106,7 +1106,7 @@ func Test_SearchIssues_IFC_InsidersMode(t *testing.T) {
11061106
assert.Equal(t, "public", ifcMap["confidentiality"])
11071107
})
11081108

1109-
t.Run("insiders mode mixed public and private emits private untrusted", func(t *testing.T) {
1109+
t.Run("insiders mode mixed public and private emits private trusted", func(t *testing.T) {
11101110
searchResult := &github.IssuesSearchResult{Issues: []*github.Issue{
11111111
makeIssue("octocat", "private-repo", 1),
11121112
makeIssue("octocat", "public-repo", 2),
@@ -1127,7 +1127,7 @@ func Test_SearchIssues_IFC_InsidersMode(t *testing.T) {
11271127

11281128
require.NotNil(t, result.Meta)
11291129
ifcMap := unmarshalIFC(t, result.Meta["ifc"])
1130-
assert.Equal(t, "untrusted", ifcMap["integrity"])
1130+
assert.Equal(t, "trusted", ifcMap["integrity"])
11311131
assert.Equal(t, "private", ifcMap["confidentiality"])
11321132
})
11331133

@@ -2719,7 +2719,7 @@ func Test_ListIssues_IFC_InsidersMode(t *testing.T) {
27192719
assert.Equal(t, "public", ifcMap["confidentiality"])
27202720
})
27212721

2722-
t.Run("insiders mode enabled on private repo emits private untrusted label", func(t *testing.T) {
2722+
t.Run("insiders mode enabled on private repo emits private trusted label", func(t *testing.T) {
27232723
matcher := githubv4mock.NewQueryMatcher(query, vars, makeResponse(true))
27242724
gqlClient := githubv4.NewClient(githubv4mock.NewMockedHTTPClient(matcher))
27252725
deps := BaseDeps{
@@ -2742,7 +2742,7 @@ func Test_ListIssues_IFC_InsidersMode(t *testing.T) {
27422742
var ifcMap map[string]any
27432743
require.NoError(t, json.Unmarshal(ifcJSON, &ifcMap))
27442744

2745-
assert.Equal(t, "untrusted", ifcMap["integrity"])
2745+
assert.Equal(t, "trusted", ifcMap["integrity"])
27462746
assert.Equal(t, "private", ifcMap["confidentiality"])
27472747
})
27482748
}

pkg/github/repositories.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2121,9 +2121,9 @@ 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; any private repo makes
2125+
// the joined result private-trusted). Visibility is read directly
2126+
// from the response, so no extra API call is needed.
21272127
visibilities := make([]bool, 0, len(minimalRepos))
21282128
for _, mr := range minimalRepos {
21292129
visibilities = append(visibilities, mr.Private)

pkg/github/search.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@ func SearchRepositories(t translations.TranslationHelperFunc) inventory.ServerTo
173173
// every matched repository and attaches the result to callResult when IFC
174174
// labels are enabled. Visibility is read directly from the search response —
175175
// no extra API call. The join math is shared with search_issues via
176-
// ifc.LabelSearchIssues: integrity is always untrusted; confidentiality is
177-
// private if any matched repository is private, otherwise public. The
176+
// ifc.LabelSearchIssues: public-only results stay public-untrusted, while any
177+
// private matched repository makes the joined result private-trusted. The
178178
// feature-flag check is centralized here (mirroring the attach* helpers in
179179
// ifc_labels.go) so the handler can call this unconditionally.
180180
func attachSearchRepositoriesIFCLabel(ctx context.Context, deps ToolDependencies, repos []*github.Repository, callResult *mcp.CallToolResult) {
@@ -302,9 +302,9 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool {
302302
}
303303

304304
callResult := utils.NewToolResultText(string(r))
305-
// Code search spans repositories and exposes file contents
306-
// (untrusted). Confidentiality is the IFC join across every matched
307-
// repository's visibility, read directly from the search response.
305+
// Code search spans repositories; the IFC label is the join across
306+
// every matched repository's visibility, read directly from the
307+
// search response.
308308
visibilities := make([]bool, 0, len(result.CodeResults))
309309
for _, code := range result.CodeResults {
310310
if code.Repository != nil {
@@ -593,9 +593,9 @@ func SearchCommits(t translations.TranslationHelperFunc) inventory.ServerTool {
593593
}
594594

595595
callResult := utils.NewToolResultText(string(r))
596-
// Commit search spans repositories and exposes commit content
597-
// (untrusted). Confidentiality is the IFC join across every matched
598-
// repository's visibility, read directly from the search response.
596+
// Commit search spans repositories; the IFC label is the join across
597+
// every matched repository's visibility, read directly from the
598+
// search response.
599599
visibilities := make([]bool, 0, len(result.Commits))
600600
for _, commit := range result.Commits {
601601
if commit.Repository != nil {

pkg/github/search_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ func Test_SearchRepositories_IFC_InsidersMode(t *testing.T) {
238238
assert.Equal(t, "public", ifcMap["confidentiality"])
239239
})
240240

241-
t.Run("insiders mode any private match emits private untrusted", func(t *testing.T) {
241+
t.Run("insiders mode any private match emits private trusted", func(t *testing.T) {
242242
deps := BaseDeps{
243243
Client: mustNewGHClient(t, makeMockClient([]repoFixture{
244244
{owner: "octocat", name: "private-repo", isPrivate: true},
@@ -255,7 +255,7 @@ func Test_SearchRepositories_IFC_InsidersMode(t *testing.T) {
255255

256256
require.NotNil(t, result.Meta)
257257
ifcMap := unmarshalIFC(t, result.Meta["ifc"])
258-
assert.Equal(t, "untrusted", ifcMap["integrity"])
258+
assert.Equal(t, "trusted", ifcMap["integrity"])
259259
assert.Equal(t, "private", ifcMap["confidentiality"])
260260
})
261261

pkg/ifc/ifc.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,11 @@ func LabelGetMe() SecurityLabel {
7676
// LabelListIssues returns the IFC label for a list_issues result.
7777
// Public repositories are universally readable; private repositories are
7878
// restricted to their collaborators (resolved client-side from the marker).
79-
// Issue contents are attacker-controllable, so integrity is always untrusted.
79+
// Public repository issue contents are attacker-controllable, while private
80+
// repository issues are treated as trusted collaborator-authored data.
8081
func LabelListIssues(isPrivate bool) SecurityLabel {
8182
if isPrivate {
82-
return PrivateUntrusted()
83+
return PrivateTrusted()
8384
}
8485
return PublicUntrusted()
8586
}
@@ -99,12 +100,11 @@ func LabelGetFileContents(isPrivate bool) SecurityLabel {
99100
// result, joining per-repository labels across all matched repositories.
100101
// Used by both search_issues and search_repositories.
101102
//
102-
// Integrity is always untrusted because results expose user-authored content.
103-
//
104-
// Confidentiality follows the IFC meet (greatest lower bound): if any matched
105-
// repository is private the joined label is private; otherwise public. The
106-
// reader set is opaque (the "private" marker); the client engine resolves
107-
// concrete readers on demand at egress decision time.
103+
// Public-only results are untrusted and public. If any matched repository is
104+
// private, the joined label is trusted and private because private repository
105+
// content is treated as trusted collaborator-authored data. The reader set is
106+
// opaque (the "private" marker); the client engine resolves concrete readers
107+
// on demand at egress decision time.
108108
//
109109
// An empty result set is treated as public-untrusted (no repository data is
110110
// leaked).
@@ -121,7 +121,7 @@ func LabelGetFileContents(isPrivate bool) SecurityLabel {
121121
func LabelSearchIssues(repoVisibilities []bool) SecurityLabel {
122122
for _, isPrivate := range repoVisibilities {
123123
if isPrivate {
124-
return PrivateUntrusted()
124+
return PrivateTrusted()
125125
}
126126
}
127127
return PublicUntrusted()

pkg/ifc/ifc_test.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,60 @@ import (
66
"github.com/stretchr/testify/assert"
77
)
88

9+
func TestLabelListIssues(t *testing.T) {
10+
t.Parallel()
11+
12+
t.Run("public repo issues are untrusted and public", func(t *testing.T) {
13+
t.Parallel()
14+
label := LabelListIssues(false)
15+
assert.Equal(t, IntegrityUntrusted, label.Integrity)
16+
assert.Equal(t, ConfidentialityPublic, label.Confidentiality)
17+
})
18+
19+
t.Run("private repo issues are trusted and private", func(t *testing.T) {
20+
t.Parallel()
21+
label := LabelListIssues(true)
22+
assert.Equal(t, IntegrityTrusted, label.Integrity)
23+
assert.Equal(t, ConfidentialityPrivate, label.Confidentiality)
24+
})
25+
}
26+
927
func TestLabelSearchIssues(t *testing.T) {
1028
t.Parallel()
1129

1230
tests := []struct {
1331
name string
1432
visibilities []bool
33+
wantIntegrity Integrity
1534
wantConfidential Confidentiality
1635
}{
1736
{
1837
name: "empty result is treated as public",
38+
wantIntegrity: IntegrityUntrusted,
1939
wantConfidential: ConfidentialityPublic,
2040
},
2141
{
2242
name: "single public repo",
2343
visibilities: []bool{false},
44+
wantIntegrity: IntegrityUntrusted,
2445
wantConfidential: ConfidentialityPublic,
2546
},
2647
{
2748
name: "all public repos stay public",
2849
visibilities: []bool{false, false, false},
50+
wantIntegrity: IntegrityUntrusted,
2951
wantConfidential: ConfidentialityPublic,
3052
},
3153
{
32-
name: "any private match flips to private",
54+
name: "any private match flips to trusted private",
3355
visibilities: []bool{false, true, false},
56+
wantIntegrity: IntegrityTrusted,
3457
wantConfidential: ConfidentialityPrivate,
3558
},
3659
{
37-
name: "all private repos stay private",
60+
name: "all private repos stay trusted private",
3861
visibilities: []bool{true, true},
62+
wantIntegrity: IntegrityTrusted,
3963
wantConfidential: ConfidentialityPrivate,
4064
},
4165
}
@@ -44,7 +68,7 @@ func TestLabelSearchIssues(t *testing.T) {
4468
t.Run(tc.name, func(t *testing.T) {
4569
t.Parallel()
4670
label := LabelSearchIssues(tc.visibilities)
47-
assert.Equal(t, IntegrityUntrusted, label.Integrity)
71+
assert.Equal(t, tc.wantIntegrity, label.Integrity)
4872
assert.Equal(t, tc.wantConfidential, label.Confidentiality)
4973
})
5074
}

0 commit comments

Comments
 (0)