Security content lifecycle management for Microsoft Sentinel and Microsoft Defender XDR.
A Python CLI plus GitHub Actions pipeline that manages the full lifecycle of detection rules across Microsoft Sentinel and Microsoft Defender XDR from a single repo, single tenant, with Git as the source of truth. Analysts author lean YAML; the pipeline validates, lints, plans, applies via ARM REST + Graph beta, watches for portal-side drift, and writes a hash-chained audit trail of every write.
CLI.
contentopsis the only entry point afterpip install -e .. Bothcontentops <cmd>andpython -m contentops <cmd>work.
ContentOps logo
_____ _ _ ____
/ ____| | | | | / __ \
| | ___ _ __ | |_ ___ _ __ | |_| | | |_ __ ___
| | / _ \| '_ \| __/ _ \ '_ \| __| | | | '_ \/ __|
| |___| (_) | | | | || __/ | | | |_| |__| | |_) \__ \
\_____\___/|_| |_|\__\___|_| |_|\__|\____/| .__/|___/
| |
|_|
One linear path. ~30 minutes from clone to first scaffolded rule.
# 1. Clone + install
git clone https://github.com/KustoKing/ContentOps.git
cd ContentOps
python -m venv .venv
# Activate (pick your shell):
.\.venv\Scripts\Activate.ps1 # Windows PowerShell
# source .venv/bin/activate # macOS / Linux bash
python -m pip install -r requirements.txt
python -m pip install -e .
# 2. Wire credentials. The three adopter personas + locked-down
# Windows recipes are in docs/quickstart.md; the shortest path
# on a personal laptop is `az login` (no .env needed).
copy config\tenant.yml.example config\tenant.yml # tenant + workspace IDs (gitignored)
# 3. Pre-flight + first rule
python -m contentops doctor # green = ready (L1 install only)
python -m contentops new sentinel_analytic my-first-rule
notepad detections\sentinel_analytic\my-first-rule.yml
python -m contentops lint --strict # PR-time gate, locally
python -m contentops plan --role prod # read-only diff vs the tenant
python -m pipandpython -m contentopsare the recommended invocations — they work on locked-down corporate Windows machines where Device Guard / WDAC blocks pip-installed.exeshims. The barecontentopsconsole script also works afterpip install -e ..
When that works, read docs/OPERATOR_GUIDE.md
for the daily flow and the when-things-break decision tree.
One command answers "is everything wired correctly?" — read-only, safe against production tenants:
contentops conformance # full layered report (L1–L7)
contentops conformance --scope L1,L2 # local install + tenant config only
contentops conformance --format json # machine-readable sidecarLayers — each printed with PASS / FAIL / SKIP and an actionable remediation hint on failure:
| Layer | Verifies |
|---|---|
| L1 | Local install: Python, package import, envelope parse, audit chain |
| L2 | Tenant config: tenant.yml parses, GUIDs aren't placeholders, auth env set |
| L3 | OIDC / token acquisition (ARM + Graph) via DefaultAzureCredential |
| L4 | Microsoft Graph permissions on the App Registration (app role assignments + federated credential subjects) |
| L5 | Azure RBAC: every configured Sentinel workspace exists, RG exists, Sentinel onboarded |
| L6 | Functional reach: list alertRules, list detectionRules, KQL print 1 |
| L7 | GitHub repo: required secrets exist, branch protection requires expected checks (skipped without gh CLI) |
The full reference, including how to override the expected-permissions
list per-fork via .contentops-conformance.yml, lives in
docs/operations/deployment-conformance.md.
For a separate non-destructive end-to-end CLI matrix (every leaf
command exercised against a tmpdir sandbox, three modes:
offline / mocked / live), see
docs/operations/e2e-capability-tests.md.
Enterprise adopters typically host the code in their own GitHub Enterprise (GHEC or GHES) org rather than working directly against the public mirror. The recommended topology is:
origin→ your private GHE repo (where you push, run CI, branch-protect)upstream→KustoKing/ContentOps(the nightly-rebuilt public mirror)
You only do the import once. After that you periodically fetch from
upstream and merge into your origin/main.
# 1. Sign in to your GitHub Enterprise host
gh auth login --hostname github.<your-ghe>
gh auth status --hostname github.<your-ghe>
# Optional: clear stale cached Git credentials if pushes 401/403.
# When prompted enter `protocol=https`, `host=github.<your-ghe>`,
# then press Enter twice.
# git credential-manager erase
# 2. Clone the public mirror into a working directory
cd C:\work
git clone https://github.com/KustoKing/ContentOps.git contentops
cd contentops
# 3. Re-wire remotes: public becomes upstream, private GHE becomes origin
git remote rename origin upstream
git remote add origin https://github.<your-ghe>/<org>/<repo>.git
git remote -v
# 4. Push main + tags to your private GHE repo
git branch --show-current # confirm branch name
git push -u origin main # or `master` if that's your default
git push origin --tags# 1. Sign in to your GitHub Enterprise host
gh auth login --hostname github.<your-ghe>
gh auth status --hostname github.<your-ghe>
# If cached creds misbehave, inspect `git config --global credential.helper`
# and clear the relevant entry (osxkeychain, libsecret, store, etc.).
# 2. Clone the public mirror into a working directory
mkdir -p ~/work && cd ~/work
git clone https://github.com/KustoKing/ContentOps.git contentops
cd contentops
# 3. Re-wire remotes: public becomes upstream, private GHE becomes origin
git remote rename origin upstream
git remote add origin https://github.<your-ghe>/<org>/<repo>.git
git remote -v
# 4. Push main + tags to your private GHE repo
git branch --show-current # confirm branch name
git push -u origin main # or `master` if that's your default
git push origin --tagsThe public mirror is rebuilt nightly, so a weekly merge is usually plenty. Same three commands on both shells:
git fetch upstream
git merge upstream/main
git push origin mainSee docs/quickstart.md
for the mirror's sync cadence, and
docs/quickstart.md
if a GHEC org with SAML SSO returns 404 on git push or gh repo view.
Once you're past Day-1, these are the docs to bookmark.
docs/quickstart.md— deploy your first detection in 15 minutes.docs/glossary.md— pipeline vocabulary on one page.docs/OPERATOR_GUIDE.md— daily flow, when-things-break decision tree.docs/operations/authentication-setup.md— App Registration + OIDC first-timer primer.docs/operations/tenant-config-modes.md— three tenant-config layouts (committed, secret, vars+secrets).docs/operations/multi-workspace.md— single tenant, N Sentinel workspaces; integration vs. prod.docs/development/local-testing.md—.env, RBAC, pre-flight.docs/development/live-integration-tests.md— live Azure validation.docs/reference/architecture.md— handler protocol, envelope schema, hash chain, state file, Mermaid diagrams.docs/reference/workflows.md— index of every GitHub Actions workflow with triggers and runtimes.docs/reference/feature-catalog.md— every CLI command, workflow, lint rule.docs/reference/asset-coverage.md— six implemented asset kinds.docs/reference/audit-trail.md— JSONL schema, query examples, retention.docs/reference/gap-assessment.md— what isn't built yet. Honest.docs/reference/roadmap.md— proposed features.DESIGN.md— full design (contract-with-future-self).docs/archive/DESIGN.md— original v1 design, kept for context.
| Layer | Technology |
|---|---|
| Language | Python 3.12+ |
| CLI | Click |
| HTTP | httpx |
| Validation | Pydantic v2 |
| YAML | PyYAML (custom block-scalar dumper) |
| Auth | azure-identity (DefaultAzureCredential) |
| CI/CD | GitHub Actions (OIDC) |
| Sentinel | ARM REST 2025-07-01-preview |
| Defender | Microsoft Graph Security beta |
Single tenant. Single-tenant App Registration with OIDC federated credentials for production; client-secret fallback for local dev.
See CONTRIBUTING.md for the local setup, the
DCO sign-off requirement, branch protection, and the status
promotion lifecycle. Community expectations are documented in
CODE_OF_CONDUCT.md. Security reports go
through the private channel in SECURITY.md.
Code is licensed under the Apache License 2.0. You may use, modify, and redistribute it - including commercially. A patent grant comes with the license; see Apache 2.0 §3.
The names ContentOps and SecM8 are trademarks of
KustoKing / SecM8 and are NOT licensed under Apache 2.0 (the
license explicitly disclaims trademark rights in §6). You may not
name a fork "ContentOps", use the SecM8 wordmark in marketing, or
imply SecM8 endorsement without permission. See
TRADEMARK.md for the full policy.
Attribution is appreciated but not required - see NOTICE.