Skip to content

Sync upstream and fix CrowdStrike detection-ID and host-FQL error handling - #29

Open
gdandu-uptycs wants to merge 74 commits into
mainfrom
chore/sync-upstream-cql-guidance
Open

Sync upstream and fix CrowdStrike detection-ID and host-FQL error handling#29
gdandu-uptycs wants to merge 74 commits into
mainfrom
chore/sync-upstream-cql-guidance

Conversation

@gdandu-uptycs

Copy link
Copy Markdown
Collaborator

Summary

Brings the fork up to date with CrowdStrike/falcon-mcp (it was 68 commits behind, now 0), then adds two fixes for cases where a tool failure gave the caller nothing to correct from.

1. Sync with upstream

Merges upstream/main. Notable changes this pulls in:

Conflict resolution: cloud.py and test_cloud.py resolved in upstream's favour, since CrowdStrike#397 supersedes our own fix and carries matching tests. Our f61f23f (chunking _base_get_by_ids at 1000 IDs to avoid HTTP 413) is preserved.

Also cherry-picks two script lint fixes (ruff E702 and mypy on probe_pagination) that were already on another branch, so ruff check . passes repo-wide.

2. hosts.py — return the FQL guide on a failed search

search_hosts returned a bare [error] on a search-step failure, dropping the guide. The API answers only "Request failed with status code 400" / "Invalid filter expression supplied", naming neither the offending field nor the valid set.

A common case is filtering on agent_id, which the Hosts API does not accept — the field is device_id. SEARCH_HOSTS_FQL_DOCUMENTATION already documents it; it just never reached the caller.

Now uses _format_fql_error_response, as detections, cloud, firewall, ioc and rtr already do. Both the helper and the guide were already imported.

3. detections.py — reject malformed composite IDs before calling

get_detection_details passed ids straight through. When a caller rebuilds a composite ID rather than forwarding it verbatim, the API answers invalid CID provided, which does not identify the bad part.

Validates the shapes that arise from reconstruction — leading CID dropped (ind:<aid>:<id>), ldt: glued onto a composite ID, only the trailing detection-id kept, or a different scheme prefix — and returns the offending ID, the problem, and the expected format without a request to Falcon.

Deliberately conservative:

  • an unfamiliar-but-valid scheme is passed through, so an unreadable-tenant ID still surfaces as a tenant error rather than a formatting one
  • legacy ldt:<aid>:<detect_id> is left alone
  • only a bare value matching the trailing detection-id pattern is flagged, so arbitrary single-segment IDs are untouched

Testing

  • 905 passed, 257 skipped, 83 subtests (was 902/257/76)
  • ruff check . and mypy clean
  • Verified live against a real Falcon tenant: a malformed ID short-circuits with the validation message and no API call; a well-formed ID still returns detection data unchanged; an agent_id filter returns 400 carrying the guide that names device_id

precognitivem0nk and others added 30 commits May 12, 2026 14:54
…#364)

* test(compliance): add MCP protocol compliance test suite

Adds tests/test_mcp_compliance.py covering MCP spec revision 2025-06-18
and JSON-RPC 2.0 conformance. Seven tests:

- streamable-http Origin header validation (security)
- tools/list immutability across sessions (rug-pull guard)
- JSON-RPC error code conformance (-32601, -32602)
- Mcp-Session-Id binding and entropy
- Capability negotiation honesty
- Resource URI format compliance
- Read-only tool annotation correctness across modules

Output schema conformance deferred to a follow-up PR.

Resolves CrowdStrike#235.

* refactor(test): align MCP compliance tests with project conventions

- Convert bare pytest functions to unittest.TestCase / IsolatedAsyncioTestCase
  classes to match the style used across all other test files
- Make MUTATING_TOOL_ALLOWLIST bidirectional: assert no ghost entries (deleted
  tools), no stale entries (tools reverted to read-only), and no unlisted
  mutating tools
- Add starlette and sse-starlette to dev dependencies (were transitive only)
- Use mcp.types.LATEST_PROTOCOL_VERSION instead of hardcoded revision string
- Reset AppStatus.should_exit alongside should_exit_event in setUp/tearDown
- Replace _accept_headers() function with ACCEPT_HEADERS constant
- Type _parse_jsonrpc response parameter as httpx.Response
- Remove from __future__ import annotations (no other test file uses it)
- Remove section separator comment blocks (not used elsewhere in tests/)
- Remove redundant test_all_tools_have_annotations from test_server.py
  (strict superset coverage now lives in the compliance suite)

---------

Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
…rowdStrike#379)

When authentication fails, the error message now includes the HTTP
status code and reason from the CrowdStrike API response, plus a
troubleshooting hint tailored to the failure mode (bad credentials,
invalid member_cid, disabled scopes, or network/URL issues).

