Skip to content

feat(security): enhance security features #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

tzervas
Copy link
Owner

@tzervas tzervas commented Jul 10, 2025

This PR adds comprehensive security enhancements to the MCP server:

Security Test Suite

  • Add tests for permission boundaries
  • Add security headers validation
  • Implement toolset isolation testing
  • Add health check functionality tests

CI/CD Security Checks

  • Add container scanning with Trivy
  • Implement dependency scanning with govulncheck
  • Add SAST scanning with gosec
  • Configure gitleaks for secrets detection

Configuration

  • Add .gitleaks.toml for secrets scanning configuration
  • Configure security scanning in GitHub Actions workflow

Other Improvements

  • Enhance existing CodeQL workflow
  • Add comprehensive security validation
  • Implement daily security scans

This PR helps ensure the security of the MCP server by adding comprehensive testing and automated security scanning.

Summary by Sourcery

Add comprehensive security enhancements by configuring default toolset permissions in the Docker image, introducing a robust CI security scanning pipeline, and adding a suite of security tests for server configuration, HTTP headers, rate limiting, toolset isolation, request validation, and audit logging.

Build:

  • Set default GitHub toolset permissions via environment variable in Dockerfile

CI:

  • Rename and expand code scanning workflow triggers to include daily schedules, main branch pushes, and pull requests
  • Introduce a security-scan job in GitHub Actions running Trivy, govulncheck, gosec, and gitleaks with SARIF uploads

Tests:

  • Add security test suite covering default config, HTTP headers, rate limiting, toolset isolation, request validation, and audit logging

Chores:

  • Add .gitleaks.toml for custom secrets scanning configuration

Documentation Bot added 2 commits July 9, 2025 19:46
- Add default permission levels (read, write, admin) for toolsets
- Map required GitHub scopes to each toolset
- Configure default access levels for all available toolsets
- Add comprehensive security test suite
- Add container and dependency scanning
- Configure gitleaks for secrets detection
- Enhance CI/CD security checks
Copy link

sourcery-ai bot commented Jul 10, 2025

Reviewer's Guide

The PR implements a robust security framework by overhauling the GitHub Actions workflow with automated container, dependency, SAST and secrets scans, injecting default toolset permissions in the Docker image, introducing an extensive security middleware test suite, and establishing custom rules for secret detection.

ER diagram for toolset permissions in Docker image

erDiagram
  TOOLSET_PERMISSIONS {
    string issues_level
    string issues_scopes
    string pullrequests_level
    string pullrequests_scopes
    string discussions_level
    string discussions_scopes
    string actions_level
    string actions_scopes
    string dependabot_level
    string dependabot_scopes
    string code_scanning_level
    string code_scanning_scopes
    string secret_scanning_level
    string secret_scanning_scopes
  }
Loading

Class diagram for security test suite additions

classDiagram
  class SecurityTestSuite {
    +TestPermissionBoundaries()
    +TestSecurityHeadersValidation()
    +TestToolsetIsolation()
    +TestHealthCheckFunctionality()
  }
Loading

File-Level Changes

Change Details Files
Enhanced CI/CD security scanning pipeline
  • Renamed and extended workflow triggers to include scheduled and branch filters
  • Added a dedicated ‘security-scan’ job running Trivy, govulncheck, gosec and gitleaks
  • Streamed SARIF outputs for each scanner and configured upload steps
  • Adjusted job permissions for content read and security-events write
.github/workflows/code-scanning.yml
Injected default toolset permissions in Docker image
  • Defined GITHUB_TOOLSET_PERMISSIONS JSON env var with scoped permissions levels
  • Set up default read/write/admin scopes for issues, PRs, actions and scanning
Dockerfile
Added comprehensive security middleware test suite
  • Implemented tests for default config values and rate limiting behavior
  • Validated security headers and request validation middleware
  • Verified toolset isolation contexts and audit logging functionality
pkg/security/security_test.go
Configured custom rules for secret scanning
  • Created .gitleaks.toml with patterns for GitHub tokens, OAuth, AWS keys, and private keys
  • Set allowlist for test, mock, example and sample file paths
.gitleaks.toml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @tzervas - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `.github/workflows/code-scanning.yml:63` </location>
<code_context>
+          govulncheck ./...
+
+      # SAST Scanning
+      - name: Run gosec
+        uses: securego/gosec@master
+        with:
+          args: '-no-fail -fmt sarif -out gosec-results.sarif ./...'
</code_context>

<issue_to_address>
Pin the gosec action to a stable release instead of 'master'.

Using 'master' may introduce breaking changes or vulnerabilities. Please use a specific version tag for stability and security.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
      - name: Run gosec
        uses: securego/gosec@master
        with:
          args: '-no-fail -fmt sarif -out gosec-results.sarif ./...'
=======
      - name: Run gosec
        uses: securego/gosec@v2.19.0
        with:
          args: '-no-fail -fmt sarif -out gosec-results.sarif ./...'
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `.github/workflows/code-scanning.yml:76` </location>
<code_context>
+          category: gosec
+
+      # Secrets Scanning
+      - name: Run gitleaks
+        uses: gitleaks/gitleaks-action@v2
+        with:
+          config-path: .gitleaks.toml
</code_context>

