Skip to content

Runbook 02 — Security Data Ingestion and Normalization

Runbook Type: Security Data Engineering / Security Automation Operations
Difficulty: Intermediate → Advanced
Primary Audience: SOC Analysts / Security Engineers / Detection Engineers / Cloud Security Engineers / Security Automation Engineers
Security Domains: SOC / SIEM / SOAR / Cloud Security / Threat Intelligence / Vulnerability Management
Execution Model: Repeatable ingestion and normalization workflow
Primary Goal: Turn inconsistent raw security data into trustworthy, structured, traceable security records

Security automation depends on data.

But security data arrives from many different systems:

SIEM
EDR
FIREWALLS
IDENTITY PLATFORMS
CLOUD PROVIDERS
VULNERABILITY SCANNERS
THREAT INTELLIGENCE
EMAIL SECURITY
APPLICATION LOGS
LINUX SYSTEMS
WINDOWS SYSTEMS

Each system may represent similar information differently.

For example:

HOSTNAME

may appear as:

WEB01
web01
web01.example.local
Web01

Severity may appear as:

Critical
CRITICAL
critical
5
P1

Authentication outcomes may appear as:

Success
SUCCESS
allowed
accepted
0

Before security data can be:

CORRELATED
SCORED
SEARCHED
AUTOMATED
REPORTED

it needs to be:

VALIDATED
NORMALIZED
DEDUPLICATED
ENRICHED
TRACEABLE

The central principle of this runbook is:

BAD SECURITY DATA
IN
=
BAD SECURITY DECISIONS
OUT
SOURCE
COLLECT
PRESERVE RAW
VALIDATE
PARSE
NORMALIZE
DEDUPLICATE
ENRICH
QUALITY CHECK
STORE
CORRELATE
SECURITY DECISION

Do not start by collecting everything.

Start with:

WHAT SECURITY QUESTION
ARE WE TRYING TO ANSWER?

Examples:

WHICH USERS ARE EXPERIENCING
REPEATED LOGIN FAILURES?
WHICH INTERNET-FACING ASSETS
HAVE CRITICAL VULNERABILITIES?
WHICH CLOUD RESOURCES
ARE PUBLIC?
WHICH ALERTS INVOLVE
PRIVILEGED USERS?
WHICH IOCS APPEAR
IN SECURITY EVENTS?

For each security question, determine:

WHICH DATA
IS ACTUALLY REQUIRED?

Example:

Question:
Which privileged accounts have suspicious authentication activity?

Required data:

AUTHENTICATION EVENTS
IDENTITY INVENTORY
PRIVILEGE STATUS
TIMESTAMPS
SOURCE IP
TARGET SYSTEM

More data does not automatically mean better security.

Excessive collection increases:

COST
STORAGE
PRIVACY RISK
PROCESSING TIME
NOISE
ATTACK SURFACE

Use:

DATA MINIMIZATION

Create a source inventory.

Example:

Source Data Method Owner
SIEM Alerts API SOC
Entra ID Identity API IAM
CMDB Assets API IT
Vulnerability tool Findings API VM Team
Linux hosts Auth logs File/Agent Platform
Cloud platform Config API Cloud Team

Every data source should have an owner.

Document:

SOURCE NAME
TECHNICAL OWNER
BUSINESS OWNER
CONTACT
SUPPORT PROCESS

Not every system is authoritative for every field.

Example:

SIEM

may know:

OBSERVED USERNAME

but:

IDENTITY DIRECTORY

may be authoritative for:

ACCOUNT STATUS
DEPARTMENT
PRIVILEGE

Example:

IDENTITY
→ DIRECTORY
ASSET OWNER
→ CMDB
VULNERABILITY STATUS
→ VULNERABILITY PLATFORM
CLOUD RESOURCE STATE
→ CLOUD API

For each source, record:

RELIABILITY
FRESHNESS
KNOWN LIMITATIONS
COLLECTION METHOD
FAILURE MODES

Common collection methods include:

API
LOG FORWARDER
AGENT
FILE EXPORT
DATABASE QUERY
MESSAGE QUEUE
WEBHOOK
CLOUD EVENT STREAM

Where possible prefer:

JSON
CSV
DATABASE RECORDS
STRUCTURED API RESPONSES

over unstructured:

FREE-FORM TEXT

Structured data is usually easier to:

VALIDATE
PARSE
NORMALIZE
TEST

Many useful security sources are log-based.

Examples:

