Skip to content

Add Cortex XSOAR adapter#299

Draft
maximelb wants to merge 2 commits into
masterfrom
add-cortex-xsoar-adapter
Draft

Add Cortex XSOAR adapter#299
maximelb wants to merge 2 commits into
masterfrom
add-cortex-xsoar-adapter

Conversation

@maximelb

Copy link
Copy Markdown
Contributor

What

Adds a USP adapter that ingests incidents from Palo Alto Networks Cortex XSOAR (formerly Demisto) into LimaCharlie. Incidents are shipped verbatim (LimaCharlie parses/maps in the cloud). Closes the XSOAR connectivity gap — XSOAR had no existing adapter or extension.

Works against both Cortex XSOAR 6 (on-prem) and 8 (cloud) — the request/response shapes are identical; only the API path prefix (/xsoar/public/v1 on v8) and the auth headers differ, both selected from config.

API

Built entirely from the official/SDK-sourced API shapes (no live instance available to me):

  • Endpoint: POST /incidents/searchdemisto-py swagger (searchIncidents, SearchIncidentsData, IncidentFilter, Order); sample payload from SearchIncidentsV2.
  • Auth: standard key (Authorization: <key>), XSOAR 8 adds x-xdr-auth-id; advanced key is signed — Authorization = sha256hex(apiKey + nonce + timestamp) with x-xdr-nonce / x-xdr-timestamp / x-xdr-auth-id (plain SHA-256, not HMAC). Per the demisto-py auth client and XSOAR 8 API docs.

How it works

  • Incremental by modified time with a high-water cursor (modified:>="<cursor>", ascending sort). Updated/reopened incidents are re-collected so the platform sees the incident's evolution.
  • Exactly-once per version: dedup keyed on id + modified (re-fetched boundary records and unchanged re-polls suppressed; genuine updates re-ship). First poll bounded by initial_lookback.
  • Pagination: 0-indexed page/size over {"data":[...],"total":N}, walked to a short page / total, capped by max_pages.
  • Errors: source-side failures are non-fatal warnings (skip + retry next poll, matching the cloud-sensor host's restart semantics); 5xx/429 retried with backoff. Only failures delivering to LimaCharlie are fatal.

Testing

go test ./cortex_xsoar/... — passes (also -race). End-to-end against an in-memory mock that reproduces standard + advanced auth, the modified filter, ascending sort, 0-indexed pagination and the {data,total} envelope, plus unit tests for validation, URL/version resolution, query/body building, parsing (precision, zero-time, offsets), id resolution, dedup-by-version, and the advanced signature.

⚠️ Not yet live-verified against a real Cortex XSOAR instance (no credentials available); the mock and fixtures mirror the documented shapes. Worth a live smoke-test against a 6.x and an 8.x tenant before GA, and confirming the modified:>= Lucene query behaves as expected on a real instance.

Follow-ups (not in scope here)

  • Optional: ingest other XSOAR objects (war-room entries, indicators) — same pattern.
  • Optional: push/response back into XSOAR (the customer ask is ingest-only for now).

🤖 Generated with Claude Code

Adds a USP adapter that polls the Palo Alto Networks Cortex XSOAR (formerly
Demisto) "search incidents by filter" API and ships each incident verbatim to
LimaCharlie. Works against both Cortex XSOAR 6 (on-prem) and 8 (cloud).

- Auth: standard and advanced (SHA-256 signed) API keys; x-xdr-auth-id for
  XSOAR 8 and advanced keys.
- Incremental collection by incident "modified" time with a high-water cursor;
  updated incidents are re-collected, deduplicated per id+modified so each
  distinct version ships exactly once.
- 0-indexed page/size pagination over the {"data":[...],"total":N} envelope.
- Source-side errors are non-fatal warnings (retried next poll); 5xx/429 retried
  with backoff.

Tested end-to-end against an in-memory mock reproducing the documented API
shapes (auth, modified filter, ascending sort, pagination, envelope). Not yet
live-verified against a real instance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BgvY1mfwhUa5FZzwHPctH
lcbill
lcbill previously approved these changes Jun 25, 2026
From a thorough self-review:

- Paginate on the raw page length, not the post-filter count. A persistently
  malformed record on an otherwise-full page would make the page look short and
  end the walk early, skipping later pages each poll. extractIncidents now also
  returns the raw record count; the short-page/total checks use it.
- Cap the cursor at "now". A single incident with a future-dated "modified"
  (clock skew / a bad record) would have jumped the cursor ahead and starved
  collection of everything between now and that future time.
- Stop the poll loop on parent-context cancellation, not only Close(); otherwise
  a cancelled ctx makes every poll fail fast and the loop spins once per interval.

Added regression tests for all three (raw-length pagination end-to-end, malformed
record on a full page, future-dated cursor cap) plus the extractIncidents pageLen
unit assertions. All tests pass under -race.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BgvY1mfwhUa5FZzwHPctH
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