Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion pkg/github/context_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

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"
"github.com/github/github-mcp-server/pkg/scopes"
"github.com/github/github-mcp-server/pkg/translations"
Expand Down Expand Up @@ -103,7 +104,13 @@ func GetMe(t translations.TranslationHelperFunc) inventory.ServerTool {
},
}

return MarshalledTextResult(minimalUser), nil, nil
result := MarshalledTextResult(minimalUser)
if deps.GetFlags(ctx).InsidersMode {
result.Meta = mcp.Meta{
"ifc": ifc.LabelGetMe(),
Comment thread
JoannaaKL marked this conversation as resolved.
Outdated
}
}
return result, nil, nil
Comment on lines +107 to +114
},
)
}
Expand Down
60 changes: 60 additions & 0 deletions pkg/github/context_tools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,66 @@ func Test_GetMe(t *testing.T) {
}
}

func Test_GetMe_IFC_InsidersMode(t *testing.T) {
t.Parallel()

serverTool := GetMe(translations.NullTranslationHelper)

mockUser := &github.User{
Login: github.Ptr("testuser"),
HTMLURL: github.Ptr("https://github.com/testuser"),
CreatedAt: &github.Timestamp{Time: time.Now()},
}
mockedHTTPClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
GetUser: mockResponse(t, http.StatusOK, mockUser),
})

t.Run("insiders mode disabled omits ifc label from result meta", func(t *testing.T) {
deps := BaseDeps{
Client: github.NewClient(mockedHTTPClient),
Flags: FeatureFlags{InsidersMode: false},
}
handler := serverTool.Handler(deps)

request := createMCPRequest(map[string]any{})
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 insiders mode is disabled")
})

t.Run("insiders mode enabled includes ifc label in result meta", func(t *testing.T) {
deps := BaseDeps{
Client: github.NewClient(mockedHTTPClient),
Flags: FeatureFlags{InsidersMode: true},
}
handler := serverTool.Handler(deps)

request := createMCPRequest(map[string]any{})
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
require.NoError(t, err)
require.False(t, result.IsError)

require.NotNil(t, result.Meta, "result meta should be set when insiders mode is enabled")
ifcLabel, ok := result.Meta["ifc"]
require.True(t, ok, "result meta should contain ifc key")

ifcJSON, err := json.Marshal(ifcLabel)
require.NoError(t, err)

var ifcMap map[string]any
err = json.Unmarshal(ifcJSON, &ifcMap)
require.NoError(t, err)

assert.Equal(t, "high", ifcMap["integrity"])
confList, ok := ifcMap["confidentiality"].([]any)
require.True(t, ok, "confidentiality should be a list")
require.Len(t, confList, 1)
assert.Equal(t, "public", confList[0])
})
}