<issue_to_address>
Consider pinning the gitleaks action to a specific commit or release.

Pinning to a specific release or commit hash helps prevent unexpected changes in the action from impacting your workflow.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
      - name: Run gitleaks
        uses: gitleaks/gitleaks-action@v2
        with:
          config-path: .gitleaks.toml
          format: sarif
          report-path: gitleaks-report.sarif
=======
      - name: Run gitleaks
        uses: gitleaks/gitleaks-action@v2.19.0
        with:
          config-path: .gitleaks.toml
          format: sarif
          report-path: gitleaks-report.sarif
>>>>>>> REPLACE

</suggested_fix>

### Comment 3
<location> `pkg/security/security_test.go:50` </location>
<code_context>
+	}
+}
+
+func TestRateLimiting(t *testing.T) {
+	logger := logrus.New()
+	cfg := DefaultConfig()
</code_context>

<issue_to_address>
Rate limiting tests do not cover reset or recovery after limit is exceeded.

Please add a test to verify the rate limiter allows requests again after the reset period, confirming correct recovery behavior.

Suggested implementation:

```golang
func TestRateLimiting(t *testing.T) {
	logger := logrus.New()
	cfg := DefaultConfig()

```

```golang
}

func TestRateLimiter_ResetsAfterPeriod(t *testing.T) {
	logger := logrus.New()
	cfg := DefaultConfig()
	// Set a short window for testing
	cfg.RateLimitWindow = 100 * time.Millisecond
	cfg.RateLimitRequests = 2

	middleware := NewSecurityMiddleware(cfg, logger)

	handler := middleware.RateLimit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	}))

	req := httptest.NewRequest("GET", "/", nil)
	rec := httptest.NewRecorder()

	// First request should pass
	handler.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)

	// Second request should pass
	rec = httptest.NewRecorder()
	handler.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)

	// Third request should be rate limited
	rec = httptest.NewRecorder()
	handler.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusTooManyRequests, rec.Code)

	// Wait for the window to reset
	time.Sleep(cfg.RateLimitWindow + 10*time.Millisecond)

	// After reset, request should pass again
	rec = httptest.NewRecorder()
	handler.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
}

```
</issue_to_address>

### Comment 4
<location> `pkg/security/security_test.go:14` </location>
<code_context>
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSecurityConfig(t *testing.T) {
+	t.Run("DefaultConfig", func(t *testing.T) {
+		cfg := DefaultConfig()
</code_context>

<issue_to_address>
Test for SecurityConfig covers only default values.

Please add tests for non-default configurations, including toggling security features, to verify correct behavior in all supported modes.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
func TestSecurityConfig(t *testing.T) {
	t.Run("DefaultConfig", func(t *testing.T) {
		cfg := DefaultConfig()
		assert.True(t, cfg.ReadOnly, "Default config should be read-only")
		assert.True(t, cfg.DynamicToolsets, "Dynamic toolsets should be enabled by default")
		assert.True(t, cfg.RateLimit.Enabled, "Rate limiting should be enabled by default")
		assert.Equal(t, float64(10), cfg.RateLimit.RequestsPerSecond, "Default RPS should be 10")
	})
}
=======
func TestSecurityConfig(t *testing.T) {
	t.Run("DefaultConfig", func(t *testing.T) {
		cfg := DefaultConfig()
		assert.True(t, cfg.ReadOnly, "Default config should be read-only")
		assert.True(t, cfg.DynamicToolsets, "Dynamic toolsets should be enabled by default")
		assert.True(t, cfg.RateLimit.Enabled, "Rate limiting should be enabled by default")
		assert.Equal(t, float64(10), cfg.RateLimit.RequestsPerSecond, "Default RPS should be 10")
	})

	t.Run("NonDefaultConfig", func(t *testing.T) {
		cfg := DefaultConfig()
		cfg.ReadOnly = false
		cfg.DynamicToolsets = false
		cfg.RateLimit.Enabled = false
		cfg.RateLimit.RequestsPerSecond = 42

		assert.False(t, cfg.ReadOnly, "ReadOnly should be false when set")
		assert.False(t, cfg.DynamicToolsets, "DynamicToolsets should be false when set")
		assert.False(t, cfg.RateLimit.Enabled, "Rate limiting should be disabled when set")
		assert.Equal(t, float64(42), cfg.RateLimit.RequestsPerSecond, "RPS should reflect non-default value")
	})
}
>>>>>>> REPLACE

</suggested_fix>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +63 to +66
- name: Run gosec
uses: securego/gosec@master
with:
args: '-no-fail -fmt sarif -out gosec-results.sarif ./...'
Copy link

Choose a reason for hiding this comment

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

🚨 suggestion (security): Pin the gosec action to a stable release instead of 'master'.

Using 'master' may introduce breaking changes or vulnerabilities. Please use a specific version tag for stability and security.

Suggested change
- name: Run gosec
uses: securego/gosec@master
with:
args: '-no-fail -fmt sarif -out gosec-results.sarif ./...'
- name: Run gosec
uses: securego/gosec@v2.19.0
with:
args: '-no-fail -fmt sarif -out gosec-results.sarif ./...'

Comment on lines +76 to +81
- name: Run gitleaks
uses: gitleaks/gitleaks-action@v2
with:
config-path: .gitleaks.toml
format: sarif
report-path: gitleaks-report.sarif
Copy link

Choose a reason for hiding this comment

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

🚨 suggestion (security): Consider pinning the gitleaks action to a specific commit or release.

Pinning to a specific release or commit hash helps prevent unexpected changes in the action from impacting your workflow.

Suggested change
- name: Run gitleaks
uses: gitleaks/gitleaks-action@v2
with:
config-path: .gitleaks.toml
format: sarif
report-path: gitleaks-report.sarif
- name: Run gitleaks
uses: gitleaks/gitleaks-action@v2.19.0
with:
config-path: .gitleaks.toml
format: sarif
report-path: gitleaks-report.sarif

}
}

func TestRateLimiting(t *testing.T) {
Copy link

Choose a reason for hiding this comment

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

suggestion (testing): Rate limiting tests do not cover reset or recovery after limit is exceeded.

Please add a test to verify the rate limiter allows requests again after the reset period, confirming correct recovery behavior.

Suggested implementation:

func TestRateLimiting(t *testing.T) {
	logger := logrus.New()
	cfg := DefaultConfig()
}

func TestRateLimiter_ResetsAfterPeriod(t *testing.T) {
	logger := logrus.New()
	cfg := DefaultConfig()
	// Set a short window for testing
	cfg.RateLimitWindow = 100 * time.Millisecond
	cfg.RateLimitRequests = 2

	middleware := NewSecurityMiddleware(cfg, logger)

	handler := middleware.RateLimit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	}))

	req := httptest.NewRequest("GET", "/", nil)
	rec := httptest.NewRecorder()

	// First request should pass
	handler.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)

	// Second request should pass
	rec = httptest.NewRecorder()
	handler.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)

	// Third request should be rate limited
	rec = httptest.NewRecorder()
	handler.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusTooManyRequests, rec.Code)

	// Wait for the window to reset
	time.Sleep(cfg.RateLimitWindow + 10*time.Millisecond)

	// After reset, request should pass again
	rec = httptest.NewRecorder()
	handler.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
}

