f3nris fd4b1b18f7 feat(sentinelone): fetch the unified (UAM) alerts (v1.8.0)
The package could already write to a unified alert — update its status and
its analyst verdict — but never read one. The only fetch was get_alerts,
which reads /cloud-detection/alerts: a different object, covering only the
detections the STAR and custom rules raise. Everything SentinelOne surfaces
through the console-wide alert model — cloud, identity, third-party — had
no way into an incident.

get_unified_alerts reads the unifiedalerts GraphQL endpoint, alongside the
two mutations already pointed at it. Notes on the shape it returns:

- edges/node is flattened to a plain data[]. The envelope carries no id of
  its own, so a rule dedup'ing on a top-level id would key every alert on
  nothing; flattened, the ingest block matches the REST commands in this
  package (results_path: data, dedup_key: id).
- Sorted ascending, so a run truncated by the limit leaves the watermark on
  the oldest unseen alert instead of skipping everything behind the newest.
- created_after is normalised from ISO8601, epoch seconds or epoch
  milliseconds: the scheduler injects the watermark raw, in whichever of the
  three formats the rule was configured with. The script builds the GraphQL
  filter itself, so the incremental input stays a plain timestamp.
- A GraphQL error arrives as HTTP 200 with an errors array. Left unread, an
  unusable query would look like an endless run of empty, successful fetches.

The mapper covers 74 fields. Its severity branch ends on the raw value
rather than a number, so an alert with no severity — or a level SentinelOne
adds later — falls through to the rule's default instead of being asserted
Informational on data that never said so. A UAM alert also carries fields
with no honest OCSF home (assignee, ticketId, the kubernetes controllers);
they are fetched and left in the raw payload rather than forced into an
unrelated field.

The package default ingestion still points at get_threats: the new command
is opt-in from the Collect editor, so no existing instance changes what it
collects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 16:16:46 +02:00

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.yaml is 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 *.py file becomes a command implementation, keyed by filename without the extension. So a command with id: get_ip_report is backed by get_ip_report.py.
  • Files not named manifest.yaml are ignored as integration roots, so templates like manifest.example.yaml are 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

  1. Create integrations/<id>/manifest.yaml (one directory per integration).
  2. Add scripts/<command-id>.py only for script-based commands.
  3. Bump version (semver) on every change — Riposte tracks versions per source.
  4. Validate the YAML parses and id/name are set.
  5. Open a merge request.

See templates/manifest.example.yaml for a fully-commented starting point (that file is intentionally not ingested).

S
Description
No description provided
Readme 6.1 MiB
Languages
Python 100%