LINUX AUTH LOG
WINDOWS EVENT LOG
WEB SERVER LOG
FIREWALL LOG
APPLICATION LOG

These may require:

REGEX
PARSER
FIELD EXTRACTION

before normalization.

Decide whether data arrives:

REAL-TIME
NEAR REAL-TIME
HOURLY
DAILY
WEEKLY
ON DEMAND

Example:

SOC ALERTS
→ NEAR REAL-TIME
ASSET INVENTORY
→ DAILY MAY BE ACCEPTABLE
QUARTERLY COMPLIANCE EVIDENCE
→ PERIODIC

Every collection should record:

WHEN WAS THIS DATA COLLECTED?

Use:

UTC

where possible.

Example:

2026-08-29T06:30:00Z

Do not confuse:

EVENT TIME

with:

COLLECTION TIME

Example:

EVENT OCCURRED:
10:00
COLLECTOR RECEIVED:
10:02
PIPELINE PROCESSED:
10:05

These are different.

Never discard the original timestamp if it is useful for investigation.

Store:

original_timestamp

and normalized:

event_time_utc

Before transforming important security data:

PRESERVE
THE ORIGINAL

Architecture:

SOURCE
RAW COPY
NORMALIZED COPY

If normalization produces the wrong result, you need:

ORIGINAL EVIDENCE

to determine:

WAS THE SOURCE WRONG?
WAS THE PARSER WRONG?
WAS THE NORMALIZER WRONG?

Possible structure:

data/
|
+-- raw/
| +-- siem/
| +-- identity/
| +-- cloud/
| +-- vulnerability/
|
+-- normalized/

Treat raw data as:

IMMUTABLE

where practical.

Do transformations on:

COPIES

Use:

SHA-256

to support integrity checking.

Mental model:

RAW FILE
SHA-256
INTEGRITY REFERENCE

Every normalized record should ideally answer:

WHERE DID THIS COME FROM?

Add fields such as:

source_system
source_record_id
collection_time
collector_version
{
"source_system": "identity-monitor",
"source_record_id": "ALT-1002",
"collection_time": "2026-08-29T06:20:00Z",
"collector_version": "1.3.0"
}

Before processing files:

DOES FILE EXIST?

If not:

LOG FAILURE
MARK SOURCE UNAVAILABLE
DO NOT PRETEND DATA IS EMPTY

These are different:

FILE MISSING

may mean:

COLLECTION FAILURE

whereas:

EMPTY VALID FILE

may mean:

NO EVENTS

Confirm expected formats such as:

JSON
CSV
LOG
NDJSON

Do not assume extensions guarantee content validity.

A JSON source can fail because of:

MALFORMED JSON
TRUNCATED FILE
ENCODING ISSUE
UNEXPECTED STRUCTURE

Before parsing CSV:

EXPECTED:
alert_id
timestamp
severity
asset

If the source provides:

id
time
risk
hostname

do not silently assume mappings.

Example alert schema:

alert_id
timestamp
source
alert_type
severity

Optional:

user
asset
source_ip
description

30 — Reject or Quarantine Invalid Records

Section titled “30 — Reject or Quarantine Invalid Records”

Invalid records should be:

QUARANTINED

rather than silently discarded.

Example:

invalid-records.json

Example:

{
"record_id": "ALT-20",
"reason": "Invalid timestamp"
}

Preferred behavior:

1000 RECORDS
999 VALID
1 INVALID
PROCESS 999
QUARANTINE 1

rather than:

1 BAD RECORD
ENTIRE PIPELINE FAILS

unless integrity requirements demand the entire batch be rejected.

Data sources change.

Add:

schema_version

where practical.

Example:

1.0

Version 1 may use:

hostname

Version 2 may use:

device_name

Your parser needs to know which schema it received.

Create consistent internal names.

Example:

host
hostname
device
computer
machine

becomes:

asset

Example normalized alert:

{
"event_id": "ALT-1001",
"event_time": "2026-08-29T08:15:00Z",
"event_type": "authentication_failure",
"severity": "medium",
"user": "admin01",
"asset": "JUMP01",
"source_ip": "203.0.113.25"
}

Choose one internal severity model.

For example:

critical
high
medium
low
informational
unknown

Example:

Vendor:
5
Internal:
critical

or:

Vendor:
P2
Internal:
high

Document mappings explicitly.

If a vendor field says:

risk = 7

you need documentation before deciding whether:

7
=
high

Store:

source_severity

and:

normalized_severity

when useful.

Different systems may use:

Open
OPEN
active
new
in_progress

Define a common model.

Example:

open
in_progress
closed
unknown

Do not lose vendor-specific meaning.

Store:

source_status

where necessary.

Source values may include:

True
TRUE
yes
1
enabled

Normalize to:

true / false / unknown

Do not convert:

MISSING

to:

false

automatically.

Use:

unknown

where appropriate.

Example:

web01
WEB01
Web01

to:

WEB01

Example:

WEB01.EXAMPLE.LOCAL

may need:

asset_fqdn

and:

asset_short_name

Do not blindly remove domains if they matter.

An asset may be identified by:

HOSTNAME
IP
CLOUD RESOURCE ID
AGENT ID
SERIAL NUMBER
INSTANCE ID

Where possible correlate on:

ASSET ID

rather than only:

HOSTNAME

because hostnames can change.

Examples:

ADMIN01
admin01
DOMAIN\admin01
admin01@example.com

may refer to:

SAME OR DIFFERENT IDENTITIES

Do not normalize blindly.

Store fields such as:

username
domain
upn
identity_id

Prefer:

DIRECTORY OBJECT ID
EMPLOYEE ID
ACCOUNT ID

for correlation where available.

Use standard representations.

For IPv4:

010.001.001.001

should be handled carefully.

For IPv6:

multiple textual forms

may represent the same address.

Use proper IP parsing libraries.

Do not accept:

999.999.999.999

as a valid IP.

Potential categories:

PRIVATE
PUBLIC
LOOPBACK
LINK-LOCAL
DOCUMENTATION
UNKNOWN

Convert:

Example.COM

to:

example.com

when appropriate.

DNS names may appear as:

example.com.

Document your normalization approach.

Avoid over-normalizing URLs.

These may differ:

https://example.com/login
https://example.com/Login

depending on application behavior.

File hashes should be:

LOWERCASE HEX

for consistent comparison.

Common lengths:

MD5
32 hex
SHA-1
40 hex
SHA-256
64 hex

Do not classify:

randomtext

as:

SHA-256

Example:

cve-2026-1234

to:

CVE-2026-1234

where valid.

Expected:

0.0–10.0

Reject or mark unknown values such as:

15
high
N/A

Convert:

08/29/2026
2026-08-29
29-08-2026

into a consistent internal representation.

Preferred:

ISO 8601

Convert event timestamps to:

UTC

while preserving original time if useful.

A log without timezone is dangerous.

Example:

2026-08-29 10:00:00

Ask:

WHICH TIMEZONE?

Timezones can change because of:

DST

Prefer timezone-aware timestamps.

If a timestamp cannot be parsed:

DO NOT
MAKE ONE UP

Mark:

timestamp_status = invalid

Sources may report:

4625
login_failed
failed_authentication
authentication_failure

Map them to a common type such as:

authentication_failure

Keep:

source_event_id = 4625

because source-specific context still matters.

Example:

TCP
tcp
6

could normalize to:

tcp

when mapping is clear.

Convert:

"443"

to integer:

443

after validation.

Expected:

0–65535

depending on your model.

Use:

aws
azure
gcp

instead of:

Amazon Web Services
Microsoft Azure
Google Cloud Platform

internally.

Create standard fields:

cloud_provider
cloud_scope_id
cloud_scope_name
resource_id
resource_type

Cloud resource names can be duplicated.

Prefer:

FULL RESOURCE IDENTIFIER

where possible.

Possible model:

production
staging
development
test
sandbox
unknown

Example internal model:

critical
high
medium
low
unknown

If asset inventory has no criticality:

unknown

is safer than:

low

Owner may represent:

TEAM
APPLICATION OWNER
BUSINESS OWNER
TECHNICAL OWNER

Separate these when required.

Avoid free-text owner fields when possible.

Use stable identifiers such as:

TEAM ID
GROUP ID

Security systems often generate duplicate records.

Causes:

RETRIES
MULTIPLE COLLECTORS
MULTIPLE SCANNERS
MESSAGE REDELIVERY
REIMPORT

Examples:

ALERT ID

or:

ASSET
+
CVE
+
PORT
+
SCANNER

or:

USER
+
SOURCE IP
+
TIMESTAMP BUCKET
+
EVENT TYPE

This:

same hostname

is not enough to declare:

duplicate vulnerability

Do not necessarily delete duplicates permanently.

Store:

DUPLICATE COUNT
SOURCE IDS
FIRST SEEN
LAST SEEN

These are different.

DEDUPLICATION

asks:

IS THIS THE SAME RECORD?
CORRELATION

asks:

ARE THESE DIFFERENT RECORDS
RELATED?
5 identical alerts

may be:

DUPLICATES

but:

FAILED LOGIN
+
SUCCESSFUL LOGIN

are:

RELATED

not duplicates.

Events may arrive after newer events.

Do not assume ingestion order equals:

EVENT ORDER

Use:

EVENT TIMESTAMP

for timelines.

Example:

EVENT A
10:02
EVENT B
10:01

may be collected:

B AFTER A

because of pipeline delay.

Normalized records should have a stable identifier.

Options:

SOURCE ID
UUID
HASH-BASED FINGERPRINT

Possible fingerprint:

source
+
event_type
+
asset
+
user
+
timestamp

Hash the combination for tracking.

Do not use fingerprinting blindly where timestamps or fields change slightly between copies.

After normalization, add context.

Examples:

ASSET OWNER
ASSET CRITICALITY
IDENTITY PRIVILEGE
DEPARTMENT
CLOUD PROVIDER
IOC CONTEXT
VULNERABILITY CONTEXT

Recommended:

NORMALIZE FIRST
ENRICH SECOND

because matching normalized values is more reliable.

Example:

user = admin01

becomes:

user = admin01
department = Cloud Operations
privileged = true
account_status = enabled

Example:

asset = WEB01

becomes:

criticality = high
environment = production
internet_facing = true
owner = Application Team

Example:

203.0.113.25

may gain:

source
classification
confidence
last_seen

An alert on:

WEB01

may gain:

critical_open_vulnerabilities = 1

If an enrichment source is unavailable:

KEEP THE RECORD

Mark:

enrichment_status = partial

Example:

IDENTITY
ASSET
IOC
VULNERABILITY

Result:

PARTIAL CONTEXT

not:

DROP ALERT

Measure:

COMPLETENESS
VALIDITY
CONSISTENCY
UNIQUENESS
FRESHNESS
ACCURACY

Question:

ARE REQUIRED FIELDS PRESENT?

Example:

98% OF ALERTS
HAVE ASSET ID

Question:

DO VALUES MATCH EXPECTED FORMAT?

Example:

99.9% OF IP ADDRESSES
ARE VALID

Question:

IS THE SAME CONCEPT
REPRESENTED THE SAME WAY?

Example:

critical

instead of:

critical / CRIT / 5 / urgent

Question:

HOW MANY RECORDS
ARE DUPLICATES?

Question:

HOW OLD IS THE DATA?

Harder question:

DOES THE DATA
REFLECT REALITY?

This often requires comparison with authoritative systems.

A future model might track:

completeness
validity
freshness
enrichment coverage

Do not compress everything into one opaque score without explanation.

Example:

IDENTITY FOUND
ASSET FOUND
IOC CHECK COMPLETE
VULNERABILITY CONTEXT AVAILABLE

Analysts should see:

ASSET CONTEXT:
UNKNOWN

rather than the field silently disappearing.

Example:

TOTAL RECORDS
VALID RECORDS
INVALID RECORDS
DUPLICATES
UNKNOWN ASSETS
UNKNOWN USERS
INVALID IPS
INVALID TIMESTAMPS
{
"total_records": 1000,
"valid_records": 960,
"invalid_records": 20,
"duplicates": 20,
"unknown_assets": 15,
"unknown_users": 8
}

Example:

INVALID RATE > 5%
→ INVESTIGATE PIPELINE

Thresholds should reflect your environment.

Example:

NORMAL:
10,000 EVENTS/HOUR
CURRENT:
50 EVENTS/HOUR

Possible causes:

COLLECTOR FAILURE
SOURCE FAILURE
NETWORK ISSUE

Example:

NORMAL:
10,000
CURRENT:
2,000,000

Possible:

EVENT STORM
LOOP
ATTACK
PARSER ERROR

Track each data source:

HEALTHY
DEGRADED
FAILED
UNKNOWN
{
"siem": "healthy",
"identity": "healthy",
"cmdb": "degraded",
"threat_intel": "failed"
}

A report should state:

THREAT INTELLIGENCE SOURCE
UNAVAILABLE

rather than pretending:

NO IOC MATCHES FOUND

Normalized security data may be stored in:

