# Riposte Marketplace

Official catalog of **integrations** for the [Riposte](https://gitea.riposte-labs.com/f3nris/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`)

```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`](templates/manifest.example.yaml) for a
fully-commented starting point (that file is intentionally **not** ingested).