Closes CrowdStrike#351
…udgets (CrowdStrike#376)

* fix(tools): omit outputSchema from tools/list to fit client context budgets

FastMCP auto-derives outputSchema from each tool's return type. With 67
registered tools, the resulting tools/list payload exceeds the per-tool
context budget that VS Code Copilot (and likely other clients) applies
when injecting tool definitions into the model context, so tools are
silently dropped from both the model and the UI tool picker.

outputSchema is intended for post-call structured output validation per
MCP 2025-11-25, not for tool selection. Passing structured_output=False
to every server.add_tool() call leaves output_schema=None on each Tool,
which Pydantic excludes from the JSON-RPC payload (exclude_none=True).

Fixes CrowdStrike#325.

* test(tools): assert tools/list omits outputSchema for every registered tool

Regression coverage for issue CrowdStrike#325. Three assertions:

  - tool.outputSchema is None for every tool returned by list_tools()
  - inputSchema is still emitted (guards against an over-broad fix)
  - the wire serialization (model_dump_json with exclude_none=True,
    matching the options used by the MCP session layer) does not
    contain an outputSchema key for any tool

* refactor(tools): address review feedback on outputSchema fix

Remove verbose inline comments that duplicate git history context.
Replace redundant wire-format test with payload size budget assertion
(110KB threshold). Add unit test verifying _add_tool forwards
structured_output=False. File follow-up issue CrowdStrike#380 for description
trimming.

---------

Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
Convert async test methods in TestNGSIEMModule to synchronous wrappers
using asyncio.run(). The async def + @pytest.mark.asyncio pattern had no
effect because the class inherits from unittest.TestCase (via TestModules),
causing all 9 async tests to be silently skipped on every run.

Also fix a latent assertion in test_search_ngsiem_missing_job_id that
expected a 'details' key which _format_error_response omits when passed
an empty dict.

Closes CrowdStrike#375
…rowdStrike#385)

Replace verbose multi-paragraph FQL boilerplate and inconsistent one-liners
with a researched 2-4 sentence standard covering what the tool does, when to
use it, and what it returns. FQL search tools now steer models to consult the
corresponding fql-guide resource before constructing filters.

Removes unused EMBEDDED_FQL_SYNTAX imports from detections and rtr modules.
Fixes inaccurate return claims identified through live API validation.

Closes CrowdStrike#380
* feat(modules/cases): add Case Management module (CrowdStrike#386)

Add a new cases module replacing the deprecated Incidents module, providing
LLM-facing tools for case lifecycle management via the CrowdStrike Case
Management API (FalconPy CaseManagement class).

Tools:
- falcon_search_cases — two-step query+get search with FQL filtering
- falcon_get_cases — retrieve cases by known IDs
- falcon_create_case — create case (PUT, supports inline evidence)
- falcon_update_case — update case fields with optimistic concurrency
- falcon_add_case_alert_evidence — attach alert composite IDs
- falcon_add_case_event_evidence — attach LogScale event IDs
- falcon_manage_case_tags — add/remove tags (asymmetric POST/DELETE)
- falcon_list_case_templates — two-step template query+get

Key implementation details:
- Evidence IDs converted to object format [{"id": "..."}] per API spec
- update_case nests fields under "fields" key (flat body silently no-ops)
- delete_case_tags uses DELETE query params, not POST body
- get_templates uses use_params=True (GET endpoint)
- 40+ FQL filter fields validated against live API

Closes CrowdStrike#386

* docs(modules/cases): use natural workflow-style example prompts

Rewrites case tool examples to sound like real analyst questions rather
than generic descriptions. Avoids numeric severity in favor of named
levels (high, critical).
* chore(main): release 0.10.0

* chore: sync gemini-extension.json version to 0.10.0

* chore: sync server.json version to 0.10.0

* chore: update uv.lock

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…riteria (CrowdStrike#396)

* fix(modules/idp): resolve FieldInfo defaults before building search_criteria

* refactor(modules/idp): extract unwrap_field_default utility and add zero-arg test

DRY extraction: move FieldInfo unwrapping logic from nested function and
inline hasattr check into a shared utility in common/utils.py. Add test
coverage for the zero-argument call path to verify no FieldInfo leak.

---------

Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
…CrowdStrike#397)

* fix(modules/cloud): return int count from count_kubernetes_containers

* fix(modules/cloud): handle edge cases in count extraction

- default_result=0 so empty responses return int, not []
- fix copy-paste bug in error test (was calling search_ not count_)
- guard against null count values from API
- use specific error message for this operation

* docs(modules/cloud): add returns sentence to count tool description

* test(modules/cloud): remove issue reference from test docstring

---------

Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
…CrowdStrike#391)

* feat(modules/correlation-rules): add NG-SIEM Correlation Rules module

Adds a new module exposing CrowdStrike Correlation Rules API (NG-SIEM
detection rules) via 8 MCP tools:

- falcon_search_correlation_rules  — FQL-filtered combined search (read)
- falcon_get_correlation_rules     — fetch rules by ID (read)
- falcon_create_correlation_rule   — create a new CQL-based rule (write)
- falcon_update_correlation_rule   — update rule fields (write)
- falcon_delete_correlation_rules  — delete rules by ID (destructive)
- falcon_publish_correlation_rule  — promote a draft version to live (write)
- falcon_export_correlation_rules  — export rules as JSON/YAML (read)
- falcon_import_correlation_rule   — import a rule definition (write)

Also adds:
- falcon://correlation-rules/fql-guide resource with FQL field reference
- API scope mappings for all 8 operations under "Correlation Rules:read/write"

Module is auto-discovered by the registry; no changes to server.py required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(modules/correlation-rules): rewrite module based on live API validation

Rewrites the Correlation Rules module from 8 tools down to 4 after live
API testing revealed several broken endpoints and incorrect payload formats.

Key fixes:
- Add customer_id to create body (was causing 401)
- Remove execution_mode/use_ingest_time from create search object (read-only fields)
- Fix severity to enumerated values (10/30/50/70/90)
- Change default outcome from deprecated "incident" to "detection"
- Remove redundant get tool (search already returns full details)
- Remove publish/export/import tools (async/multipart/unnecessary)
- Validate FQL filters against live API, remove broken fields
- PATCH body must be a list (APIHarnessV2 does not auto-wrap)

---------

Co-authored-by: Sahil Sharma <sahilsharma@ip-172-19-22-57.eu-west-1.compute.internal>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
…rowdStrike#407)

Replace deprecated tactic/technique params with mitre_attack array,
fix trigger_mode from invalid per_event to verbose, and add
use_ingest_time parameter to the search block.
)

* feat(modules/rtr): add audit and command wait workflows

* fix(modules/rtr): read sequence_id from API response in polling loop

The RTR_CheckCommandStatus API returns a sequence_id field in each
response that the caller must use for the next poll. The previous
implementation naively incremented the counter, which is incorrect per
the Falcon API docs and live API validation.

* fix(docs): regenerate module docs with correct tool names

The extract_registered_tool_names() fix added in this PR also corrects
pre-existing wrong tool names in the idp and intel docs (method names
were used instead of registered MCP tool names). Regenerated all module
docs to pick up the corrections.

* fix(modules/rtr): align resource URIs with project convention and add to allowlist

Resource URIs must match the falcon://{module}/{path}/{kind}-guide
pattern enforced by the MCP compliance test suite. Also adds
run_rtr_read_only_command_and_wait to MUTATING_TOOL_ALLOWLIST since it
initiates RTR commands (non-read-only annotation is correct).

---------

Co-authored-by: Kenneth Lund <nek9505@icloud.com>
Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
* feat(modules/quarantine): add quarantine workflows

* refactor(modules/quarantine): simplify tool surface and align with project standards

Rewrites the quarantine module from the original 7-tool PR down to 4 tools
with a cleaner interface that follows established codebase patterns.

- Unified update/delete tools (ids OR filter, not separate _by_ids/_by_filter)
- Renamed preview_quarantine_action_counts to count_quarantine_actions
- Removed get_quarantined_file_details (search already returns full details)
- Removed q parameter (FQL filter covers all discovery needs)
- Fixed annotations (idempotentHint=True on delete, inlined for docs generator)
- Fixed docstrings to what/when/Consult/Returns standard
- Fixed filter descriptions to concise pointer format
- Added empty-list and empty-filter guards
- Added MUTATING_TOOL_ALLOWLIST entries
- Added TOOL_EXAMPLES and regenerated docs
- Validated all operations against live API

---------

Co-authored-by: KenUdigIT710 <nek9505@icloud.com>
Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
…ions (CrowdStrike#408)

Users in enterprise/restricted network environments can now route Falcon API
traffic through an HTTP/HTTPS proxy via the --proxy CLI flag or FALCON_PROXY_URL
environment variable.

Closes CrowdStrike#405
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/theskumar/python-dotenv/releases)
- [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md)
- [Commits](theskumar/python-dotenv@v1.2.1...v1.2.2)

---
updated-dependencies:
- dependency-name: python-dotenv
  dependency-version: 1.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [black](https://github.com/psf/black) from 26.1.0 to 26.5.1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](psf/black@26.1.0...26.5.1)

---
updated-dependencies:
- dependency-name: black
  dependency-version: 26.3.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ike#324)

Bumps [crowdstrike-falconpy](https://github.com/CrowdStrike/falconpy) from 1.6.0 to 1.6.2.
- [Release notes](https://github.com/CrowdStrike/falconpy/releases)
- [Changelog](https://github.com/CrowdStrike/falconpy/blob/main/CHANGELOG.md)
- [Commits](CrowdStrike/falconpy@v1.6.0...v1.6.2)

---
updated-dependencies:
- dependency-name: crowdstrike-falconpy
  dependency-version: 1.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.14 to 0.15.14.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](astral-sh/ruff@0.14.14...0.15.14)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.15.11
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [mypy](https://github.com/python/mypy) from 1.19.1 to 2.1.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](python/mypy@v1.19.1...v2.1.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 1.20.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#345)

Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 6.0.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@08c6903...de0fac2)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…rike#369)

Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.6.0 to 6.2.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](actions/setup-python@a26af69...a309ff8)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…dStrike#347)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 7.0.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@ea165f8...043fb46)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…o 23.2.0 (CrowdStrike#371)

deps(actions): bump DavidAnson/markdownlint-cli2-action

Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 20.0.0 to 23.2.0.
- [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases)
- [Commits](DavidAnson/markdownlint-cli2-action@992badc...ded1f94)

---
updated-dependencies:
- dependency-name: DavidAnson/markdownlint-cli2-action
  dependency-version: 23.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
)

The previous Dockerfile used the `ghcr.io/astral-sh/uv:python3.13-alpine`
combined image as the build stage. When Dependabot bumps the digest, it
resolves to the bare distroless uv image (no /bin/sh), breaking arm64
builds. Similarly, the runtime python image pin drifted from Alpine to
Debian, breaking the `adduser -D` syntax.

Switch to a 3-stage build: COPY only the /uv binary from the distroless
image into a python:3.13-alpine builder stage. This decouples the uv
version bump from shell availability and keeps both images pinned to
their correct Alpine variants.
The docs site is moving to a separate repository so these dependency
updates are no longer applicable here.
Reduces PR noise from ~12 individual PRs to max 3 per month (one grouped
PR per ecosystem: python, actions, docker).
…e#429)

get_mitre_report with format='json' returned a raw JSON string instead
of parsed Python objects. FalconPy's GetMitreReport returns bytes for
both CSV and JSON, and the decoded JSON string was passed through
without parsing.

Parse JSON responses into a list of dicts, handle the b'null' sentinel
(actors with no MITRE mappings) by returning an empty list, and guard
malformed payloads with a structured error. CSV format still returns
raw text.

Closes CrowdStrike#383
carlosmmatos-cs and others added 29 commits June 24, 2026 14:55
)

docs(usage): rename dynamic-mode.md and fix URL slug to /usage/dynamic-mode/

Reverts the rename from PR CrowdStrike#451 — the file is restored to dynamic-mode.md
so the Astro site builds to /falcon-mcp/usage/dynamic-mode/ as intended.
Updates the three cross-references in README.md, docs/usage/cli.md, and
docs/getting-started/configuration.md to match the corrected slug.
…oldown (CrowdStrike#454)

* revert(deps): remove constraint-dependencies block, add dependabot cooldown

Prodsec flagged the [tool.uv] constraint-dependencies block added in CrowdStrike#447
as introducing package version bumps outside the normal vetting process.
Removes the block entirely; the starlette>=1.3.1 floor in dev dependencies
stays as it was a legitimate dependabot bump.

Adds a 7-day cooldown to all three dependabot ecosystems (uv, github-actions,
docker) so new releases sit for a week before a bump PR is opened, giving
time for internal vetting. uv.lock regeneration left to CI.

* regenerate lock file
…e#446)

* feat(modules/recon): add Falcon Intelligence Recon module

Adds a new ReconModule covering Falcon Intelligence Recon — dark web monitoring,
leaked credentials, typosquatting detection, and breach notifications. Ships three
read-only search tools following the established two-step GET pattern (query IDs →
fetch full details via GET query params):

- falcon_search_recon_notifications: search and retrieve full notification details
  including breach summaries and item metadata; disambiguated from endpoint
  detections in the tool description ("also called recon alerts")
- falcon_search_recon_rules: list and inspect monitoring rules by topic, priority,
  and status
- falcon_search_recon_exposed_data_records: retrieve leaked credential and PII rows
  associated with notifications

FQL guides validated against the live API — confirmed field values include
SA_DOMAIN/SA_TYPOSQUATTING (uppercase), status:'active' for rules, and
newly_reported/previously_reported/confirmed_active for credential status. Documents
that breach_summary.credential_statuses causes a 400 FQL parse failure and that
typosquatting.* sub-fields have unconfirmed queryability on QueryNotificationsV1.

Adds dynamic mode filter hints, API scope entries, TOOL_EXAMPLES for doc generation,
and an alphabetical README row. All 6 FalconPy operations verified against the
installed SDK; 14 live integration tests confirm correct operation names and
GET-with-params response shape.

* docs(modules/recon): add Counter Adversary Operations (CAO) to all tool descriptions

Fixes a typo in the existing CAO reference in search_recon_notifications and adds
consistent CAO mentions to search_recon_rules and search_recon_exposed_data_records
so prompts using "Counter Adversary Operations" or "CAO" surface all three tools.
Regenerates docs/modules/recon.md.

* chore(docs): update example prompts for recon exposed-data tool

* docs(modules/recon): restore falcon_search_detections cross-reference in notifications tool

The disambiguation hint was present in the original committed docs but never in the
source docstring, so it was silently dropped when docs were regenerated. Moving it
into the docstring so it survives future regeneration runs.

* docs(modules): disambiguate alert/detection/notification semantics

falcon_search_detections now explicitly owns the generic term "alert" and calls out
coverage across EPP, IDP, XDR, OverWatch, and NG-SIEM. Recon no longer competes for
bare "alert" or "detection" — "typosquatting detections" becomes "typosquatting matches"
and the cross-reference to falcon_search_detections is tightened to name specific
products (endpoint, XDR, NG-SIEM).

* docs(modules/detections): restore attribute keywords in search_detections docstring

First pass over-generalized by dropping severity/status/hostname/time range keywords.
Restored them alongside the new product-coverage sentence and alert synonym so all
three routing signals are present.
…rowdStrike#455)

Three transitive dependencies in the current lock are below their
security fix versions: pyjwt 2.10.1 (CVE-2026-32597), idna 3.10
(CVE-2026-45409), and requests 2.32.4 (CVE-2026-25645). The other
packages flagged in the same advisory scan (cryptography, python-
multipart, starlette, urllib3) are already at or above their safe
floor and need no change.

Adds a [tool.uv] constraint-dependencies block with minimum version
floors for the three affected packages. All are transitive with
uncapped parents, so the resolver already picks safe versions on a
fresh lock; the constraints prevent future re-resolves from quietly
regressing below the safe floor.

uv.lock regeneration is left to CI so only pyproject.toml is
committed here.
* chore(main): release 0.13.0

* chore: sync gemini-extension.json version to 0.13.0

* chore: sync server.json version to 0.13.0

* chore: update uv.lock

* chore: sync docs changelog

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…ncies (CrowdStrike#456)

The E2E testing framework (langchain_openai + mcp_use against a live LLM
agent) has been unused and was pulling in 55+ transitive packages, widening
the dependency attack surface unnecessarily.

Removes the tests/e2e/ directory, the CI workflow, the HTML report scripts,
and the e2e-testing.md doc. Drops langchain-openai and mcp-use[search] from
dev dependencies along with the now-dead langchain_core filterwarnings entry.
Strips the internal scan tool names, project/ticket references, and CVE
identifiers from the [tool.uv] constraint-dependencies comments while keeping
the version floor constraints themselves.
fix(deps): add cryptography floor constraint for CVE-2026-34180
…s, and falcon_get_cloud_groups tools (CrowdStrike#467)

* feat(modules/cloud): add cloud risks FQL resource documentation

* feat(modules/cloud): add falcon_search_cloud_risks tool

* fix(modules/cloud): address code quality review findings

- Fix pyproject.toml: replace broken packages=["falcon_mcp"] with
  find: include=["falcon_mcp*"] to correctly include all subpackages
  while excluding the plans/ directory from distribution
- Add combined_cloud_risks scope to api_scopes.py ("Cloud Security API Risks:read")
- Fix CLOUD_RISKS_FQL_DOCUMENTATION to use === sentinel === heading format
  consistent with all other FQL documentation in resources/cloud.py
- Remove duplicate test_register_tools_includes_cloud_risks and
  test_register_resources_includes_cloud_risks_fql methods which
  duplicated assertions already present in test_register_tools and
  test_register_resources

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(modules/cloud): add falcon_search_cloud_groups and falcon_get_cloud_groups tools

Implements ListCloudGroupsExternal and ListCloudGroupsByIDExternal operations
as MCP tools with full unit test coverage; registers API scopes for both.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(modules/cloud): add integration tests for cloud risks and cloud group tools

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(modules/cloud): add cloud risks tools to module docs and README

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(modules/cloud): fix linting and type errors

- Fix import sort order in scripts/scope_preflight_cloud_risks.py (ruff I001)
- Add falcon_search_cloud_risks entry to FILTER_HINTS so dynamic mode
  provides inline FQL field hints for the new tool
- Fix test_search_cspm_assets_with_tag_filter to accept empty dict response
  when the filter returns zero assets (consistent with other tests in the suite)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(modules/cloud): address feature-review findings in FQL guide and filter hints

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(modules/cloud): strengthen unit test assertions and add cloud groups filter hint

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(modules/cloud): fix filter hint sentence separator for threat_actors field

* docs(modules/cloud): clarify iom_findings vs cloud_risks tool selection boundary

Add symmetric cross-references between falcon_search_iom_findings and
falcon_search_cloud_risks so agents can distinguish per-rule-per-resource
violations (IOMs) from aggregated per-asset risk records (cloud risks).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* CR fixes

* CR fixes

* chore(modules/cloud): remove one-off scope preflight dev script

Development scaffold used to verify cloud risks/groups API scopes during
implementation. No longer referenced; scope coverage is captured in the
integration tests and api_scopes.py.

---------

Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
…e#463)

Search tools query entity IDs with sort applied, then hydrate full
details by ID. Some get-by-ID endpoints return entities in arbitrary
order, discarding the requested sort. Add an idempotent
BaseModule._reorder_by_ids helper and call it after hydration in every
two-step search tool so results match the query-step order.

Also correct the recon exposed-data-records sort docstring: exposure_date
is a valid sort field and updated_date is not (verified against the API).
…Strike#460)

The facet parameter on falcon_search_vulnerabilities only accepted a
single string, forcing callers to make one request per detail block.
The underlying combinedQueryVulnerabilities operation natively supports
multiple facets in a single request, so widen the parameter to
str | list[str] and forward it unchanged. Single-string usage remains
backward compatible.
* chore(issues): update modules list

The main version insert several new module.

* chore(issues): add remaining modules to feature-request dropdown

Add Case Management, Correlation Rules, Custom IOA, Policies, and Shield
to the module area dropdown so all 24 registered modules are selectable.

---------

Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
…d_report (CrowdStrike#464) (CrowdStrike#466)

fix(scheduled_reports): send body as array in launch_scheduled_report (CrowdStrike#464)

Co-authored-by: sanjibani <18418553+sanjibani@users.noreply.github.com>
* chore(main): release 0.14.0

* chore: sync gemini-extension.json version to 0.14.0

* chore: sync server.json version to 0.14.0

* chore: update uv.lock

* chore: sync docs changelog

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Search tools now return a structured envelope with results, pagination
(total/offset/limit/next), and filter_used, replacing the ad-hoc empty
response shape. A new _base_search_with_meta captures body.meta.pagination
before hydration discards it, and _build_pagination_envelope assembles the
response consistently across all search modules.

The envelope never synthesizes a total: when the API reports no pagination
count, total is null rather than 0, so a non-null total always reflects a
real value the API returned. Docstrings note when a tool cannot report a
total (e.g. Shield's activity monitor), and the Shield resource pagination
note is scoped to the one operation that omits it.

The doc generator now handles reflowed multi-line description= literals so
resource descriptions are no longer blanked, and the integration-test
list-response helper tolerates a null total.
* chore(main): release 0.15.0

* chore: sync gemini-extension.json version to 0.15.0

* chore: sync server.json version to 0.15.0

* chore: update uv.lock

* chore: sync docs changelog

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
… tools (CrowdStrike#474)

* fix(modules): fold meta.next cursor into pagination.next for CSPM assets/IOM

The CSPM assets and CSPM IOM endpoints return their next-page cursor at the
top level of body.meta as meta.next, with no meta.pagination.after. The
envelope builder only read the nested after key, so search_cspm_assets and
search_iom_findings always reported pagination.next = null and callers could
not page past the first response.

Unify the cursor to a single pagination.next, reading from whichever spot the
endpoint populated with deterministic precedence: meta.pagination.next, then
meta.pagination.after, then top-level meta.next. _extract_pagination now folds
the top-level cursor into the pagination dict at lowest precedence, and the
builder honors a nested next as well as after.

Closes CrowdStrike#473

* fix(modules): drop redundant offset input from dual-pagination search tools

Six search tools exposed both offset and a cursor parameter, giving the
model two mutually-exclusive ways to paginate. For CSPM in particular,
offset and after are mutually exclusive and offset >= 10000 returns HTTP
400, so deep paging must use the cursor. Now that pagination.next reliably
surfaces a real cursor, the offset input is redundant and ambiguous.

Remove the offset input parameter from search_firewall_rules,
search_firewall_rule_groups, search_iocs, search_vulnerabilities,
search_cspm_assets, and search_shield_alerts, and point their docstrings at
pagination.next as the single paging mechanism (last_id for Shield). The
response envelope is unchanged — the API still echoes an informational
offset key in its meta; we simply stop accepting and advertising offset as
an input.
…dStrike#475)

* docs(modules/ngsiem): add CQL authoring guidance to search tool

The falcon_search_ngsiem tool executed CQL but gave agents nothing on how
to build a valid query, so smaller models frequently emitted SPL/SQL-style
syntax that the API silently free-text-matched. Enrich the query_string
description and the tool docstring with the CQL pipe model, worked examples
(tag filter, groupBy, sort), and links to the authoritative LogScale
references.

Guidance lives inline in the description rather than in a separate resource:
in live testing on a smaller model, inline guidance produced valid CQL every
time while a resource behind a pointer was never fetched. The docstring also
notes that the API does not return detailed CQL parser errors, so queries
should be built from the references up front rather than corrected from
error feedback.

* feat(modules/ngsiem): add CQL authoring guidance and steer models to it

The falcon_search_ngsiem tool executes bring-your-own CQL, and smaller models
were confidently producing invalid queries for anything beyond the simplest
shapes. Add a workflow-oriented CQL guide as both an inline building-block
description on query_string and a registered falcon://ngsiem/search/cql-guide
resource, and inject the full guide into the tool's failure and empty-result
paths so it reaches the model exactly when a query didn't work. Dynamic mode now
carries a compact CQL hint on query_string, mirroring the FQL filter hints.

The guide's examples are validated against the live NG-SIEM API and the
CrowdStrike community-content corpus: regex uses field=/re/i (not =~), head() is
oldest-first with tail() for recent, and distinct count uses the positional
count(field, distinct=true) form.

Also reword the repository parameter: available repositories depend on the
tenant and its configuration, so it is a list of common examples rather than a
closed set of valid options (xdr and custom repositories are accepted too).
* feat(server): offload blocking tool handlers to threads for concurrency

Sync tool handlers ran inline on the asyncio event loop, so a single
blocking FalconPy call froze the loop and serialized every other in-flight
request. A single instance could only service one call at a time regardless
of caller count.

Offload each sync handler to a worker thread at the tool-registration
boundary via an async functools.wraps wrapper, so a single instance now
interleaves concurrent Falcon calls. FastMCP reads the tool schema through
the wrapped signature, so parameters and descriptions are unchanged, and it
detects the wrapper as async and awaits it off-loop. Already-async handlers
(ngsiem) are skipped and instead await the new command_async, and a lock
serializes the stale-token refresh so concurrent callers don't stampede the
token endpoint.

* docs(server): document thread-pool limits and pin them with tests

Review follow-ups on the handler-offload change. The thread cap and
cancellation semantics were load-bearing but undocumented, and two
behaviors that reviewers had to rediscover are now covered by tests.

Document on offload_to_thread that anyio's default limiter caps
concurrency at 40 per event loop, so speedup stops tracking caller count
past that point, and that the default abandon_on_cancel=False means a
cancelled request holds its worker until the blocking call returns.
Both are the right defaults; neither was written down.

Correct the _ensure_token_fresh docstring, which claimed the refresh
fires "exactly once across concurrent callers". That holds only when
login succeeds and clears token_stale. On failure the token stays stale
and waiting threads retry in turn, so the guarantee is really that
retries are serialized rather than collapsed.

Add tests for the thread cap, the cancellation contract, and the
failed-login path, and switch docstrings in the touched file to single
GFM backticks. The ngsiem suite routes command_async through the sync
command mock to keep its existing assertions, which meant a revert to
the blocking client passed unnoticed; a count comparison between the two
mocks now catches that, including a single reverted call site.
…back bind (CrowdStrike#477)

* docs(server): document --api-key and warn on unauthenticated non-loopback bind

HTTP transports have no authentication by default, and binding to a non-loopback
address such as --host 0.0.0.0 exposes an unauthenticated server with no warning.
The --api-key option already exists as the mitigation, but the examples steered
users toward the open-bind pattern and never showed how a client sends the key.

Add a non-halting startup warning when the server binds beyond loopback without
an API key, naming the host:port and pointing to --api-key. The server still
starts. Managed runtimes like AWS Bedrock AgentCore sit behind their own network
layer and are called out as unaffected.

Rework the README and docs/ examples so the secure pattern (bind scope + --api-key)
is the one users copy, add a canonical HTTP Transport Security section with both
server-side and client-side (x-api-key header, MCP config and curl) examples, and
flip the remote-client config so the authenticated version leads.

* fix(docs): resolve MD028 blockquote lint errors in deployment docs

* fix(docs): replace relative links that break on the docs portal

Relative markdown links resolve against the GitHub repo path and work there,
but the developer.crowdstrike.com portal renders them against the page URL, so
a link like ../../.github/CONTRIBUTING.md turned into
developer.crowdstrike.com/.github/contributing.md/ and 404'd.

Point the two CONTRIBUTING references at the full GitHub URL (governance files
live in .github/ and are not published to the portal), matching what
contributing.md already does, and repoint the README#modules scope-mapping
links to the portal's own Module Overview page.

* update bedrock verbiage

* fix(docs): resolve MD028 between AgentCore admonitions

* Update falcon_mcp/server.py

Co-authored-by: Gabe Alford <redhatrises@gmail.com>

* fix(server): correct format-arg count in open-bind warning

A prior edit shortened the warning message to a single %s but left three
positional args, so logger.warning raised "TypeError: not all arguments
converted during string formatting" whenever the server bound to a
non-loopback host without an API key. Use %s:%d and pass host and port.

---------

Co-authored-by: Gabe Alford <redhatrises@gmail.com>
…e#480)

The Falcon API exposes 135 live aggregate operations. 50 of them share one
request-body schema across four near-identical dialects, so building the body
construction once keeps the 12 downstream per-module aggregate tools as thin
wrappers instead of 12 hand-rolled builders that each rediscover the quirks.

_build_aggregate_spec covers the full 21-field msa.AggregateQueryRequest
superset and omits unset keys, which means the three narrower dialects need no
separate code path — they are strict subsets. _base_aggregate makes the call and
always wraps the body in a list, because every dialect rejects a bare object
live even though swagger marks six operations as bare.

Swagger turned out to be wrong on three counts, so the design follows live
probing instead: the true minimal body is type + field only (the 16 "required"
fields are a spec artifact), the body is always list-wrapped, and result buckets
key on label rather than key. Response order across multiple specs is not
preserved, but each result carries its own name, so callers identify them by
name rather than position.

Aggregate responses have no meta.pagination block at all, so there is no
envelope to build and the API's resources list is returned directly, matching
the existing non-paginated tool pattern. Type support is genuinely
per-operation — casemgmt rejects a date_histogram that alerts accepts — so
types pass through to the API rather than through an allowlist that would
wrongly reject types valid on operations nobody probed.

One behavioral addition beyond body construction: a 2xx response can still
carry a body-level 400, and handle_api_response only inspects the HTTP status,
so an invalid aggregate type would otherwise be discarded and surface as an
empty list. That case is now formatted as a real error while keeping the true
transport status in details, and the check is scoped to success statuses so a
genuine 403 still reaches handle_api_response's scope-hint branch.

This registers no tools; those arrive with the downstream module tasks.
The two aggregate helpers landed with 59- and 71-line docstrings, against
10-17 lines for every other _base_* helper in this file. Most of that was
research notes from live-probing the endpoints: exact status codes per
unsupported aggregation type, the two meanings of a 403, which swagger
fields are wrong, a four-row dialect table. Useful while building it, noise
for anyone reading the code afterward.

Both now state the contract and stop: summary, Args, Returns, Raises.
_base_aggregate drops from 71 lines to 20.

What came out was API behavior rather than function contract. That a bad FQL
filter returns a silent 200, or that an unsupported type surfaces as a 500,
is the API's behavior and not something these helpers decide — and it is
already covered by tests that fail if it changes, which is where a claim like
that belongs. One quirk stays in Returns: buckets key on `label` rather than
`key`, since callers have to destructure the result.

Inline comments survive only where the code looks wrong or removable without
them: the list-wrapped body, the 2xx-with-errors check, the empty-specs
guard, and formatting the error directly. Each is now a line or two of
reason instead of a paragraph of investigation. The list-wrapping comment no
longer quotes the API's internal deserializer error verbatim — that string
described another runtime's implementation, which has no place in this
codebase and would go stale silently; it now just says the API rejects a
bare object.

No behavior change. The full research record is in the CrowdStrike#480 commit message
and the tracking task.
* feat(modules/detections): add alert aggregation tool

Adds falcon_aggregate_alerts, backed by PostAggregatesAlertsV2, so "how many
by X" and "top N" questions can be answered with a single counting call instead
of paging through search results. Swagger marks both PostAggregatesAlertsV1 and
GetAggregateDetects deprecated, so v2 is the only viable endpoint.

The parameter surface is limited to what the live API honors. Ten aggregation
types work (terms, date_histogram, date_range, range, cardinality, max, min,
avg, sum, percentiles); stats and significant_terms return 500 and histogram
returns 400, and since an unrecognized type also yields an opaque 500 the
argument is constrained to a Literal. Four fields the swagger documents —
exclude, min_doc_count, max_doc_count and from — are silently ignored by the
API, verified with values that would have visibly changed the result, so they
are not exposed rather than offered as controls that quietly do nothing.

Sorting accepts only the pipe form. `_count.desc` is rejected with a 400, which
also corrects the shared helper's docstring: the sibling RTR aggregate endpoint
answers the dot form with "invalid sort spec: _count.desc".

include_hidden is a query parameter, not part of the body, and omitting it
counts roughly 25k more alerts than passing false. _base_aggregate had no path
for query parameters, so it gained a `parameters` argument; the change is
additive and its existing tests are untouched.

Three types also need a companion argument — date_histogram needs interval,
date_range needs date_ranges, range needs ranges — and the API answers a spec
missing one with an unactionable 500 at any nesting depth. The check therefore
lives in _base_aggregate and recurses through sub_aggregates, so the eleven
modules that will follow this pattern inherit it rather than reimplement it.

Swagger lists sixteen body fields as required; only type and field actually
are. The FQL guide documents the 58 aggregatable fields confirmed against a
live tenant, which is a narrower set than the filterable fields, and notes that
an unsupported field returns an empty result rather than an error — the same
shape as a genuine zero count.

* docs(modules/base): scope the aggregate sort note to the endpoint

The shared helper claimed the pipe form was the only accepted sort, which
overstates what is known. Sort handling is per-endpoint: on the four aggregate
endpoints where the parameter is exercisable the dot form does return 400, but
that is four of roughly fifty, and two others reject both forms for unrelated
reasons. The helper now says accepted forms vary and leaves the specific claim
to each tool, where falcon_aggregate_alerts already documents what its own
endpoint takes.
…-cql-guidance

# Conflicts:
#	falcon_mcp/modules/cloud.py
#	tests/modules/test_cloud.py
- type-annotate register_tools(server, ->None)
- narrow _base_search_api_call / _base_get_by_ids unions via isinstance(dict) raise path
- annotate params dict as dict[str, Any]
search_hosts dropped the guide on a search-step error and returned a bare
[error], so a caller whose filter used an invalid field had nothing to
correct from. The API answers only "Request failed with status code 400" /
"Invalid filter expression supplied", naming neither the offending field nor
the valid set.

A common case is filtering on `agent_id`, which the Hosts API does not
accept — the field is `device_id`. SEARCH_HOSTS_FQL_DOCUMENTATION already
documents device_id; it simply never reached the caller.

Use _format_fql_error_response, as detections, cloud, firewall, ioc and rtr
already do. The guide and the helper were both already imported here.
…I call

get_detection_details passed `ids` straight through. When a caller rebuilds a
composite ID instead of forwarding it verbatim, the API answers "invalid CID
provided" — which does not say which part of the ID was wrong, so the caller
cannot correct it.

Validate the shapes that arise when an ID is reconstructed: the leading CID
dropped ("ind:<aid>:<id>"), the legacy "ldt:" scheme glued onto a composite
ID, only the trailing detection-id kept, or a different scheme prefix. Each
returns the offending ID, what is wrong with it, and the expected format,
without a request to Falcon.

Deliberately conservative: an unfamiliar-but-valid scheme is passed through to
the API rather than rejected here, so an unreadable-tenant ID still surfaces
as a tenant error rather than a formatting one. The legacy "ldt:<aid>:<id>"
form is also left alone. Only a bare value matching the trailing
detection-id pattern is flagged, so arbitrary single-segment IDs are untouched.
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.