The PAM attack chain, written as Sentinel alerts. A sleeping admin waking up, a user granting themselves a role, a vault read at 3am. Detections written by someone who actually runs privileged access, not a generic SOC.
| Domains | Azure, CyberArk/Idira (identity threat model) |
| Built on | Azure/Azure-Sentinel (schema and conventions) |
| Cost | ~$1 to $3 (log ingestion). Runtime ~4 hours |
| Status | All four detections execute against a real Kusto engine (11 tests with decoys, output in findings/). Sentinel-specific behaviour still needs a workspace |
A PAM engineer knows the privileged access attack chain cold. Most Sentinel content is written by SOC analysts working from a generic threat model. Detections written by someone who runs privileged access catch things theirs do not, and that difference is the whole brand.
Turn that domain knowledge into working detection logic that a real Sentinel workspace can run.
I wrote four detections:
| File | Fires when | Why it is a PAM insight |
|---|---|---|
| dormant reactivation | an admin idle 30+ days signs in | abandoned but not deleted admins are what PAM should own |
| self service role | someone grants themselves a privileged role | the roleAssignments write escalation, watched directly |
| bulk secret read | one identity reads many secrets fast | vault enumeration, which CyberArk audit catches for free |
| break glass off hours | an emergency account is used off hours | break glass should be near silent, so any use is a question |
Each one is schema checked the way the upstream Azure Sentinel CI checks it: required keys, valid severity, correct attack tactic spelling, non empty query, and no placeholder IDs.
All four detections execute against a real query engine, not just validate as YAML. Sentinel needs an Azure subscription, but the Kusto engine underneath it ships as a free container — so each rule runs against controlled data and has to fire on the attack while staying silent on decoys planted next to it. A failed self-grant, SecretList instead of SecretGet, a failed sign-in against a dormant admin: the decoys are the real test, because a rule that fires on everything is as useless as one that fires on nothing, and schema validation cannot tell you which you have.
24 tests total — 13 structural, 11 executing real KQL with decoys. CI runs the emulator and fails the build if the suite skips instead of running, so a broken engine cannot masquerade as a pass. Full output in findings/kusto-execution-run.txt.
The rigor caught a modeling bug in the checker itself: hunting queries carry no severity because they are not alerts, so validating them against the alert schema wrongly failed them. The checker now tells the two apart. In LAB-NOTES.md.
| Chose | Over | Because |
|---|---|---|
| Execute every rule against a real Kusto engine | schema validation only | Valid YAML proves a rule is well-formed, not that it detects anything. Sentinel needs a subscription, but the Kusto engine underneath ships as a free container — so the rules can actually run. |
| Plant decoys beside every attack | asserting only that the rule fires | A rule that fires on everything is as useless as one that fires on nothing, and schema validation cannot tell you which you have. A failed self-grant, SecretList instead of SecretGet, a failed sign-in from a dormant admin — the decoys are the real test. |
| Fail CI when the suite skips | treating skips as green | A broken emulator produces skips, and skips read as "fine" in a CI summary while proving nothing. |
| A separate schema for hunting queries | validating everything as an alert | Hunting queries carry no severity because they are not alerts. Validating them against the alert schema failed them for being correct. |
| Point each detection at a watchlist | hardcoding identities into the query | Makes the rules portable into someone else's workspace instead of tied to mine. |
| Four narrow PAM detections | broad SOC-style coverage | Most Sentinel content is written from a generic threat model. The value here is the privileged-access attack chain specifically — dormant admins, self-grants, vault enumeration, break-glass use. |
The Sentinel schema and conventions are Microsoft's. The detection logic, the thresholds, and the choice of what to watch for are the PAM knowledge, and that is mine.
The detections import into Sentinel as Analytics Rules. Each one points at a watchlist so it is portable. Validate locally first, then import through the portal or your pipeline.
python -m pytest tests/ -v
python scripts/validate.pyfindings/ holds the Kusto execution run: all four detections firing on real data, each with decoys that must not fire. LAB-NOTES.md is the log.
Lab code: MIT (LICENSE). Azure Sentinel conventions stay MIT, credited above.