Comment on lines +14 to +22
func TestSecurityConfig(t *testing.T) {
t.Run("DefaultConfig", func(t *testing.T) {
cfg := DefaultConfig()
assert.True(t, cfg.ReadOnly, "Default config should be read-only")
assert.True(t, cfg.DynamicToolsets, "Dynamic toolsets should be enabled by default")
assert.True(t, cfg.RateLimit.Enabled, "Rate limiting should be enabled by default")
assert.Equal(t, float64(10), cfg.RateLimit.RequestsPerSecond, "Default RPS should be 10")
})
}
Copy link

Choose a reason for hiding this comment

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

suggestion (testing): Test for SecurityConfig covers only default values.

Please add tests for non-default configurations, including toggling security features, to verify correct behavior in all supported modes.

Suggested change
func TestSecurityConfig(t *testing.T) {
t.Run("DefaultConfig", func(t *testing.T) {
cfg := DefaultConfig()
assert.True(t, cfg.ReadOnly, "Default config should be read-only")
assert.True(t, cfg.DynamicToolsets, "Dynamic toolsets should be enabled by default")
assert.True(t, cfg.RateLimit.Enabled, "Rate limiting should be enabled by default")
assert.Equal(t, float64(10), cfg.RateLimit.RequestsPerSecond, "Default RPS should be 10")
})
}
func TestSecurityConfig(t *testing.T) {
t.Run("DefaultConfig", func(t *testing.T) {
cfg := DefaultConfig()
assert.True(t, cfg.ReadOnly, "Default config should be read-only")
assert.True(t, cfg.DynamicToolsets, "Dynamic toolsets should be enabled by default")
assert.True(t, cfg.RateLimit.Enabled, "Rate limiting should be enabled by default")
assert.Equal(t, float64(10), cfg.RateLimit.RequestsPerSecond, "Default RPS should be 10")
})
t.Run("NonDefaultConfig", func(t *testing.T) {
cfg := DefaultConfig()
cfg.ReadOnly = false
cfg.DynamicToolsets = false
cfg.RateLimit.Enabled = false
cfg.RateLimit.RequestsPerSecond = 42
assert.False(t, cfg.ReadOnly, "ReadOnly should be false when set")
assert.False(t, cfg.DynamicToolsets, "DynamicToolsets should be false when set")
assert.False(t, cfg.RateLimit.Enabled, "Rate limiting should be disabled when set")
assert.Equal(t, float64(42), cfg.RateLimit.RequestsPerSecond, "RPS should reflect non-default value")
})
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy