Private Enrichments

Private enrichments let you extend Malfors with your own data sources.

You can set up your own enrichment server that integrates with your own private databases, or public vendors that Malfors does not yet support.

Entity metadata panel showing private enrichment results from a custom vendor
Private enrichment results appear in entity metadata the same way as managed vendors

Setting Up

Create a Vendor

Navigate to Enrichments and click New private enrichment. A vendor represents your enrichment provider (e.g., your company's internal threat intel API).

Set Credentials

Open your vendor and click Add token to configure an auth token. This token is sent as a Bearer token in the Authorization header when Malfors calls your endpoint.

Always verify the Authorization header to confirm requests are coming from Malfors.

Add Enrichment Sources

Click New source on the vendor page to add an enrichment source. Each source is a specific enrichment capability. Fill in the following fields:

FieldDescription
NameDisplay name for the enrichment source
DescriptionShort description of what this source provides
EndpointURL that Malfors will POST to (must start with https://)
Supported EntitiesEntity types this source can enrich (e.g., Domain, IP Address).

You can add multiple sources per vendor, each with a different endpoint and set of supported entity types.

Implementing Your Enrichment Server

When an enrichment is triggered, Malfors sends a POSTrequest to your source's endpoint. The request includes the entity type and value that are being enriched.

POST <your-endpoint-url>
Content-Type: application/json
Authorization: Bearer <your-token>

{
  "type": "domain",
  "value": "example.com"
}

Your server must respond with 200 OK and a JSON body matching the enrichment response schema.

The schema defines how exactly the data is displayed in the entity metadata panel. Here is a minimal response that will render a code block section:

{
  "result": {
    "format": "malfors",
    "version": 1,
    "sections": [
      {
        "type": "code",
        "title": "Code Section",
        "value": "It works!"
      }
    ]
  }
}

Response Schema

Successful response must be a JSON object with a single result property.

The result object must contain the following properties: format (always malfors), version (always 1), and sections (array of sections).

Each section has a type field that determines how it renders: data-list for key-value pairs, table for tabular data, and code for preformatted text.

Below, each section schema is described.

Data List

Key-value pairs. Use for structured metadata.

{
  "type": "data-list",
  "title": "WHOIS Information",
  "omitEmpty": true,
  "items": [
    { "label": "Registrar", "value": "Cloudflare, Inc.", "type": "organization" },
    { "label": "Created", "value": "2004-08-13" },
    { "label": "Name Servers", "value": "ns1.example.com", "type": "domain" },
    { "label": "Status", "value": null }
  ]
}
FieldTypeRequiredDescription
type"data-list"YesSection type identifier
titlestringYesSection heading
omitEmptybooleanNoIf true, items with null/undefined values are hidden. Default: false.
itemsarrayYesArray of key-value items
items[].labelstringYesLabel for the value
items[].valuestring | number | boolean | nullNoThe value to display
items[].typestringNoEntity type of the value (e.g., "domain", "ip").

Table

Tabular data. Use for lists of records.

{
  "type": "table",
  "title": "DNS Records",
  "columns": [
    { "header": "Type", "key": "recordType", "size": 80 },
    { "header": "Value", "key": "value", "type": "domain" },
    { "header": "TTL", "key": "ttl", "size": 80 }
  ],
  "data": [
    { "recordType": "A", "value": "93.184.216.34", "ttl": 300 },
    { "recordType": "AAAA", "value": "2606:2800:220:1::248", "ttl": 300 }
  ]
}
FieldTypeRequiredDescription
type"table"YesSection type identifier
titlestringYesSection heading
columnsarrayYesColumn definitions
columns[].headerstringYesColumn header text
columns[].keystringYesKey to look up in each data row. Supports nested keys (e.g., "a.b.c").
columns[].sizenumberNoColumn width in pixels. Must be set unless the table has only one column.
columns[].typestringNoEntity type for values in this column.
dataarrayYesArray of row objects

Code

Raw text in a code block. Use for unstructured or preformatted data.

{
  "type": "code",
  "title": "Raw Response",
  "value": "HTTP/1.1 200 OK\nContent-Type: text/html\n\n<!DOCTYPE html>..."
}
FieldTypeRequiredDescription
type"code"YesSection type identifier
titlestringYesSection heading
valuestringYesFreetext content. Newlines and spacing are preserved.

Entity Types

Data List and Table may specify type of values – when provided, Malfors treats a value as an entity – it becomes interactive, can be added to a graph, gets highlighted in case of correlations, etc.

The best practice is to specify types for only those values that might be useful for analysis.

Private enrichments do not support custom entity types, so you should pick the best one from those managed by Malfors:

CategoryTypes
Networkasn, certificate, cpe, cve, domain, ip, netblock, port, url
Identitycredentials, email, national_id, password, person, phone, username
Threat Intelattack_pattern, campaign, indicator, malware, threat_actor, tool
Socialgithub, instagram, linkedin, pinterest, telegram, tiktok, vk, x, youtube
Cryptobtc_address, btc_transaction, eth_address, eth_transaction
Files & Infrastructurefile, hash, infrastructure, location, organization, other, vehicle

Errors

Enrichment endpoint may return errors in case the enrichment fails (e.g. no data found, invalid entity value format, etc.)

In that case, it must respond with 4xx (except 409) error and JSON body with a single plaintext error field:

{
  "error": "Can not parse entity value"
}

Errors are surfaced to the user as is.

Note, that 5xx and 429 errors make Malfors do up to 3 retries. In case of 429, Retry-After header will be respected.

Testing Locally

The best way to test enrichment server without deploying is to use Ngrok, Cloudflared, or similar tunneling service.

It allows you to create a temporary public HTTPS-endpoint that routes to your localhost, thus can process Malfors enrichment requests without deploying.