Skip to content

feat(mcp): add Docker deployment and harden MCP tools - #111

Merged
thib3113 merged 24 commits into
mainfrom
feat/crowdsec-mcp
Oct 29, 2026
Merged

thib3113 merged 24 commits into
mainfrom
feat/crowdsec-mcp

Conversation

@thib3113

@thib3113 thib3113 commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

Adds the CrowdSec MCP server (stdio + streamable HTTP), its Docker/Compose deployment, and hardens the MCP tools after a targeted QA pass. It also extracts shared duration helpers into crowdsec-client and refreshes repo metadata, docs, CI and security workflows.

Highlights

  • New crowdsec-mcp package: decisions, alerts, allowlists and a status tool, plus MCP resources (crowdsec://docs/*) and prompts. Supports bouncer and watcher modes, read-only by default.
  • Input validation: reject unitless string durations ("300"); numeric durations are interpreted as seconds (300 → 5m). Validate and normalize Country/AS scope values before calling the LAPI.
  • List pagination: list_decisions and list_alerts honor limit/all, report exact hidden counts when limit is explicit, and no longer cap list_alerts at 25 rows. Default calls stay cheap and show a “more results may be available” hint.
  • Allowlists: trim allowlist names, reject blank names, and distinguish a CIDR match from an exact IP match.
  • Docker deployment: Dockerfile (multi-stage, non-root, read-only friendly), Compose sidecar sharing CrowdSec’s network namespace for local-only delete_alerts, and a GHCR publishing workflow with SBOM/provenance.
  • CI/security: pin external GitHub Actions to commit SHAs, add actionlint, zizmor, OpenSSF Scorecard and a Snyk MCP scan, harden release caches, and update vulnerable transitive dependencies.
  • Docs/metadata: MCP README, docs/agent-setup-guide.md, llms.txt, SECURITY/CONTRIBUTING/CODE_OF_CONDUCT/SUPPORT, issue and PR templates, package licenses and metadata.

Validation

  • pnpm --filter crowdsec-mcp build
  • pnpm --filter crowdsec-mcp test -- --run → 219 passed, 11 skipped
  • pnpm --filter crowdsec-client test -- --run → 200 passed, 2 skipped
  • pnpm --filter crowdsec-mcp lint
  • Prettier check
  • git diff --cached --check
  • GitHub Actions: all checks green (CI matrix on Node 24/lts/latest, CodeQL, SonarCloud, Snyk, actionlint, zizmor, HTTP E2E, Docker build).
  • Targeted live QA: PASS
    • 40 controlled alerts/decisions: limit=40 → 0 hidden, limit=26 → 14 hidden, limit=25 → 15 hidden, limit=1000 → 0 hidden, all=true → 40
    • invalid/valid durations and scope values
    • allowlist trim and CIDR/IP wording
    • filtered delete_alerts, unban_ip by IP/ID
    • test data cleaned; background data and allowlist intact

Notes

  • delete_alerts remains local-only on the LAPI; use the sidecar deployment or run the MCP on the LAPI host.
  • An explicit limit fetches all matching rows to compute an exact hidden count. This keeps the count accurate at the cost of a larger query; without limit the request stays small and only hints that more rows may exist.
  • crowdsec-client now uses axios@1.21.0 with follow-redirects@1.16.2; the previous Snyk follow-redirects finding is resolved.
  • MCP transitive dependencies were updated for security: body-parser@2.4.0, qs@6.17.0, fast-uri@3.1.9.
  • npm publishing of crowdsec-client@1.3.0 and crowdsec-mcp remains a separate release step.

Add a stdio and Streamable HTTP MCP server for CrowdSec with bouncer, watcher and mTLS authentication, read-only-by-default tools, server-side filtering and Markdown output.

Tools cover status, decisions (list/ban/unban), alerts (list/get/delete) and allowlists (list/get/check). HTTP always requires a bearer token.

Includes unit, HTTP contract, stdio end-to-end and opt-in Docker integration tests. Alert deletion is documented as local-only because of the CrowdSec LAPI restriction.
- add Dockerfile, compose sidecar and GHCR workflow
- reject invalid durations/scope values before the LAPI call
- honor limit/all and report exact hidden counts in list tools
- trim allowlist names and clarify CIDR matches
- refresh licenses, package metadata, docs and QA TODO
Comment thread .github/workflows/CI.yml Fixed
Comment thread .github/workflows/docker-mcp.yml Fixed
Comment thread .github/workflows/docker-mcp.yml Fixed
Comment thread .github/workflows/mcp-scan.yml Fixed
Comment thread .github/workflows/mcp-scan.yml Fixed
Comment thread .github/workflows/scorecard.yml Fixed
- pin external actions to commit SHAs and harden release caches
- upgrade crowdsec-client to axios 1.21.0 / follow-redirects 1.16.2
- escape backslashes in Markdown table cells
- pin zizmor and snyk-agent-scan versions
- run uvx tools with --no-build and pin their versions
- use explicit permissions in the Scorecard workflow
- simplify the duration regex and a few lint findings
- bump body-parser to 2.4.0, qs to 6.16.1 and fast-uri to 3.1.9
- addresses Snyk findings in the MCP dependency tree
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

- make the CodeQL job depend on the Snyk security job so code scanning sees its SARIF configurations
- keep the standalone CodeQL workflow for the weekly scheduled scan only
- split complex handlers into focused helpers
- remove async-without-await and awaited loops
- simplify duration parsing, ternaries and string handling
- inline the Node LTS tsconfig and set the Sonar Python version
- remove the now-unnecessary await on the synchronous passthrough factory
- split the remaining complex helpers
- make cell stringification and JSON body parsing explicit
- add model-facing resources, instructions and triage/investigation prompts
- require scope+value for delete_alerts
- add Dockerized HTTP passthrough E2E and CI workflow
- document deployment, E2E, security notes and release status
- address SNYK-JS-QS-21755395 in the MCP dependency tree
- use bash [[ tests in the E2E script
- remove an async function without await
- replace push calls with arrays in the investigation prompt
- add shared duration helpers to crowdsec-client (days, Go wire format)
- interpret numeric ban durations as seconds
- stop sending undocumented alert filters and filter kind locally
- delete decisions by id/scope directly, keep search for CIDR containment
- harden HTTP transport (timeouts, 413 vs 500, safe cleanup, shutdown)
- move the HTTP E2E script to .github/scripts and run the live integration test in CI
- remove unused workflow patch scripts
- pass contains=false for alert searches so CIDR ranges match contained decisions
- keep contains for watcher decisions and internal range unbans
- extract list_decisions validation to lower cognitive complexity (Sonar S3776)
- config: default to watcher for TLS-only auth
- unban_ip: require confirm for type/scenario-only deletes
- alerts: keep since/until as Go durations on DELETE
- allowlist: cover the year-1 never-expiration sentinel
- docs/status: hide delete_alerts in read-only, clarify write capability
- http: catch unhandled request failures
- client: export GoDuration/SearchAlertsQuery/DeleteAlertsQuery overrides
- build: use esbuild.js and generated pkg.ts for crowdsec-mcp
- normalize scope/value before sending list filters to the LAPI
- keep CrowdSec casing (Ip/CAPI) in bouncer decision output
- preserve real network/TLS errors during passthrough auth
- handle aborted HTTP request bodies without leaking handlers
- use the bulk allowlist endpoint for single checks too
- fix Sonar issues: node: imports, regex escapes, nested ternary,
  async handler without await
- keep GoDuration as a documented branded alias instead of a plain string
Gate the destructive delete_alerts tool on the HTTP transport itself:
without a service token it stays hidden unless
CROWDSEC_MCP_ALLOW_ALERT_DELETE_WITHOUT_TOKEN is explicitly set.
This covers passthrough and any future tokenless HTTP setup.
Replace em/en dashes, arrows, ellipsis, comparison and multiplication
symbols in docs, comments, MCP output placeholders and tests. Keep the
micro sign (µ) and the 2³² superscripts, which are meaningful units/values.
…nt HTTP security

delete_alerts without a narrow target (id, ip, range or scope+value) now requires confirm=true, like unban_ip type/scenario-only filters. Update the tool description, server instructions and README, and add tests for each broad filter. Also expand the README security section with the transport threat model, recommended deployments and security-relevant variables.
@sonarqubecloud

Copy link
Copy Markdown

@thib3113
thib3113 merged commit cea37a1 into main Oct 29, 2026
30 checks passed
@thib3113
thib3113 deleted the feat/crowdsec-mcp branch October 29, 2026 21:40
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.

2 participants