FILES
SQLITE
POSTGRESQL
DATA LAKE
SIEM INDEX
SEARCH PLATFORM

For labs:

JSON
CSV
SQLITE

are often enough.

For enterprise scale:

CENTRAL DATABASE
SEARCH PLATFORM
DATA LAKE

may be more appropriate.

119 — Separate Raw and Normalized Storage

Section titled “119 — Separate Raw and Normalized Storage”

Example:

RAW
→ immutable source copy
NORMALIZED
→ query-ready data

Each normalized dataset should record:

schema_version

Record:

pipeline_version

so you know which code produced the output.

A change such as:

P2 → high

can alter analytics.

Track:

normalization_version

Generate one:

collection_id

per pipeline run.

They allow:

TRACEABILITY
ROLLBACK
REPROCESSING
AUDIT

If normalization logic changes, you may need to:

REPROCESS RAW DATA

using the new pipeline.

126 — Never Require Source Recollection if Avoidable

Section titled “126 — Never Require Source Recollection if Avoidable”

Preserving raw data allows:

REPROCESSING

without requesting the source system again.

Use:

PRIMARY KEYS
UNIQUE KEYS
NOT NULL
CHECK CONSTRAINTS

where appropriate.

risk_score
0–100
cvss
0–10
port
0–65535

Examples:

event_time
asset
user
source_ip
severity

Indexes:

IMPROVE READ PERFORMANCE

but increase:

STORAGE
WRITE COST
MAINTENANCE

Do not correlate:

WEB01

with:

web01

before normalization if they should represent the same asset.

Common:

ASSET ID
USER ID
SOURCE IP
SESSION ID
ALERT ID
CLOUD RESOURCE ID

Avoid relying only on:

DISPLAY NAME

if a stable identifier exists.

Correlation should usually consider:

TIME

Example:

FAILED LOGIN
08:15
SUCCESS
08:18

is more meaningful than:

FAILED LOGIN
JANUARY
SUCCESS
AUGUST

135 — Build Normalized Security Event Schema

Section titled “135 — Build Normalized Security Event Schema”

Recommended core fields:

event_id
event_time
collection_time
source_system
event_type
severity
user_id
username
asset_id
asset_name
source_ip
destination_ip
status
raw_reference
schema_version
privileged_user
asset_criticality
environment
internet_facing
ioc_confidence
open_vulnerability_count
cloud_provider

137 — Keep Context Separate Where Needed

Section titled “137 — Keep Context Separate Where Needed”

Do not duplicate massive enrichment objects into every record if a relational model is more efficient.

Use:

EVENT TABLE
ASSET TABLE
IDENTITY TABLE

with joins.

EVENTS
asset_id
ASSETS
EVENTS
user_id
IDENTITIES
EVENTS
indicator
IOC_CONTEXT

For important records, maintain:

RAW SOURCE
PARSER
NORMALIZER
ENRICHMENT
FINAL RECORD

If an analyst challenges a value:

WHY DOES THIS SAY
USER IS PRIVILEGED?

you should be able to answer:

SOURCE:
Identity Directory
COLLECTED:
06:00 UTC

Security datasets may contain:

USERNAMES
INTERNAL IPS
HOSTNAMES
VULNERABILITIES
EMAIL ADDRESSES
INCIDENT INFORMATION

Treat them as sensitive.

Not every user needs access to:

RAW SECURITY TELEMETRY

Define:

VIEWER
ANALYST
ENGINEER
ADMIN

where appropriate.

Protect sensitive security data:

IN TRANSIT
AT REST

according to organizational requirements.

Pipeline logs should include:

RUN ID
SOURCE
START TIME
RECORD COUNT
VALID COUNT
INVALID COUNT
DUPLICATE COUNT
FAILURE COUNT
END TIME

145 — Avoid Logging Raw Sensitive Payloads

Section titled “145 — Avoid Logging Raw Sensitive Payloads”

Do not log entire:

AUTHENTICATION TOKEN
EMAIL BODY
API RESPONSE

unless necessary and approved.

Track:

INGESTION RATE
VALIDATION FAILURE RATE
NORMALIZATION FAILURE RATE
DUPLICATE RATE
ENRICHMENT SUCCESS RATE
PROCESSING LATENCY

Calculate:

PROCESSING TIME
-
EVENT TIME

where appropriate.

If normal delay is:

1 MINUTE

and current delay is:

45 MINUTES

investigate.

If using message queues, track:

QUEUE DEPTH
OLDEST MESSAGE AGE

Invalid or repeatedly failing records can move to:

DEAD-LETTER QUEUE

for review.

Preferred:

FAILED RECORD
QUARANTINE
REVIEW

not:

FAILED RECORD
DELETE

Retries may help for:

TEMPORARY NETWORK ERROR
429
5XX

Use:

MAX RETRIES
BACKOFF
DEAD-LETTER

Running ingestion twice should not unintentionally create:

DOUBLE RECORDS

Use:

source_record_id
event_id
fingerprint

Example:

10,000 RECORDS
9,950 SUCCESS
50 FAILED

Report:

PARTIAL SUCCESS

Avoid:

STATUS = SUCCESS

without:

FAILED RECORD COUNT

Define retention separately for:

RAW DATA
NORMALIZED DATA
QUARANTINED DATA
PIPELINE LOGS

Do not keep data:

FOREVER

without a documented need.

160 — Privacy and Regulatory Requirements

Section titled “160 — Privacy and Regulatory Requirements”

Security telemetry can overlap with:

EMPLOYEE DATA
CUSTOMER DATA
PERSONAL DATA

Consult applicable organizational requirements.

161 — Normalize Without Destroying Evidence

Section titled “161 — Normalize Without Destroying Evidence”

The rule is:

NORMALIZE FOR ANALYSIS
PRESERVE FOR INVESTIGATION

Source:

DOMAIN\Admin01

Normalized:

username = admin01
domain = DOMAIN

Do not simply discard:

DOMAIN

if it is meaningful.

Every parser should be tested against:

VALID SAMPLE
MISSING FIELD
EXTRA FIELD
INVALID VALUE
EMPTY RECORD
UNEXPECTED TYPE

When source formats change, ensure:

OLD VALID DATA

still processes as expected.

Maintain synthetic samples:

tests/data/
|
+-- valid/
|
+-- invalid/
|
+-- edge-cases/

If a vendor changes:

host_name

to:

device_name

the pipeline should:

DETECT SCHEMA CHANGE

rather than silently producing:

EMPTY ASSET VALUES

Example:

ASSET NULL RATE
Yesterday:
1%
Today:
80%

This likely indicates a parser or source problem.

Track:

NEW FIELD
REMOVED FIELD
TYPE CHANGE
ENUM CHANGE

Example:

cvss

should be:

NUMBER

not:

OBJECT

Example severity should belong to:

critical
high
medium
low
informational

Anything else:

UNKNOWN

or quarantined according to policy.

Document expected:

EVENTS / HOUR
ALERTS / DAY
VULNERABILITIES / SCAN
ASSETS / INVENTORY

A sudden change may indicate:

ATTACK ACTIVITY
COLLECTION FAILURE
DUPLICATION LOOP
CONFIGURATION CHANGE

Example:

AWS CloudTrail
Azure Activity Log
Google Cloud Audit Logs

all normalize into:

CLOUD ACTIVITY EVENT

Common model does not mean:

DELETE PROVIDER-SPECIFIC DATA

Keep useful native fields in:

source_details

or related storage.

Architecture:

AWS
AWS ADAPTER
COMMON MODEL
AZURE
AZURE ADAPTER
COMMON MODEL
GCP
GCP ADAPTER
COMMON MODEL

Provider-specific parsing belongs in:

ADAPTER

Common analytics should consume:

NORMALIZED RECORD

For SIEM data, normalize concepts such as:

ALERT ID
RULE ID
EVENT TIME
USER
ASSET
SOURCE IP
SEVERITY

Common fields:

DEVICE ID
HOSTNAME
USER
PROCESS
PARENT PROCESS
HASH
ALERT SEVERITY

Common fields:

FINDING ID
ASSET ID
CVE
SEVERITY
CVSS
STATUS
FIRST SEEN
LAST SEEN
DUE DATE

Common:

INDICATOR
TYPE
SOURCE
CONFIDENCE
FIRST SEEN
LAST SEEN
EXPIRATION

Common:

IDENTITY ID
USERNAME
ACCOUNT TYPE
ENABLED
PRIVILEGED
DEPARTMENT

Common:

ASSET ID
HOSTNAME
IP
ENVIRONMENT
CRITICALITY
OWNER
INTERNET FACING

Common:

RULE ID
RULE NAME
VERSION
SEVERITY
DATA SOURCE

Use this runbook when:

