When I started building LogWard, I wanted more than just a log viewer. I wanted developers to have the same security detection capabilities that SOC teams use in enterprise SIEMs like Splunk or QRadar.
But how do you implement security detection rules without reinventing the wheel?
Enter Sigma: the open standard for detection rules that works across different log management platforms.
What is Sigma? (And Why You Should Care)
Sigma is like "Snort rules for logs." It's a generic signature format that describes suspicious activity in a platform-agnostic way.
Instead of writing vendor-specific queries, you write a single YAML rule that can be translated to:
- Splunk SPL
- Elastic Query DSL
- Microsoft Sentinel KQL
- ...and now, SQL for LogWard
Here's a real example from SigmaHQ's repository:
title: Suspicious PowerShell Encoded Command
status: test
description: Detects suspicious encoded PowerShell commands
logsource:
category: process_creation
product: windows
detection:
selection:
CommandLine|contains:
- 'powershell'
- '-enc'
- '-encodedcommand'
condition: selection
level: medium
This rule detects when PowerShell is executed with an encoded command (a common malware/ransomware technique).
The Problem: Making Sigma Work with PostgreSQL
Most Sigma tooling (like sigmac or pySigma) focuses on translating rules to Elasticsearch or Splunk. But LogWard uses PostgreSQL + TimescaleDB.
I needed to:
- Parse Sigma YAML rules
- Translate the detection logic to SQL
WHEREclauses - Run these queries efficiently against millions of log rows
- Trigger alerts when matches are found
The Implementation: A TypeScript Sigma Engine
I built a Sigma rule engine directly into LogWard's backend (Fastify + TypeScript). Here's the architecture:
1. Parsing Sigma Rules
First, I parse the YAML using js-yaml:
import yaml from 'js-yaml';
import fs from 'fs';
interface SigmaRule {
title: string;
description: string;
logsource: {
category?: string;
product?: string;
service?: string;
};
detection: {
[key: string]: any;
condition: string;
};
level: 'low' | 'medium' | 'high' | 'critical';
falsepositives?: string[];
}
function loadSigmaRule(filePath: string): SigmaRule {
const content = fs.readFileSync(filePath, 'utf8');
return yaml.load(content) as SigmaRule;
}
2. Translating Detection Logic to SQL
This is the tricky part. Sigma's detection field can be complex. Let me show you a simplified version:
class SigmaToSQL {
translateDetection(detection: any, condition: string): string {
// Parse the condition (e.g., "selection" or "selection and not filter")
const clauses: string[] = [];
for (const [key, value] of Object.entries(detection)) {
if (key === 'condition') continue;
// Handle different field modifiers (contains, startswith, endswith, etc.)
const sqlClause = this.buildClause(key, value);
clauses.push(sqlClause);
}
// Combine clauses based on the condition
return this.combineConditions(clauses, condition);
}
private buildClause(detectionName: string, fields: any): string {
const conditions: string[] = [];
for (const [field, values] of Object.entries(fields)) {
// Handle modifiers like "contains", "startswith", etc.
const [fieldName, modifier] = this.parseFieldModifier(field);
if (Array.isArray(values)) {
// Multiple values = OR condition
const orConditions = values.map(v =>
this.buildSingleCondition(fieldName, modifier, v)
);
conditions.push(`(${orConditions.join(' OR ')})`);
} else {
conditions.push(this.buildSingleCondition(fieldName, modifier, values));
}
}
return `(${conditions.join(' AND ')})`;
}
private buildSingleCondition(field: string, modifier: string, value: any): string {
// Map log fields to database columns
const dbColumn = this.mapFieldToColumn(field);
switch (modifier) {
case 'contains':
return `${dbColumn} ILIKE '%${this.escape(value)}%'`;
case 'startswith':
return `${dbColumn} ILIKE '${this.escape(value)}%'`;
case 'endswith':
return `${dbColumn} ILIKE '%${this.escape(value)}'`;
case 'exact':
default:
return `${dbColumn} = '${this.escape(value)}'`;
}
}
private parseFieldModifier(field: string): [string, string] {
if (field.includes('|')) {
const [fieldName, modifier] = field.split('|');
return [fieldName, modifier];
}
return [field, 'exact'];
}
private mapFieldToColumn(sigmaField: string): string {
// Map Sigma standard fields to LogWard's schema
const mapping: Record = {
'CommandLine': 'attributes->\'CommandLine\'',
'Image': 'attributes->\'Image\'',
'User': 'attributes->\'User\'',
// ... more mappings
};
return mapping[sigmaField] || `attributes->'${sigmaField}'`;
}
private escape(value: string): string {
// SQL injection prevention
return value.replace(/'/g, "''");
}
}
3. Running Detection Queries
Now I can translate a Sigma rule to SQL and run it against the logs:
async function detectThreats(orgId: string, rule: SigmaRule) {
const translator = new SigmaToSQL();
const whereClause = translator.translateDetection(
rule.detection,
rule.detection.condition
);
// Query logs that match the detection rule
const query = `
SELECT id, timestamp, message, attributes, level
FROM logs
WHERE org_id = $1
AND timestamp > NOW() - INTERVAL '1 hour'
AND (${whereClause})
ORDER BY timestamp DESC
LIMIT 100
`;
const results = await db.query(query, [orgId]);
if (results.rows.length > 0) {
// Trigger alert!
await triggerAlert(orgId, rule, results.rows);
}
}
4. Scheduled Detection
I use BullMQ to run Sigma rules on a schedule (every 5 minutes, hourly, etc.):
import { Queue, Worker } from 'bullmq';
const detectionQueue = new Queue('sigma-detection', {
connection: redis
});
// Schedule rule execution
await detectionQueue.add(
'run-sigma-rule',
{ ruleId: 'suspicious-powershell', orgId: 'org-123' },
{ repeat: { every: 300000 } } // Every 5 minutes
);
// Worker processes the job
const worker = new Worker('sigma-detection', async (job) => {
const { ruleId, orgId } = job.data;
const rule = await loadSigmaRule(ruleId);
await detectThreats(orgId, rule);
});
Real-World Example: Detecting Crypto Mining
Let's use an actual Sigma rule from the community to detect cryptocurrency mining:
title: Cryptocurrency Mining Indicators
description: Detects common crypto mining processes
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith:
- '\xmrig.exe'
- '\cpuminer.exe'
- '\nheqminer.exe'
condition: selection
level: high
When translated, this becomes:
SELECT * FROM logs
WHERE org_id = 'org-123'
AND timestamp > NOW() - INTERVAL '1 hour'
AND (
attributes->>'Image' ILIKE '%\xmrig.exe'
OR attributes->>'Image' ILIKE '%\cpuminer.exe'
OR attributes->>'Image' ILIKE '%\nheqminer.exe'
)
If any logs match, LogWard sends an alert via email/webhook.
The Benefits: Detection-as-Code
With Sigma integration, developers get:
- Community-Maintained Rules: Access to 4000+ detection rules maintained by the security community
- No Vendor Lock-In: Rules work across platforms
- Version Control: Store rules in Git alongside your infrastructure code
- Customization: Fork and adapt rules to your environment
What's Next for LogWard's Sigma Engine
The current implementation is a working MVP, but I'm planning:
- Full pySigma Compatibility: Supporting advanced features like aggregation conditions
- Rule Testing Framework: Simulate attacks against sample logs to validate rules
- SigmaHQ Integration: One-click import of curated rule sets
- Visual Rule Builder: GUI for creating Sigma rules without touching YAML
Try It Yourself
The Sigma engine is already in LogWard's codebase. You can:
- Clone the repo:
git clone https://github.com/logward-dev/logward - Add your Sigma rules to
/sigma-rules/ - Configure detection schedules in the UI
- Get alerts when threats are detected
Or try the hosted version at logward.dev (currently in free alpha).
What do you think? Should every log management tool support Sigma out-of-the-box? Let me know in the comments.
💻 GitHub: https://github.com/logward-dev/logward
🔐 Sigma Rules: https://github.com/SigmaHQ/sigma
🚀 Try LogWard: https://logward.dev
Top comments (0)