Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
31 changes: 31 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# The help target prints out all targets with their descriptions organized
# beneath their categories. The categories are represented by '##@' and the
# target descriptions by '##'. The awk commands is responsible for reading the
# entire set of makefiles included in this invocation, looking for lines of the
# file as xyz: ## something, and then pretty-format the target and help. Then,
# if there's a line with ##@ something, that gets pretty-printed as a category.
# More info on the usage of ANSI catalog characters for terminal formatting:
# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
# More info on the awk command:
# http://linuxcommand.org/lc3_adv_awk.php

# Check if OPA CLI is installed
OPA := $(shell command -v opa 2> /dev/null)
ifeq ($(OPA),)
$(error "opa CLI not found. Please install it: https://www.openpolicyagent.org/docs/latest/cli/")
endif

##@ Help
help: ## Display this concise help, ie only the porcelain target
@awk 'BEGIN {FS = ":.*##"; printf "\033[1mUsage\033[0m\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-30s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

help-all: ## Display all help items, ie including plumbing targets
@awk 'BEGIN {FS = ":.*#"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?#/ { printf " \033[36m%-25s\033[0m %s\n", $$1, $$2 } /^#@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

# Bundle the policies into a tarball for OCI registry
clean: # Cleanup build artifacts
@rm -rf dist/*

build: clean ## Build the policy bundle
@mkdir -p dist/
@go build -o dist/plugin main.go
53 changes: 43 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,37 @@ import (
"context"
"errors"
"fmt"
"slices"

policyManager "github.com/compliance-framework/agent/policy-manager"
"github.com/compliance-framework/agent/runner"
"github.com/compliance-framework/agent/runner/proto"
"github.com/google/go-github/v71/github"
"github.com/hashicorp/go-hclog"
goplugin "github.com/hashicorp/go-plugin"
"github.com/mitchellh/mapstructure"
"slices"
)

type PluginConfig struct {
Token string `mapstructure:"token"`
Organization *string `mapstructure:"organization"`
User *string `mapstructure:"user"`
Token string `mapstructure:"token"`
Organization *string `mapstructure:"organization"`
User *string `mapstructure:"user"`
SecurityTeamName *string `mapstructure:"security-team-name"`
}

type DependabotPlugin struct {
logger hclog.Logger
data map[string]interface{}
config *PluginConfig

config *PluginConfig
githubClient *github.Client
}

type DependabotData struct {
Alerts []*github.DependabotAlert
SecurityTeamMembers []*github.User
}

func (l *DependabotPlugin) Configure(req *proto.ConfigureRequest) (*proto.ConfigureResponse, error) {
//l.config = req.GetConfig()
config := &PluginConfig{}
mapstructure.Decode(req.GetConfig(), config)
l.config = config
Expand All @@ -42,7 +47,20 @@ func (l *DependabotPlugin) Configure(req *proto.ConfigureRequest) (*proto.Config
func (l *DependabotPlugin) Eval(req *proto.EvalRequest, apiHelper runner.ApiHelper) (*proto.EvalResponse, error) {
ctx := context.TODO()
repochan, errchan := l.FetchRepositories(ctx)

var securityTeamMembers []*github.User
if l.config.SecurityTeamName != nil && *l.config.SecurityTeamName != "" {
Copy link

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

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

Inconsistent nil checking. This code checks both SecurityTeamName and Organization for nil, but the FetchSecurityTeamMembers method at line 116 only checks SecurityTeamName for nil while still dereferencing Organization without validation.

Suggested change
if l.config.SecurityTeamName != nil && *l.config.SecurityTeamName != "" {
if l.config.SecurityTeamName != nil && *l.config.SecurityTeamName != "" && l.config.Organization != nil && *l.config.Organization != "" {

Copilot uses AI. Check for mistakes.
var err error
securityTeamMembers, err = l.FetchSecurityTeamMembers(ctx)
if err != nil {
return &proto.EvalResponse{
Status: proto.ExecutionStatus_FAILURE,
}, err
}
}

done := false

for !done {
select {
case err, ok := <-errchan:
Expand All @@ -66,7 +84,14 @@ func (l *DependabotPlugin) Eval(req *proto.EvalRequest, apiHelper runner.ApiHelp
}, err
}

evidences, err := l.EvaluatePolicies(ctx, repo, alerts, req)
data := &DependabotData{
Alerts: alerts,
}
if securityTeamMembers != nil {
data.SecurityTeamMembers = securityTeamMembers
}

evidences, err := l.EvaluatePolicies(ctx, repo, data, req)
if err != nil {
return &proto.EvalResponse{
Status: proto.ExecutionStatus_FAILURE,
Expand All @@ -87,6 +112,14 @@ func (l *DependabotPlugin) Eval(req *proto.EvalRequest, apiHelper runner.ApiHelp
}, nil
}

func (l *DependabotPlugin) FetchSecurityTeamMembers(ctx context.Context) ([]*github.User, error) {
members, _, err := l.githubClient.Teams.ListTeamMembersBySlug(ctx, *l.config.Organization, *l.config.SecurityTeamName, nil)
Copy link

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

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

Potential nil pointer dereference. The method dereferences l.config.Organization and l.config.SecurityTeamName without checking if they are nil, which could cause a panic if either field is not set.

Copilot uses AI. Check for mistakes.
if err != nil {
return nil, err
}
return members, nil
}

func (l *DependabotPlugin) FetchRepositoryDependabotAlerts(ctx context.Context, repo *github.Repository) ([]*github.DependabotAlert, error) {
alerts, _, err := l.githubClient.Dependabot.ListRepoAlerts(ctx, repo.GetOwner().GetLogin(), repo.GetName(), &github.ListAlertsOptions{
ListOptions: github.ListOptions{
Expand Down Expand Up @@ -140,7 +173,7 @@ func (l *DependabotPlugin) FetchRepositories(ctx context.Context) (<-chan *githu
return repositories, errs
}

func (l *DependabotPlugin) EvaluatePolicies(ctx context.Context, repo *github.Repository, alerts []*github.DependabotAlert, req *proto.EvalRequest) ([]*proto.Evidence, error) {
func (l *DependabotPlugin) EvaluatePolicies(ctx context.Context, repo *github.Repository, data *DependabotData, req *proto.EvalRequest) ([]*proto.Evidence, error) {
var accumulatedErrors error

activities := make([]*proto.Activity, 0)
Expand Down Expand Up @@ -260,7 +293,7 @@ func (l *DependabotPlugin) EvaluatePolicies(ctx context.Context, repo *github.Re
actors,
activities,
)
evidence, err := processor.GenerateResults(ctx, policyPath, alerts)
evidence, err := processor.GenerateResults(ctx, policyPath, data)
evidences = slices.Concat(evidences, evidence)
if err != nil {
accumulatedErrors = errors.Join(accumulatedErrors, err)
Expand Down
26 changes: 23 additions & 3 deletions main_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ package main

import (
"context"
"os"
"testing"

policy_manager "github.com/compliance-framework/agent/policy-manager"
"github.com/google/go-github/v71/github"
"github.com/hashicorp/go-hclog"
"os"
"testing"
)

func TestDependabotPlugin_Integration_FetchRepositories(t *testing.T) {

ctx := context.Background()

plugin := DependabotPlugin{
Expand Down Expand Up @@ -76,3 +76,23 @@ func TestDependabotPlugin_Integration_FetchRepositoryDependabotAlerts(t *testing

t.Log("Successfully collected alerts", len(alerts))
}

func TestDependabotPlugin_Integration_FetchTeamMembers(t *testing.T) {
ctx := context.Background()

plugin := DependabotPlugin{
logger: hclog.NewNullLogger(),
config: &PluginConfig{
Token: os.Getenv("GITHUB_TOKEN"),
Organization: policy_manager.Pointer("compliance-framework"),
SecurityTeamName: policy_manager.Pointer("security"),
},
githubClient: github.NewClient(nil).WithAuthToken(os.Getenv("GITHUB_TOKEN")),
}

members, err := plugin.FetchSecurityTeamMembers(ctx)
if err != nil {
t.Error(err)
}
t.Log("Successfully collected security team members", members)
}