NEW DATA SOURCE ADDED
SCHEMA CHANGED
ALERT FIELDS MISSING
CORRELATION FAILS
DUPLICATE RATE SPIKES
EVENT COUNTS DROP
UNKNOWN ASSETS INCREASE
UNKNOWN USERS INCREASE

185 — Initial Triage of Data Pipeline Issue

Section titled “185 — Initial Triage of Data Pipeline Issue”

Check:

SOURCE AVAILABLE?
COLLECTOR RUNNING?
AUTHENTICATION WORKING?
EXPECTED FILE/API DATA PRESENT?
SCHEMA CHANGED?
PARSER ERRORS?
DATABASE WRITES WORKING?
NO DATA
SOURCE?
COLLECTOR?
NETWORK?
AUTH?
PARSER?
NORMALIZER?
STORAGE?

Action:

MARK SOURCE FAILED
PRESERVE OTHER PIPELINE OUTPUT
NOTIFY OWNER
DO NOT REPORT FULL COVERAGE

For:

401

review:

TOKEN
EXPIRATION
CLIENT CONFIGURATION

For:

403

review:

REQUIRED PERMISSION
ROLE ASSIGNMENT
SCOPE

Do not immediately grant broad admin access.

For:

429

use:

BACKOFF
RETRY-AFTER
LOWER REQUEST RATE
CACHING

Stop and review:

WHAT CHANGED?
WHICH FIELDS?
WHICH TYPE?
WHICH VERSION?

Understand vendor/source changes before changing mappings.

Investigate:

RETRY LOOP
FORWARDER DUPLICATION
MULTIPLE COLLECTORS
PAGINATION BUG

Investigate:

CMDB FRESHNESS
HOSTNAME FORMAT
NEW CLOUD ASSETS
ASSET INVENTORY GAP

Investigate:

IDENTITY SYNC
DOMAIN FORMAT
SERVICE ACCOUNTS
DELETED ACCOUNTS
NORMALIZATION BUG

Escalate when:

CRITICAL DATA SOURCE UNAVAILABLE
SCHEMA CHANGE BREAKS INGESTION
LARGE PORTION OF DATA INVALID
NORMALIZATION PRODUCES WRONG IDENTITIES
CORRELATION RESULTS ARE UNTRUSTWORTHY

197 — Stop Downstream Automation if Necessary

Section titled “197 — Stop Downstream Automation if Necessary”

If data integrity is questionable:

PAUSE
HIGH-IMPACT AUTOMATION

until data quality is restored.

A response system acting on:

WRONG USER
WRONG ASSET
WRONG SEVERITY

can create serious business impact.

Useful metrics:

SOURCE HEALTH
LAST SUCCESSFUL COLLECTION
RECORD COUNT
INVALID RATE
DUPLICATE RATE
PROCESSING LATENCY
UNKNOWN ASSET RATE
UNKNOWN USER RATE
SIEM
Healthy
Last Collection: 06:30 UTC
Identity
Healthy
CMDB
Degraded
Unknown Assets: 18%
IOC
Failed

Example:

NO SUCCESSFUL
CMDB COLLECTION
FOR 24 HOURS

should trigger operational review.

The worst pipeline state is:

BROKEN
BUT APPEARS HEALTHY

Use:

HEARTBEATS
EXPECTED RECORD COUNTS
LAST-SUCCESS TIMESTAMP

Record:

run_id
start_time
end_time
status
records_processed

Use:

success
partial_success
failed

not only:

success / failure

Example:

{
"run_id": "RUN-20260829-001",
"status": "partial_success",
"sources_expected": 6,
"sources_successful": 5,
"invalid_records": 7
}

Before ingestion:

  • Security use case defined
  • Required data identified
  • Data sources documented
  • Source owners identified
  • Authoritative fields understood
  • Collection method documented
  • Frequency defined
  • Access permissions validated
  • Secrets protected
  • Raw storage prepared
  • Expected schema documented

During ingestion:

  • Source availability checked
  • Collection time recorded
  • Raw data preserved
  • Source provenance recorded
  • File/API response validated
  • Required fields validated
  • Invalid records quarantined
  • Timestamps parsed
  • Timezones normalized
  • Field names normalized
  • Severity normalized
  • Status normalized
  • Booleans normalized
  • Hostnames normalized
  • Identities normalized
  • IPs validated
  • Indicators normalized
  • Cloud identifiers preserved
  • Duplicates identified
  • Duplicates handled
  • Enrichment attempted
  • Failed enrichment recorded
  • Unknown values preserved as unknown

After ingestion:

  • Data quality calculated
  • Record counts validated
  • Duplicate rate reviewed
  • Invalid rate reviewed
  • Unknown assets reviewed
  • Unknown users reviewed
  • Source health documented
  • Normalized data stored
  • Schema version recorded
  • Pipeline version recorded
  • Collection ID recorded
  • Pipeline status recorded
  • Downstream automation safe to continue

For every new source document:

SOURCE NAME:
OWNER:
PURPOSE:
COLLECTION METHOD:
AUTHENTICATION:
REQUIRED PERMISSIONS:
FORMAT:
SCHEMA VERSION:
COLLECTION FREQUENCY:
EXPECTED RECORD VOLUME:
AUTHORITATIVE FIELDS:
NORMALIZATION RULES:
DEDUPLICATION KEY:
RETENTION:
FAILURE BEHAVIOR:

Example:

Source Field Normalized Field Rule
host_name asset_name Uppercase
userPrincipalName username Lowercase
risk severity Mapping table
eventTime event_time Convert to UTC
srcIp source_ip IP validation

Mapping changes should be:

DOCUMENTED
VERSIONED
TESTED
REVIEWED
SOURCE VALUE INTERNAL
5 critical
4 high
3 medium
2 low
1 informational

Only use if supported by the source’s official definition.

SOURCE:
ACTIVE
NEW
open
SOURCE:
RESOLVED
CLOSED
closed

Changing:

P2
FROM MEDIUM
TO HIGH

can alter:

DASHBOARDS
SCORES
ALERT QUEUES
AUTOMATION

Treat mapping changes as security engineering changes.

213 — Build Data Quality Investigation Template

Section titled “213 — Build Data Quality Investigation Template”
ISSUE:
SOURCE:
FIRST OBSERVED:
EXPECTED:
ACTUAL:
INVALID RATE:
DUPLICATE RATE:
AFFECTED FIELDS:
DOWNSTREAM IMPACT:
ROOT CAUSE:
REMEDIATION:
VALIDATION:
Issue:
Asset enrichment failure
Expected:
<5% unknown assets
Actual:
68% unknown assets
Cause:
EDR started reporting FQDN instead of short hostname
PRESERVE FQDN
EXTRACT SHORT NAME
USE ASSET ID WHERE AVAILABLE
UPDATE NORMALIZATION TESTS
REPROCESS AFFECTED DATA

When data arrives, ask:

WHERE DID IT COME FROM?
CAN I TRUST THE SOURCE?
IS THE RECORD COMPLETE?
IS THE FORMAT VALID?
WHAT DOES EACH FIELD MEAN?
HOW SHOULD IT BE NORMALIZED?
IS IT A DUPLICATE?
WHAT CONTEXT CAN BE ADDED?
HOW FRESH IS THE CONTEXT?
WHAT IS UNKNOWN?
CAN DOWNSTREAM AUTOMATION
SAFELY USE THIS DATA?

The most important lesson is:

NORMALIZATION
IS NOT JUST
FORMATTING

It is the process of ensuring that:

SECURITY DATA
HAS CONSISTENT
MEANING
RAW DATA
+
VALIDATION
+
NORMALIZATION
+
PROVENANCE
+
DATA QUALITY
+
CONTEXT
=
TRUSTWORTHY SECURITY DATA

After completing this runbook you should have:

DOCUMENTED DATA SOURCES
RAW DATA PRESERVATION
SCHEMA VALIDATION
NORMALIZED SECURITY RECORDS
DEDUPLICATION CONTROLS
DATA QUALITY METRICS
SOURCE HEALTH VISIBILITY
TRACEABLE PROVENANCE

Your security data is now ready for:

CORRELATION
ANALYTICS
RISK SCORING
REPORTING
SECURITY AUTOMATION

➡️ Runbook 03 — Security API Integration and Failure Handling

The next runbook focuses on safely operating security API integrations.

You will build a repeatable operational procedure covering:

API DISCOVERY
AUTHENTICATION
LEAST PRIVILEGE
REQUEST VALIDATION
TIMEOUTS
HTTP STATUS HANDLING
RATE LIMITS
PAGINATION
RETRIES
BACKOFF
PARTIAL FAILURE
API HEALTH
RECOVERY

The goal will be to ensure that security automation remains reliable when external APIs are slow, unavailable, rate-limited, malformed, or partially failing.