Important: This version uses the new Upload Indicators API (STIX objects upload API). If you want the old Microsoft Graph API approach, check out the old-msgraph branch.
If you do not want to use the new Upload Indicators API, set
new_upload_apitoFalsein your configuration. Note that the old approach is going to be deprecated by Microsoft.
MISP2Sentinel uploads threat intelligence indicators from a MISP instance to Microsoft Sentinel. It uses the STIX objects upload API (https://api.ti.sentinel.azure.com) to push indicators in STIX 2.1 format.
The script queries MISP for events via PyMISP, converts the attributes to STIX indicator objects and sends them to Sentinel in batches. Indicators appear in the Sentinel Threat Intelligence blade and can be used in analytics rules, hunting queries and workbooks.
MISP2Sentinel is also available through the Microsoft Sentinel Content Hub.
- Python 3 (3.10 or later recommended)
- A running MISP instance with API access
- An Azure tenant with a Microsoft Sentinel workspace
- An Azure App registration with the Microsoft Sentinel Contributor role
Register a new application in the Microsoft Application Registration Portal.
- Sign in to the Application Registration Portal.
- Choose New registration.
- Enter an application name and choose Register.

- Note the Application (client) ID and the Directory (tenant) ID.
- Under Manage > Certificates & secrets, choose New client secret and add a description. Copy the secret value straight away; it will not be shown again.
The Azure App needs the Microsoft Sentinel Contributor role on each workspace you want to connect to.
- Navigate to the Log Analytics workspace you wish to connect to.
- Select Access control (IAM).
- Select Add > Add role assignment.
- In the Role tab, select Microsoft Sentinel Contributor > Next.
- On the Members tab, select Assign access to > User, group, or service principal.
- Click Select members. Microsoft Entra applications are not shown by default, so search for your application by name.
- Select Review + assign.
- Take note of the Workspace ID. You can find it on the Overview page of the workspace.
- Go to the Sentinel service in the Azure portal.
- Choose the workspace where you want to import indicators.
- Under Content management, click Content hub.
- Find and select the MISP2Sentinel solution.
- Click Install/Update.
Instead of running the script on a server or alongside MISP, you can run it as an Azure Function. See AzureFunction/README.MD for instructions. The Azure Function supports the same indicator upload and cleanup features as the standalone script; the cleanup features are configured via environment variables (see Indicator cleanup).
MISP2Sentinel needs an API key to read events from MISP. Create one under Global Actions > My Profile > Auth keys. The key can be set to read-only because the integration does not modify any MISP data.
- Verify that
python3is installed on your system. - Clone the repository:
git clone https://github.com/cudeso/misp2sentinel.git cd misp2sentinel - Create and activate a virtual environment:
virtualenv sentinel source sentinel/bin/activate - Install the dependencies:
pip install -r requirements.txt
All settings live in config.py. Copy the provided config.py.default to config.py and fill in the values described below.
If you set the environment variable key_vault_name to the name of your Azure Key Vault, the script will attempt to read secrets from there first. Otherwise it falls back to environment variables and finally to the values in config.py itself.
The ms_auth dictionary holds the connection details for Sentinel. Fill in the tenant (Directory ID), client_id (Application client ID), client_secret (secret value) and workspace_id from the Azure setup steps above.
new_upload_api must be set to True. This is the only supported upload method going forward; the legacy Graph API has been deprecated by Microsoft and will be removed in a future release.
ms_auth = {
'tenant': '<tenant>',
'client_id': '<client_id>',
'client_secret': '<client_secret>',
'new_upload_api': True,
'scope': 'https://management.azure.com/.default',
'workspace_id': '<workspace_id>',
}ms_api_version = "2024-02-01-preview"
ms_delete_api_version = "2025-09-01"
ms_max_indicators_request = 100 # Maximum indicators per request (max 100)
ms_max_requests_minute = 100 # Maximum requests per minute (max 100)ms_api_version: The STIX objects upload API version. Leave this at"2024-02-01-preview".ms_delete_api_version: The management API version used by--verify-recent-toids-change,--delete-outdated-indicatorsanddelete-from-azure.py. Leave this at"2025-09-01"unless Microsoft publishes a newer stable version.ms_max_indicators_request: How many indicators to include in a single API call. The API allows at most 100.ms_max_requests_minute: How many API calls to make per minute. The API allows at most 100. Combined, these settings give a maximum throughput of roughly 10,000 indicators per minute.
misp_key = '<misp_api_key>'
misp_domain = '<misp_url>'
misp_verifycert = Falsemisp_key: The MISP API key you created earlier.misp_domain: The full URL of your MISP instance, for examplehttps://misp.example.com.misp_verifycert: Set toTrueif your MISP instance uses a trusted TLS certificate, orFalseto skip certificate validation (common with self-signed certificates).
The misp_event_filters dictionary controls which MISP events are fetched. Recommended settings:
misp_event_filters = {
"published": 1,
"tags": [ "workflow:state=\"complete\""],
"enforceWarninglist": True,
"includeEventTags": True,
"publish_timestamp": "14d",
}"published": 1fetches only published events."tags"lets you restrict to events with specific tags, such as a workflow state."enforceWarninglist": Trueskips indicators that match a MISP warninglist entry. This is strongly recommended, but depends on your warninglists being enabled."includeEventTags": Truecarries event-level tags over to individual indicators, giving them more context in Sentinel."publish_timestamp": "14d"fetches events published in the last 14 days.
A note on publish_timestamp: use 14 days (or more) for the initial run so that you backfill your Sentinel workspace. After that, set this value to match your synchronisation frequency. If you run the script every 12 hours, set it to "12h". There is no benefit in re-fetching older events that have already been uploaded.
misp_event_limit_per_page = 500This setting controls how many events are fetched per MISP query. Lower it if the script consumes too much memory.
days_to_expire = 50
days_to_expire_start = "valid_from" # "valid_from" or "current_date"days_to_expire: Number of days after which an indicator expires in Sentinel.days_to_expire_start: Whether the expiry is calculated from the indicator'svalid_fromtimestamp or from the current date.
You can override the expiry per indicator type using days_to_expire_mapping:
days_to_expire_mapping = {
"ipv4-addr": 1,
"ipv6-addr": 1,
"domain-name": 1,
"url": 1,
"file:hashes": 1,
}If days_to_expire_ignore_misp_last_seen is set to True (the default), the script ignores the last_seen value from MISP and always calculates expiry using days_to_expire. Set it to False to honour the MISP last_seen date where available.
default_confidence = 50Sets the default STIX confidence value (0-100) for indicators. This can be overridden per indicator through MISP tags that use the misp-confidence taxonomy.
sourcesystem = "MISP" # Source system name sent to Sentinel
verbose_log = False # Enable debug-level logging
log_file = "misp2sentinel.log" # Path to the log file
dry_run = False # Process indicators without uploading them
write_parsed_indicators = False # Write parsed indicators to parsed_indicators.txt
write_parsed_eventid = False # Log event IDs being processed
sentinel_write_response = False # Write Sentinel API responses to sentinel_response.txt
remove_pipe_from_misp_attribute = True # Strip composite attributes (e.g. filename|sha256) to the first value
ignore_localtags = True # Skip MISP tags marked as local
misp_flatten_attributes = True # Flatten MISP object attributes into the event attribute list
misp_remove_eventreports = True # Remove event reports from MISP events before processing
timeframe_toids_change = "1d" # Timeframe for --verify-recent-toids-change
ms_useragent = "MISP-1.0" # User-Agent header sent to the Sentinel API
ms_check_if_exist_in_sentinel = False # Check if an indicator already exists in Sentinel before uploadingwrite_parsed_eventid: When set toTrue, the script logs the event IDs it processes. Useful for debugging which events are being picked up by the filters.misp_remove_eventreports: When set toTrue, event reports attached to MISP events are stripped before processing. Defaults toTrue.timeframe_toids_change: The MISP search timeframe used by--verify-recent-toids-change. Defaults to"1d". Accepts the same formats as MISP timestamps (e.g."12h","7d").ms_useragent: The User-Agent string sent with API requests. Defaults to"MISP-1.0".ms_check_if_exist_in_sentinel: When set toTrue, the script checks whether each indicator already exists in Sentinel before uploading. This avoids duplicates but adds an API call per indicator, which slows down the synchronisation. Requiressubscription_id,resourceGroupNameandworkspaceNameinms_auth(see the deleting indicators section).
Make sure the virtual environment is active, then run:
python script.py
You can also process a single MISP event by passing its UUID:
python script.py --uuid <event-uuid>
When an analyst sets the to_ids flag to False on a MISP attribute, the corresponding indicator should no longer be in Sentinel. Use the --verify-recent-toids-change flag to find these attributes and delete the matching indicators in Sentinel:
python script.py --verify-recent-toids-change
This queries MISP for attributes where to_ids was recently set to False, looks them up in Sentinel and deletes them. Only indicators whose source matches sourcesystem (default "MISP") are considered, so indicators from other feeds are never affected. The timeframe is controlled by timeframe_toids_change in config.py (default: "1d"). The flag can be combined with --uuid.
Set dry_run = True to preview which indicators would be deleted without actually removing them.
Use the --delete-outdated-indicators flag to remove indicators whose validUntil date has passed:
python script.py --delete-outdated-indicators
This queries Sentinel for all indicators from the configured sourcesystem whose validUntil is in the past, and deletes them. The flag can be combined with --uuid.
Set dry_run = True to preview which indicators would be deleted without actually removing them.
Indicators are deleted by calling the Microsoft Sentinel ThreatIntelligence management API:
DELETE https://management.azure.com/subscriptions/{subscriptionId}
/resourceGroups/{resourceGroupName}
/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}
/providers/Microsoft.SecurityInsights/threatIntelligence/main/indicators/{name}
?api-version=2025-09-01
The indicator immediately disappears from the Sentinel Threat Intelligence blade and from queryIndicators results. Note that the underlying ThreatIntelligenceIndicator row in the Log Analytics workspace is not physically removed: its IsDeleted column flips from false to true. Hunting queries should filter on IsDeleted == false to ignore deleted indicators.
The DELETE call requires the subscription_id, resourceGroupName and workspaceName keys to be set in ms_auth. The API version is configurable via ms_delete_api_version in config.py (default: "2025-09-01").
Both --verify-recent-toids-change and --delete-outdated-indicators use this DELETE mechanism.
Use check-config.py to test connectivity to both MISP and Azure before running the main script:
python check-config.py
This will attempt to authenticate against MISP (and fetch one event) and obtain an access token from Azure, reporting any issues.
To keep Sentinel up to date, schedule the script with cron (Linux) or Task Scheduler (Windows). For example, to run every hour:
0 * * * * cd /path/to/misp2sentinel && sentinel/bin/python script.py
If you run the script on an Azure VM, you can store secrets in Azure Key Vault instead of keeping them in config.py.
- Enable a managed identity for the virtual machine.
- Create an Azure Key Vault.
- Add the secrets
MISP-KeyandClientSecretin the Key Vault secrets tab. - Give the VM's managed identity the
Readerrole on the Key Vault. - Give the same managed identity
GetandListpermissions in the Key Vault access policy. - Make sure the Key Vault libraries are installed (they are included in
requirements.txt):azure-keyvault-secretsazure-identity
Then uncomment and configure the Key Vault section in config.py:
import os
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential
keyVaultName = "<unique-name>"
KVUri = f"https://{keyVaultName}.vault.azure.net"
credential = DefaultAzureCredential()
client = SecretClient(vault_url=KVUri, credential=credential)
retrieved_mispkey = client.get_secret('MISP-Key')
retrieved_clientsecret = client.get_secret('ClientSecret')After that, update the relevant variables to use the retrieved values:
misp_key = retrieved_mispkey.value'client_secret': retrieved_clientsecret.valueTo get the most out of the Sentinel integration, enable the following MISP taxonomies:
- MISP taxonomy (used for confidence levels)
- sentinel-threattype (mapped to STIX
indicator_types) - kill-chain (mapped to STIX kill chain phases)
These taxonomies are not required for the integration to work, but they add useful context for analysts working with the indicators in Sentinel.
The default confidence value (set in default_confidence) can be overridden per indicator using the MISP confidence taxonomy. The tag is translated to a numerical value as defined by MISP_CONFIDENCE in constants.py.
You can tag events or attributes with the sentinel-threattype taxonomy. The integration maps these tags to STIX indicator_types, which Sentinel displays as the threat type.
Kill chain tags are translated to STIX kill chain phases following the Lockheed Martin Cyber Kill Chain model.
TLP (Traffic Light Protocol) tags on events and attributes are translated to STIX marking definitions. An attribute-level TLP takes precedence over an event-level TLP. If no TLP tag is set at all, tlp:white is applied by default (configurable via SENTINEL_DEFAULT_TLP in constants.py).
You can control which tags get synchronised using two variables in constants.py:
MISP_TAGS_IGNORE: A list of tag prefixes to exclude. It already contains the default tags added during STIX conversion, but you can extend it with your own entries.MISP_ALLOWED_TAXONOMIES: A list of allowed taxonomy prefixes. Only tags belonging to these taxonomies will be included. Leave the list empty to allow all taxonomies. For example:["tlp", "admiralty-scale", "type"].
In MISP, galaxies and clusters (such as threat actors, malware families or ATT&CK techniques) are internally represented as tags. By default, galaxy tags are not synchronised to Sentinel because MISP_TAGS_IGNORE contains the "misp-galaxy:" prefix. This keeps the indicator labels in Sentinel concise.
If you want galaxy and cluster tags to appear on your Sentinel indicators, remove "misp-galaxy:" from the MISP_TAGS_IGNORE list in constants.py.
Only attributes with a STIX pattern type are synchronised. Attributes of type yara or sigma, for instance, are skipped. The list of accepted MISP attribute types is defined by UPLOAD_INDICATOR_MISP_ACCEPTED_TYPES in constants.py.
If you want to synchronise MISP attribute types that do not have a native STIX pattern (such as iban), you can add them to the MISP_CUSTOM_ATTRIBUTE frozenset in constants.py. These attributes are sent to Sentinel as freetext indicators, similar to the freetext indicator you can create manually in the Azure portal.
Requirements:
- The attribute must have the to_ids flag set in MISP.
- The attribute type must be listed in
MISP_CUSTOM_ATTRIBUTE.
This is useful for indicator types like IBAN numbers, phone numbers or other identifiers that are not part of the STIX standard but that you still want to track in Sentinel.
The "Created by" field in Sentinel refers to the UUID of the MISP organisation that created the event. Because Sentinel does not yet fully support STIX identity objects, this appears as a textual reference rather than a linked entity.
When misp_flatten_attributes is set to True, the script extracts all attributes from MISP objects and treats them as standalone attributes. This means you lose some contextual information (although a comment is added noting which object the attribute belonged to), but it ensures that every attribute that can be converted to a STIX indicator is synchronised.
MISP2Sentinel requires outbound HTTPS (port 443) access to the hosts listed below. In a corporate environment with a proxy or firewall, make sure these domains are whitelisted.
| Domain | Required | Used for |
|---|---|---|
Your MISP instance (e.g. misp.example.com) |
Always | Fetching events and attributes via the MISP API |
login.microsoftonline.com |
Always | Azure AD OAuth2 token acquisition |
api.ti.sentinel.azure.com |
When new_upload_api is True |
Uploading STIX indicators to Sentinel |
sentinelus.azure-api.net |
When new_upload_api is False (legacy) |
Uploading indicators via the legacy API |
management.azure.com |
When using ms_check_if_exist_in_sentinel, --verify-recent-toids-change or delete-from-azure.py |
Querying and deleting indicators in Sentinel |
If you use Azure Key Vault for secret storage, also allow access to your vault endpoint (<vault-name>.vault.azure.net).
Check the following:
- Is the MISP event published?
- Is the
to_idsflag set toTrueon the attribute? - Is the attribute type one of the supported types (listed in
UPLOAD_INDICATOR_MISP_ACCEPTED_TYPESinconstants.py)? - Has the
valid_untildate already passed? Expired indicators are skipped.
This means the Azure authentication failed. Double-check that tenant, client_id, client_secret and workspace_id are correct.
tenantis the Directory (tenant) ID. Search for Tenant Properties in the Azure portal.client_idis the Application (client) ID. Find it under App Registrations in the Azure portal.workspace_idis the Workspace ID shown on the Overview page of your Log Analytics workspace.
- Set
write_parsed_indicators = Trueto write the STIX indicators sent to Sentinel toparsed_indicators.txt. This file is overwritten on each run. - Set
sentinel_write_response = Trueto write error responses from Sentinel tosentinel_response.txt.
- The blog post Figuring out MISP2Sentinel Event Filters walks through common filter configurations.
- The MISP playbook Using timestamps in MISP explains time-based filters in detail.
- The MISP OpenAPI specification for event search documents all available filter parameters.