func Test_GetTeams(t *testing.T) {
t.Parallel()

Expand Down
6 changes: 6 additions & 0 deletions pkg/ifc/labelling_engine_readers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ifc

// LabelGetMe returns a label for get_me: trusted, universal readers.
func LabelGetMe() ReadersSecurityLabel {
Comment thread
JoannaaKL marked this conversation as resolved.
Outdated
return PublicTrusted()
}
221 changes: 221 additions & 0 deletions pkg/ifc/lattice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
// Package ifc implements Information Flow Control (IFC) lattices and security labels.
//
// This package provides the fundamental lattice structures used for IFC:
// - Confidentiality lattice (LOW, HIGH)
// - Integrity lattice (TRUSTED, UNTRUSTED)
package ifc

import "fmt"

type Lattice[T any] interface {
Leq(other T) bool // self <= other
Join(other T) T // least upper bound
Meet(other T) T // greatest lower bound
fmt.Stringer // String() string
}

type ConfidentialityLevel int

const (
ConfidentialityLow ConfidentialityLevel = iota
ConfidentialityHigh
)

func (l ConfidentialityLevel) String() string {
switch l {
case ConfidentialityLow:
return "LOW"
case ConfidentialityHigh:
return "HIGH"
default:
return fmt.Sprintf("ConfidentialityLevel(%d)", int(l))
}
}

type ConfidentialityLabel struct {
Level ConfidentialityLevel
}

func LowConfidentiality() ConfidentialityLabel {
return ConfidentialityLabel{Level: ConfidentialityLow}
}

func HighConfidentiality() ConfidentialityLabel {
return ConfidentialityLabel{Level: ConfidentialityHigh}
}

type SecurityLabel struct {
ProductLabel[ConfidentialityLabel, IntegrityLabel]
}

func NewSecurityLabel(c ConfidentialityLabel, i IntegrityLabel) SecurityLabel {
return SecurityLabel{
ProductLabel: ProductLabel[ConfidentialityLabel, IntegrityLabel]{
Left: c,
Right: i,
},
}
}

func (s SecurityLabel) Leq(other SecurityLabel) bool {
return s.ProductLabel.Leq(other.ProductLabel)
}

func (s SecurityLabel) Join(other SecurityLabel) SecurityLabel {
return SecurityLabel{
ProductLabel: s.ProductLabel.Join(other.ProductLabel),
}
}

func (s SecurityLabel) Meet(other SecurityLabel) SecurityLabel {
return SecurityLabel{
ProductLabel: s.ProductLabel.Meet(other.ProductLabel),
}
}

func (s SecurityLabel) String() string {
return s.ProductLabel.String()
}

var _ Lattice[SecurityLabel] = SecurityLabel{}

var LabelHighConfidentialityTrusted = NewSecurityLabel(HighConfidentiality(), Trusted())
var LabelPublicTrusted = NewSecurityLabel(LowConfidentiality(), Trusted())
var LabelUserUntrusted = NewSecurityLabel(HighConfidentiality(), Untrusted())
var LabelPublicUntrusted = NewSecurityLabel(LowConfidentiality(), Untrusted())

func (c ConfidentialityLabel) Leq(other ConfidentialityLabel) bool {
return int(c.Level) <= int(other.Level)
}

func (c ConfidentialityLabel) Join(other ConfidentialityLabel) ConfidentialityLabel {
if c.Leq(other) {
return other
}
return c
}

func (c ConfidentialityLabel) Meet(other ConfidentialityLabel) ConfidentialityLabel {
if c.Leq(other) {
return c
}
return other
}

func (c ConfidentialityLabel) String() string {
return c.Level.String()
}

var _ Lattice[ConfidentialityLabel] = ConfidentialityLabel{}

type IntegrityLevel int

const (
IntegrityTrusted IntegrityLevel = iota
IntegrityUntrusted
)

func (l IntegrityLevel) String() string {
switch l {
case IntegrityTrusted:
return "TRUSTED"
case IntegrityUntrusted:
return "UNTRUSTED"
default:
return fmt.Sprintf("IntegrityLevel(%d)", int(l))
}
}

type IntegrityLabel struct {
Level IntegrityLevel
}

// Trusted: content originating from the user, from trusted collaborators, or system prompts.
func Trusted() IntegrityLabel {
return IntegrityLabel{Level: IntegrityTrusted}
}

// Untrusted: content from untrusted users (e.g., no push access), or from external/public sources.
func Untrusted() IntegrityLabel {
return IntegrityLabel{Level: IntegrityUntrusted}
}

func (i IntegrityLabel) Leq(other IntegrityLabel) bool {
return int(i.Level) <= int(other.Level)
}

func (i IntegrityLabel) Join(other IntegrityLabel) IntegrityLabel {
if i.Leq(other) {
return other
}
return i
}

func (i IntegrityLabel) Meet(other IntegrityLabel) IntegrityLabel {
if i.Leq(other) {
return i
}
return other
}

func (i IntegrityLabel) String() string {
return i.Level.String()
}

var _ Lattice[IntegrityLabel] = IntegrityLabel{}

// ProductLabel is a product lattice of two lattices L1 × L2.
type ProductLabel[L1 Lattice[L1], L2 Lattice[L2]] struct {
Left L1
Right L2
}

func (p ProductLabel[L1, L2]) Leq(other ProductLabel[L1, L2]) bool {
return p.Left.Leq(other.Left) && p.Right.Leq(other.Right)
}

func (p ProductLabel[L1, L2]) Join(other ProductLabel[L1, L2]) ProductLabel[L1, L2] {
return ProductLabel[L1, L2]{
Left: p.Left.Join(other.Left),
Right: p.Right.Join(other.Right),
}
}

func (p ProductLabel[L1, L2]) Meet(other ProductLabel[L1, L2]) ProductLabel[L1, L2] {
return ProductLabel[L1, L2]{
Left: p.Left.Meet(other.Left),
Right: p.Right.Meet(other.Right),
}
}

func (p ProductLabel[L1, L2]) String() string {
return fmt.Sprintf("(%s, %s)", p.Left.String(), p.Right.String())
}

var ProductLabelLattice Lattice[ProductLabel[ConfidentialityLabel, IntegrityLabel]] = ProductLabel[ConfidentialityLabel, IntegrityLabel]{}

// InverseLattice inverts the order of an underlying lattice.
type InverseLattice[L Lattice[L]] struct {
Inner L
}

func (i InverseLattice[L]) Leq(other InverseLattice[L]) bool {
// Invert order: i <= other ⇔ other.Inner <= i.Inner
return other.Inner.Leq(i.Inner)
}

func (i InverseLattice[L]) Join(other InverseLattice[L]) InverseLattice[L] {
// join in inverse is meet in the original
return InverseLattice[L]{Inner: i.Inner.Meet(other.Inner)}
}

func (i InverseLattice[L]) Meet(other InverseLattice[L]) InverseLattice[L] {
// meet in inverse is join in the original
return InverseLattice[L]{Inner: i.Inner.Join(other.Inner)}
}

func (i InverseLattice[L]) String() string {
return fmt.Sprintf("Inverse(%s)", i.Inner.String())
}

var _ Lattice[InverseLattice[ConfidentialityLabel]] = InverseLattice[ConfidentialityLabel]{}
Loading
Loading