Nirnaya v1
← cookbooks
securityready today

Security alert disposition

Decide what an alert deserves before an analyst spends a minute on it.

SOC queues are mostly noise. The expensive part is the analyst minutes spent deciding that. Give the model the alert plus context and get a disposition, a severity and two yes/no facts you can act on.

Calibration matters here: 'contain 0.52 / investigate 0.37' is a different situation from 'contain 0.95', and the distribution tells you which one you are in.

The questions

dispositionchoice

What should happen to this alert?

close_benignExpected, explainable activity; close without analyst time.
monitorKeep an eye on it; no action now.
investigateWarrants an analyst opening an investigation.
containContain the host or account immediately; do not wait for triage.
severityscore

How severe is this incident if the alert is real?

0Informational.
1Low: limited scope, no sensitive data.
2Medium: one account or host at risk.
3High: privileged access or sensitive data exposed.
4Critical: active, spreading, or business-critical systems.
true_positivenoul

This alert represents genuine malicious activity.

falseno, the statement does not hold
trueyes, the statement holds
credential_compromisenoul

The evidence indicates a credential or account has been compromised.

falseno, the statement does not hold
trueyes, the statement holds
urgencyscore

How quickly must someone act?

0Whenever.
1Within the shift.
2Within the hour.
3Right now.

Acting on the answers

  • true_positive below 0.25 and disposition close_benign: auto-close with the reasoning attached.
  • credential_compromise above 0.7: force a password reset before anyone reads the ticket.
  • Anything with confidence below 0.45: send to a human, and log it as a hard case for the next training round.

request

{
  "state": {
    "alert": {
      "rule": "impossible_travel",
      "description": "two interactive logins from distant locations within 20 minutes",
      "evidence": "User j.okafor authenticated from Lagos at 08:02 UTC and from Frankfurt at 08:19 UTC. Both sessions used the corporate SSO with MFA. The Frankfurt session originated from a known corporate VPN egress IP. No privilege changes, no unusual data access in either session."
    },
    "context": {
      "user_role": "sales engineer",
      "travel_registered": false,
      "vpn_in_use": true,
      "prior_alerts_30d": 0
    }
  },
  "questions": {
    "disposition": {
      "type": "choice",
      "instructions": "What should happen to this alert?",
      "criteria": {
        "close_benign": "Expected, explainable activity; close without analyst time.",
        "monitor": "Keep an eye on it; no action now.",
        "investigate": "Warrants an analyst opening an investigation.",
        "contain": "Contain the host or account immediately; do not wait for triage."
      }
    },
    "severity": {
      "type": "score",
      "instructions": "How severe is this incident if the alert is real?",
      "criteria": [
        "Informational.",
        "Low: limited scope, no sensitive data.",
        "Medium: one account or host at risk.",
        "High: privileged access or sensitive data exposed.",
        "Critical: active, spreading, or business-critical systems."
      ]
    },
    "true_positive": {
      "type": "noul",
      "instructions": "This alert represents genuine malicious activity."
    },
    "credential_compromise": {
      "type": "noul",
      "instructions": "The evidence indicates a credential or account has been compromised."
    },
    "urgency": {
      "type": "score",
      "instructions": "How quickly must someone act?",
      "criteria": [
        "Whenever.",
        "Within the shift.",
        "Within the hour.",
        "Right now."
      ]
    }
  }
}

call it

import { loadEngine } from "nirnaya";     // the same call this site makes

const engine = await loadEngine();          // loads once, then cached by the browser
const answers = await engine.decide(
  "{\"alert\":{\"rule\":\"impossible_travel\",\"description\":\"two interactive logins from distant locations within 20 minutes\",\"evidence\":\"User j.okafor authenticated from Lagos at 08:02 UTC and from Frankfurt at 08:19 UTC. Both sessions used the corporate SSO with MFA. The Frankfurt session originated from a known corporate VPN egress IP. No privilege changes, no unusual data access in either session.\"},\"context\":{\"user_role\":\"sales engineer\",\"travel_registered\":false,\"vpn_in_use\":true,\"prior_alerts_30d\":0}}",
  {
    "disposition": {
      "type": "choice",
      "instructions": "What should happen to this alert?",
      "criteria": {
        "close_benign": "Expected, explainable activity; close without analyst time.",
        "monitor": "Keep an eye on it; no action now.",
        "investigate": "Warrants an analyst opening an investigation.",
        "contain": "Contain the host or account immediately; do not wait for triage."
      }
    },
    "severity": {
      "type": "score",
      "instructions": "How severe is this incident if the alert is real?",
      "criteria": [
        "Informational.",
        "Low: limited scope, no sensitive data.",
        "Medium: one account or host at risk.",
        "High: privileged access or sensitive data exposed.",
        "Critical: active, spreading, or business-critical systems."
      ]
    },
    "true_positive": {
      "type": "noul",
      "instructions": "This alert represents genuine malicious activity."
    },
    "credential_compromise": {
      "type": "noul",
      "instructions": "The evidence indicates a credential or account has been compromised."
    },
    "urgency": {
      "type": "score",
      "instructions": "How quickly must someone act?",
      "criteria": [
        "Whenever.",
        "Within the shift.",
        "Within the hour.",
        "Right now."
      ]
    }
  }
);
for (const a of answers) console.log(a.qid, a.answer, a.p);

curl and Python target nirnaya_serve, the Jev-compatible server in the release; the browser snippet is exactly what this site does.