-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
Conversation
- 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
Reviewer's GuideThe 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 imageerDiagram
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
}
Class diagram for security test suite additionsclassDiagram
class SecurityTestSuite {
+TestPermissionBoundaries()
+TestSecurityHeadersValidation()
+TestToolsetIsolation()
+TestHealthCheckFunctionality()
}
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this 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>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
- name: Run gosec | ||
uses: securego/gosec@master | ||
with: | ||
args: '-no-fail -fmt sarif -out gosec-results.sarif ./...' |
There was a problem hiding this comment.
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.
- 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 ./...' |
- name: Run gitleaks | ||
uses: gitleaks/gitleaks-action@v2 | ||
with: | ||
config-path: .gitleaks.toml | ||
format: sarif | ||
report-path: gitleaks-report.sarif |
There was a problem hiding this comment.
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.
- 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) { |
There was a problem hiding this comment.
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)
}
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") | ||
}) | ||
} |
There was a problem hiding this comment.
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.
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") | |
}) | |
} |
This PR adds comprehensive security enhancements to the MCP server:
Security Test Suite
CI/CD Security Checks
Configuration
Other Improvements
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:
CI:
Tests:
Chores: