🛡️ Check MS Defender
A comprehensive Nagios plugin for monitoring Microsoft Defender for Endpoint API endpoints. Built with modern Python practices and designed for enterprise monitoring environments.
✨ Features
- 🔐 Dual Authentication - Support for Client Secret and Certificate-based authentication
- 🎯 Multiple Endpoints - Monitor onboarding status, last seen, vulnerabilities, products with CVEs, alerts, and machine details
- 📊 Nagios Compatible - Standard exit codes and performance data output
- 🏗️ Clean Architecture - Modular design with testable components
- 🔧 Flexible Configuration - File-based configuration with sensible defaults
- 📈 Verbose Logging - Multi-level debugging support
- 🐍 Modern Python - Built with Python 3.10+ using type hints and async patterns
🚀 Quick Start
Installation
# Create virtual environment (recommended)
python -m venv /usr/local/libexec/nagios/check_msdefender
source /usr/local/libexec/nagios/check_msdefender/bin/activate
# Install from source
pip install git+https://github.com/lduchosal/check_msdefender.git
Basic Usage
# Check machine onboarding status
check_msdefender onboarding -d machine.domain.tld
# Check last seen (with custom thresholds)
check_msdefender lastseen -d machine.domain.tld -W 7 -C 30
# Check vulnerabilities
check_msdefender vulnerabilities -d machine.domain.tld -W 10 -C 100
# Check products with CVE vulnerabilities
check_msdefender products -d machine.domain.tld -W 5 -C 1
# Check alerts
check_msdefender alerts -d machine.domain.tld -W 1 -C 5
# Check incidents (correlated alert groups)
check_msdefender incidents -d machine.domain.tld -W 1 -C 5
# List all machines
check_msdefender machines
# Get detailed machine info
check_msdefender detail -d machine.domain.tld
📋 Available Commands
| Command | Description | Default Thresholds |
|---|---|---|
onboarding |
Check machine onboarding status | W:1, C:2 |
lastseen |
Days since machine last seen | W:7, C:30 |
vulnerabilities |
Vulnerability score calculation | W:10, C:100 |
products |
Count of vulnerable software with CVEs | W:5, C:1 |
alerts |
Count of unresolved alerts | W:1, C:0 |
incidents |
Count of unresolved incidents (correlated alerts) | W:1, C:0 |
machines |
List all machines | W:10, C:25 |
detail |
Get detailed machine information | - |
Vulnerability Scoring
The vulnerability score is calculated as:
- Critical vulnerabilities × 100
- High vulnerabilities × 10
- Medium vulnerabilities × 5
- Low vulnerabilities × 1
Products CVE Monitoring
The products command monitors installed software with known CVE vulnerabilities:
- Groups CVEs by software (name, version, vendor)
- Shows CVE details including severity levels and disk paths
- Counts vulnerable software (not individual CVEs)
- Default thresholds: Warning at 5 vulnerable software, Critical at 1
- Displays up to 10 software entries with first 5 CVEs per software
--verify-paths: dropping what the host no longer carries
Microsoft's SoftwareVulnerabilitiesByMachine export is a snapshot regenerated on
Microsoft's own cadence, so an uninstalled product keeps being reported for days, and so
does a product that has already been patched. The export carries its own evidence: the
disk and registry paths where each product was seen.
With --verify-paths, the check submits those paths to the machine in a single remote
command and drops every product the host no longer confirms:
removed- none of the reported paths exists any moreupgraded- the file is there, but in a version above the one reported
Everything the host cannot answer for keeps its score: a path that could not be read
(DENIED), an unreachable drive or share, a product the export gave no path for, or a
probe that failed altogether. A failed probe never turns the check green - the raw
score is what gets compared to the thresholds, and the output says so.
The rule is per product, not per path: one copy still on disk in the reported version keeps the whole product counted, however many other copies are gone. A leftover binary that exists but can never run (another CPU architecture, say) is an exception to register, not a stale entry.
The first line always says that the host was asked, even when nothing was excluded, and every path listed under a product carries the host's answer, the paths that keep it in the score first:
2 vulnerable products, score: 547 (raw 712, 1 stale excluded: 165), path verification: 27 paths, 5 absent, 0 unreadable
python 3.12.9.0 (python) - Score: 442, ...
- [PRESENT 3.12.9] c:\program files\...\rust-lldb\win\aarch64\bin\python.exe
- [ABSENT] c:\program files\...\rust-lldb\win\x64\bin\python.exe
Excluded products stay visible under Stale entries excluded, with the reason and the
path that decided it, and the perfdata keeps the raw curve:
products=3319;500;2000 raw=6743 stale=3424 unverified=2
The probe itself is a command template, so the check is not tied to any one transport:
[verify]
# {host} is replaced by the machine's DNS name; the template is split shell-style and
# run without a shell. Default: ssh to the Nagios command account.
command = ssh -o BatchMode=yes -o ConnectTimeout=5 -l nagioscmd {host} powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File c:/programdata/nagioscmd/scripts/test_paths.ps1
# Seconds; spent on top of the API calls, so keep the sum under the Nagios
# service_check_timeout. Default: 20
timeout = 20
The name dialed is the one Defender reports for the machine. When the monitoring server
reaches it under a different name -- a Nagios host_name whose alias is the Defender
name, say -- pass that name with --verify-host, or the probe fails on a host key it has
never seen while every other check on that host works:
check_msdefender products -d $HOSTALIAS$ --verify-paths --verify-host $HOSTNAME$
Paths go in on stdin, one per line; the command answers one line per path:
STATE<TAB>VERSION<TAB>PATH
where STATE is PRESENT, ABSENT, DENIED or ERROR. A reference implementation for
Windows hosts ships with the Arcantel winnagioscmd Ansible role as test_paths.ps1.
Alert Monitoring
The alerts command monitors unresolved security alerts for a machine:
- Counts only unresolved alerts (status ≠ "Resolved")
- Excludes informational alerts when critical/warning alerts exist
- Shows alert details including creation time, title, and severity
- Default thresholds: Warning at 1 alert, Critical at 0 (meaning any alert triggers warning)
Incident Monitoring
An incident is a group of correlated alerts that Microsoft Defender aggregates to describe a
single attack. Each alert carries the incidentId of the incident it belongs to. The incidents
command:
- Queries the device-scoped alerts endpoint (
/api/machines/{id}/alerts) so no alert is dropped behind a tenant-wide page-size cap - Counts distinct unresolved incidents (alerts grouped by
incidentId, status ≠ "Resolved") - Surfaces the most severe alert of each incident in the output
- Default thresholds: Warning at 1 incident, Critical at 0 (meaning any incident triggers warning)
Onboarding Status Values
0- Onboarded ✅1- InsufficientInfo ⚠️2- Unknown ❌
⚙️ Configuration
Authentication Setup
Create check_msdefender.ini in your Nagios directory or current working directory:
Client Secret Authentication
[auth]
client_id = your-application-client-id
client_secret = your-client-secret
tenant_id = your-azure-tenant-id
[settings]
timeout = 30
Certificate Authentication
[auth]
client_id = your-application-client-id
tenant_id = your-azure-tenant-id
certificate_path = /path/to/certificate.pem
private_key_path = /path/to/private_key.pem
[settings]
timeout = 30
Transient API errors
Throttling (429) and transient server errors (500/502/503/504) are retried up to
3 attempts with backoff, honouring Retry-After capped at 5 s, so a passing
Microsoft outage no longer turns into an UNKNOWN that lasts until the next retry.
Timeouts are not retried, to keep the check inside Nagios' service_check_timeout.
Once the retries are spent the check reports a single line, e.g.
UNKNOWN: MS Defender API 503 Service Unavailable after 3 attempts: GET ….
Microsoft Defender API Setup
- Register Application in Azure Active Directory
- Grant API Permissions:
Machine.Read.AllVulnerability.ReadVulnerability.Read.AllAlert.Read.All
- Create Authentication (Secret or Certificate)
- Note Credentials (Client ID, Tenant ID, Secret/Certificate)
🔧 Command Line Options
| Option | Description | Example |
|---|---|---|
-c, --config |
Configuration file path | -c /custom/path/config.ini |
-m, --machineId |
Machine ID (GUID) | -m "12345678-1234-1234-1234-123456789abc" |
-d, --computerDnsName |
Computer DNS Name (FQDN) | -d "server.domain.com" |
-W, --warning |
Warning threshold | -W 10 |
-C, --critical |
Critical threshold | -C 100 |
-v, --verbose |
Verbosity level | -v, -vv, -vvv |
--version |
Show version | --version |
🏢 Nagios Integration
Command Definitions
# Microsoft Defender Commands
define command {
command_name check_defender_onboarding
command_line $USER1$/check_msdefender/bin/check_msdefender onboarding -d $HOSTALIAS$
}
define command {
command_name check_defender_lastseen
command_line $USER1$/check_msdefender/bin/check_msdefender lastseen -d $HOSTALIAS$ -W 7 -C 30
}
define command {
command_name check_defender_vulnerabilities
command_line $USER1$/check_msdefender/bin/check_msdefender vulnerabilities -d $HOSTALIAS$ -W 10 -C 100
}
define command {
command_name check_defender_products
command_line $USER1$/check_msdefender/bin/check_msdefender products -d $HOSTALIAS$ -W 5 -C 1
}
define command {
command_name check_defender_alerts
command_line $USER1$/check_msdefender/bin/check_msdefender alerts -d $HOSTALIAS$ -W 1 -C 5
}
Service Definitions
# Microsoft Defender Services
define service {
use generic-service
service_description DEFENDER_ONBOARDING
check_command check_defender_onboarding
hostgroup_name msdefender
}
define service {
use generic-service
service_description DEFENDER_LASTSEEN
check_command check_defender_lastseen
hostgroup_name msdefender
}
define service {
use generic-service
service_description DEFENDER_VULNERABILITIES
check_command check_defender_vulnerabilities
hostgroup_name msdefender
}
define service {
use generic-service
service_description DEFENDER_PRODUCTS
check_command check_defender_products
hostgroup_name msdefender
}
define service {
use generic-service
service_description DEFENDER_ALERTS
check_command check_defender_alerts
hostgroup_name msdefender
}
🏗️ Architecture
This plugin follows clean architecture principles with clear separation of concerns:
check_msdefender/
├── 📁 cli/ # Command-line interface
│ ├── commands/ # Individual command handlers
│ │ ├── onboarding.py # Onboarding status command
│ │ ├── lastseen.py # Last seen command
│ │ ├── vulnerabilities.py # Vulnerabilities command
│ │ ├── products.py # Products CVE monitoring command
│ │ ├── alerts.py # Alerts monitoring command
│ │ ├── machines.py # List machines command
│ │ └── detail.py # Machine detail command
│ ├── decorators.py # Common CLI decorators
│ └── handlers.py # CLI handlers
├── 📁 core/ # Core business logic
│ ├── auth.py # Authentication management
│ ├── config.py # Configuration handling
│ ├── defender.py # Defender API client
│ ├── exceptions.py # Custom exceptions
│ ├── nagios.py # Nagios plugin framework
│ └── logging_config.py # Logging configuration
├── 📁 services/ # Business services
│ ├── onboarding_service.py # Onboarding business logic
│ ├── lastseen_service.py # Last seen business logic
│ ├── vulnerabilities_service.py # Vulnerability business logic
│ ├── products_service.py # Products CVE monitoring business logic
│ ├── alerts_service.py # Alerts monitoring business logic
│ ├── machines_service.py # Machines business logic
│ ├── detail_service.py # Detail business logic
│ └── models.py # Data models
└── 📁 tests/ # Comprehensive test suite
├── unit/ # Unit tests
├── integration/ # Integration tests
└── fixtures/ # Test fixtures
Key Design Principles
- 🎯 Single Responsibility - Each module has one clear purpose
- 🔌 Dependency Injection - Easy testing and mocking
- 🧪 Testable - Comprehensive test coverage
- 📈 Extensible - Easy to add new commands and features
- 🔒 Secure - No secrets in code, proper credential handling
🧪 Development
Development Setup
# Clone repository
git clone https://github.com/lduchosal/check_msdefender.git
cd check_msdefender
# Create development environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install in development mode
pip install -e .
Code Quality Tools
# Format code
black check_msdefender/
# Lint code
flake8 check_msdefender/
# Type checking
mypy check_msdefender/
# Run tests
pytest tests/ -v --cov=check_msdefender
Building & Publishing
# Build package
python -m build
# Test installation
pip install dist/*.whl
# Publish to PyPI
python -m twine upload dist/*
🔍 Output Examples
Successful Check
DEFENDER OK - Onboarding status: 0 (Onboarded) | onboarding=0;1;2;0;2
Warning State
DEFENDER WARNING - Last seen: 10 days ago | lastseen=10;7;30;0;
Critical State
DEFENDER CRITICAL - Vulnerability score: 150 (1 Critical, 5 High) | vulnerabilities=150;10;100;0;
Alerts Warning
DEFENDER WARNING - Unresolved alerts for machine.domain.com | alerts=2;1;5;0;
Unresolved alerts for machine.domain.com
2025-09-14T10:22:14.12Z - Suspicious activity detected (New high)
2025-09-14T12:00:00.00Z - Malware detection (InProgress medium)
🔧 Troubleshooting
Common Issues
| Issue | Solution |
|---|---|
| Authentication Errors | Verify Azure app permissions and credentials |
| Network Connectivity | Check firewall rules for Microsoft endpoints |
| Import Errors | Ensure all dependencies are installed |
| Configuration Issues | Validate config file syntax and paths |
Debug Mode
Enable verbose logging for detailed troubleshooting:
# Maximum verbosity
check_msdefender vulnerabilities -d machine.domain.tld -vvv
# Check specific configuration
check_msdefender onboarding -c /path/to/config.ini -d machine.domain.tld -vv
Required Network Access
Ensure connectivity to:
login.microsoftonline.comapi.securitycenter.microsoft.comapi-eu.securitycenter.microsoft.comapi-eu3.securitycenter.microsoft.comapi-uk.securitycenter.microsoft.com
📊 Exit Codes
| Code | Status | Description |
|---|---|---|
0 |
OK | Value within acceptable range |
1 |
WARNING | Value exceeds warning threshold |
2 |
CRITICAL | Value exceeds critical threshold |
3 |
UNKNOWN | Error occurred during execution |
🤝 Contributing
We welcome contributions! Here's how to get started:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Guidelines
- Follow PEP 8 style guide
- Add tests for new features
- Update documentation as needed
- Ensure all tests pass before submitting
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Built with nagiosplugin framework
- Uses Azure Identity SDK for authentication
- Powered by Click for CLI interface
⭐ Star this repository if you find it useful!
💖 Sponsor
If this project helps you, consider supporting its development:
Metadata
Release files for check-msdefender 1.4.19
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| check_msdefender-1.4.19.tar.gz | 69.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| check_msdefender-1.4.19-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 123.5 kB
Release files / check_msdefender-1.4.19.tar.gz
| Download URL | check_msdefender-1.4.19.tar.gz |
|---|---|
| Size | 69.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e259b262e2aa1f3fe91312dc9996950492e837cbac81e40ee30b1006ec40322f
|
|
BLAKE2b-256 checksum How to use checksums |
2ccce4b03d597049cd3e1de328fe7b4a477ed5e24b9db45b4175de74812e617a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
pdm/2.29.2 CPython/3.13.15 Darwin/25.6.0
|
Release files / check_msdefender-1.4.19-py3-none-any.whl
| Download URL | check_msdefender-1.4.19-py3-none-any.whl |
|---|---|
| Size | 53.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
075996ffd0a78182ef92b37a4f704d632764dfc1fa29f71e8a7695add070a2f1
|
|
BLAKE2b-256 checksum How to use checksums |
ad13f1a70c3906b4c99fef11d19cfc4745e73b9ebd5b7a23a6319e339da1bc7d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
pdm/2.29.2 CPython/3.13.15 Darwin/25.6.0
|