incidents/get_incidents/ answers with a summary and nothing else: 21 fields,
no hosts, no users, no MITRE, no tags, and not one of the alerts the incident
aggregates. Ingesting through it leaves an incident whose raw payload says
almost nothing about what happened — and the shipped mapper had been written
for a richer shape than the endpoint ever returns, mapping hosts[0], users[0]
and mitre_* that simply are not in that response.
incidents/get_multiple_incidents_extra_data/ returns the same incidents with
39 fields, every alert in full — 156 fields each — and the file and network
artifacts. It is what the reference client fetches through (demisto/content,
CortexXDRIR.get_multiple_incidents_extra_data), and full_alert_fields must be
set or the nested alerts come back trimmed to a handful of fields.
Records arrive as {incident, alerts, network_artifacts, file_artifacts} with
each nested block wrapped as {total_count, data}. The script flattens them, so
every expression written against get_incidents keeps working — the summary's
21 fields are a subset of these 39 — while the alerts and artifacts land beside
them as plain lists, and their total_count says when a list is a sample rather
than the whole set. incident_sources is lifted into a scalar for the same
reason severity was on the alerts side: the incident-field mapper reads dotted
paths and cannot index a list.
get_incidents stays, for cheap polling, and now says in its description what it
does and does not carry.
The mapper maps the aggregate first and the first alert last, so the alert
fills in whatever the aggregate leaves silent — including the detection anchor,
since an XDR incident's detection_time is usually null while its alerts carry
theirs. Verified against the vendor's recorded response
(test_data/get_multiple_incidents_extra_data.json): 33 of 52 entries resolve,
severity critical lands on 5, source reads "XDR Agent", and the anchor falls
through to the alert's detection timestamp.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Riposte Marketplace
Official catalog of integrations for the Riposte SOAR platform.
Riposte syncs this repository (a "git source") and lists every integration it finds so operators can install them in one click. Nothing here is executed at sync time — Riposte only reads manifests and scripts.
How discovery works
When Riposte syncs a source it does a shallow git clone of the selected branch
and walks the whole tree looking for files named exactly manifest.yaml.
- Each
manifest.yamlis one integration. Its containing directory is the package root. Put one integration per directory. - Scripts are collected from the package directory and its
scripts/subdirectory: every*.pyfile becomes a command implementation, keyed by filename without the extension. So a command withid: get_ip_reportis backed byget_ip_report.py. - Files not named
manifest.yamlare ignored as integration roots, so templates likemanifest.example.yamlare never ingested.
integrations/
└── <integration-id>/
├── manifest.yaml # required — the integration definition
└── scripts/ # optional — only for script-based commands
└── <command-id>.py
Manifest schema (manifest.yaml)
id: my_integration # required, unique, slug
name: My Integration # required, human name
version: 1.0.0 # semver
description: What it does.
changelog: "1.1.0 — notes" # optional: shown on update so operators can assess risk
category: enrichment # free text (enrichment, containment, ticketing…)
# Per-instance configuration the operator fills when creating an instance.
# JSON-Schema shape: { properties: {...}, required: [...] }.
config_schema:
properties:
base_url:
type: string # string | number | boolean
description: API base URL
default: https://api.example.com/v1
api_key:
type: string
description: API key
x-soar-sensitive: true # stored encrypted in the vault, never returned
required:
- api_key
# Authentication methods, referenced by commands via `auth_ref`.
auth:
- id: apikey
type: api_key # api_key | bearer | basic | oauth2_client_credentials
in: header # header | query
name: x-apikey # header/query parameter name
value_template: "{{secret}}" # {{secret}} is replaced by the secret_field value
secret_field: api_key # which config_schema field holds the secret
commands:
# --- Request-based command (recommended, no code) -----------------------
- id: get_ip_report
name: Get IP report
description: Reputation for an IP address.
inputs_schema:
properties:
ip:
type: string
description: IP address to look up
required:
- ip
outputs_schema:
properties: {}
request:
method: GET # GET | POST | PUT | PATCH | DELETE
path: /ip/{ip} # {ip} is filled from inputs
query: [] # input names sent as query params
body: [] # input names sent as JSON body fields
auth_ref: apikey
# --- Script-based command -----------------------------------------------
# Omit `request` and provide scripts/<id>.py instead. The script receives the
# resolved inputs + instance config and MUST print one JSON object to stdout.
- id: enrich_custom
name: Custom enrichment
description: Runs scripts/enrich_custom.py in a sandbox.
inputs_schema:
properties:
indicator:
type: string
required:
- indicator
outputs_schema:
properties: {}
A command is request-based when it has a request: block, or
script-based when a matching scripts/<command-id>.py exists. Prefer
request-based commands: they need no sandbox and are easier to audit.
Adding this catalog to Riposte
In Riposte → Integrations → Marketplace → Add source:
| Field | Value |
|---|---|
| Name | Official marketplace |
| Git URL | https://gitea.riposte-labs.com/f3nris/riposte-marketplace.git |
| Branch | main |
| Provider | Gitea (sets the right auth scheme for private repos) |
| Token | a read-only token if the repo is private; leave empty if public |
Then Sync. Discovered integrations appear in the marketplace, ready to install.
Contributing an integration
- Create
integrations/<id>/manifest.yaml(one directory per integration). - Add
scripts/<command-id>.pyonly for script-based commands. - Bump
version(semver) on every change — Riposte tracks versions per source. - Validate the YAML parses and
id/nameare set. - Open a merge request.
See templates/manifest.example.yaml for a
fully-commented starting point (that file is intentionally not ingested).