From 4a4b9343d5d26dc8498f75eacc2fad1cc0b7975f Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Wed, 18 Feb 2026 13:29:41 -0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20add=20governance-audit=20hook=20?= =?UTF-8?q?=E2=80=94=20threat=20detection=20for=20Copilot=20sessions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add real-time governance audit hook that scans prompts for threat signals: - 5 threat categories: data exfiltration, privilege escalation, system destruction, prompt injection, credential exposure - 4 governance levels: open, standard, strict, locked - Append-only JSON audit trail (logs/copilot/governance/audit.log) - Session summary with threat counts at session end - Privacy-aware: logs decisions and metadata, never prompt content Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/README.hooks.md | 1 + hooks/governance-audit/README.md | 99 +++++++++++++ hooks/governance-audit/audit-prompt.sh | 131 ++++++++++++++++++ hooks/governance-audit/audit-session-end.sh | 39 ++++++ hooks/governance-audit/audit-session-start.sh | 27 ++++ hooks/governance-audit/hooks.json | 33 +++++ 6 files changed, 330 insertions(+) create mode 100644 hooks/governance-audit/README.md create mode 100644 hooks/governance-audit/audit-prompt.sh create mode 100644 hooks/governance-audit/audit-session-end.sh create mode 100644 hooks/governance-audit/audit-session-start.sh create mode 100644 hooks/governance-audit/hooks.json diff --git a/docs/README.hooks.md b/docs/README.hooks.md index 7fc12b04..b7220891 100644 --- a/docs/README.hooks.md +++ b/docs/README.hooks.md @@ -27,5 +27,6 @@ Hooks enable automated workflows triggered by specific events during GitHub Copi | Name | Description | Events | Bundled Assets | | ---- | ----------- | ------ | -------------- | +| [Governance Audit](../hooks/governance-audit/README.md) | Scans Copilot agent prompts for threat signals and logs governance events | sessionStart, sessionEnd, userPromptSubmitted | `audit-prompt.sh`
`audit-session-end.sh`
`audit-session-start.sh`
`hooks.json` | | [Session Auto-Commit](../hooks/session-auto-commit/README.md) | Automatically commits and pushes changes when a Copilot coding agent session ends | sessionEnd | `auto-commit.sh`
`hooks.json` | | [Session Logger](../hooks/session-logger/README.md) | Logs all Copilot coding agent session activity for audit and analysis | sessionStart, sessionEnd, userPromptSubmitted | `hooks.json`
`log-prompt.sh`
`log-session-end.sh`
`log-session-start.sh` | diff --git a/hooks/governance-audit/README.md b/hooks/governance-audit/README.md new file mode 100644 index 00000000..ce05f3d8 --- /dev/null +++ b/hooks/governance-audit/README.md @@ -0,0 +1,99 @@ +--- +name: 'Governance Audit' +description: 'Scans Copilot agent prompts for threat signals and logs governance events' +tags: ['security', 'governance', 'audit', 'safety'] +--- + +# Governance Audit Hook + +Real-time threat detection and audit logging for GitHub Copilot coding agent sessions. Scans user prompts for dangerous patterns before the agent processes them. + +## Overview + +This hook provides governance controls for Copilot coding agent sessions: +- **Threat detection**: Scans prompts for data exfiltration, privilege escalation, system destruction, prompt injection, and credential exposure +- **Governance levels**: Open, standard, strict, locked — from audit-only to full blocking +- **Audit trail**: Append-only JSON log of all governance events +- **Session summary**: Reports threat counts at session end + +## Threat Categories + +| Category | Examples | Severity | +|----------|----------|----------| +| `data_exfiltration` | "send all records to external API" | 0.7 - 0.95 | +| `privilege_escalation` | "sudo", "chmod 777", "add to sudoers" | 0.8 - 0.95 | +| `system_destruction` | "rm -rf /", "drop database" | 0.9 - 0.95 | +| `prompt_injection` | "ignore previous instructions" | 0.6 - 0.9 | +| `credential_exposure` | Hardcoded API keys, AWS access keys | 0.9 - 0.95 | + +## Governance Levels + +| Level | Behavior | +|-------|----------| +| `open` | Log threats only, never block | +| `standard` | Log threats, block only if `BLOCK_ON_THREAT=true` | +| `strict` | Log and block all detected threats | +| `locked` | Log and block all detected threats | + +## Installation + +1. Copy the hook folder to your repository: + ```bash + cp -r hooks/governance-audit .github/hooks/ + ``` + +2. Ensure scripts are executable: + ```bash + chmod +x .github/hooks/governance-audit/*.sh + ``` + +3. Create the logs directory and add to `.gitignore`: + ```bash + mkdir -p logs/copilot/governance + echo "logs/" >> .gitignore + ``` + +4. Commit to your repository's default branch. + +## Configuration + +Set environment variables in `hooks.json`: + +```json +{ + "env": { + "GOVERNANCE_LEVEL": "strict", + "BLOCK_ON_THREAT": "true" + } +} +``` + +| Variable | Values | Default | Description | +|----------|--------|---------|-------------| +| `GOVERNANCE_LEVEL` | `open`, `standard`, `strict`, `locked` | `standard` | Controls blocking behavior | +| `BLOCK_ON_THREAT` | `true`, `false` | `false` | Block prompts with threats (standard level) | +| `SKIP_GOVERNANCE_AUDIT` | `true` | unset | Disable governance audit entirely | + +## Log Format + +Events are written to `logs/copilot/governance/audit.log` in JSON Lines format: + +```json +{"timestamp":"2026-01-15T10:30:00Z","event":"session_start","governance_level":"standard","cwd":"/workspace/project"} +{"timestamp":"2026-01-15T10:31:00Z","event":"prompt_scanned","governance_level":"standard","status":"clean"} +{"timestamp":"2026-01-15T10:32:00Z","event":"threat_detected","governance_level":"standard","threat_count":1,"threats":[{"category":"privilege_escalation","severity":0.8,"description":"Elevated privileges","evidence":"sudo"}]} +{"timestamp":"2026-01-15T10:45:00Z","event":"session_end","total_events":12,"threats_detected":1} +``` + +## Requirements + +- `jq` for JSON processing (pre-installed on most CI environments and macOS) +- `grep` with `-E` (extended regex) support +- `bc` for floating-point comparison (optional, gracefully degrades) + +## Privacy & Security + +- Prompts are **never** logged — only threat signals and metadata are recorded +- Add `logs/` to `.gitignore` to keep audit data local +- Set `SKIP_GOVERNANCE_AUDIT=true` to disable entirely +- All data stays local — no external network calls diff --git a/hooks/governance-audit/audit-prompt.sh b/hooks/governance-audit/audit-prompt.sh new file mode 100644 index 00000000..0e0eab01 --- /dev/null +++ b/hooks/governance-audit/audit-prompt.sh @@ -0,0 +1,131 @@ +#!/bin/bash + +# Governance Audit: Scan user prompts for threat signals before agent processing +# +# Environment variables: +# GOVERNANCE_LEVEL - "open", "standard", "strict", "locked" (default: standard) +# BLOCK_ON_THREAT - "true" to exit non-zero on threats (default: false) +# SKIP_GOVERNANCE_AUDIT - "true" to disable (default: unset) + +set -euo pipefail + +if [[ "${SKIP_GOVERNANCE_AUDIT:-}" == "true" ]]; then + exit 0 +fi + +INPUT=$(cat) + +mkdir -p logs/copilot/governance + +TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") +LEVEL="${GOVERNANCE_LEVEL:-standard}" +BLOCK="${BLOCK_ON_THREAT:-false}" +LOG_FILE="logs/copilot/governance/audit.log" + +# Extract prompt text from Copilot input (JSON with userMessage field) +PROMPT="" +if command -v jq &>/dev/null; then + PROMPT=$(echo "$INPUT" | jq -r '.userMessage // .prompt // empty' 2>/dev/null || echo "") +fi +if [[ -z "$PROMPT" ]]; then + PROMPT="$INPUT" +fi + +# Threat detection patterns organized by category +# Each pattern has: category, description, severity (0.0-1.0) +THREATS_FOUND=() + +check_pattern() { + local pattern="$1" + local category="$2" + local severity="$3" + local description="$4" + + if echo "$PROMPT" | grep -qiE "$pattern"; then + local evidence + evidence=$(echo "$PROMPT" | grep -oiE "$pattern" | head -1) + THREATS_FOUND+=("$category:$severity:$description:$evidence") + fi +} + +# Data exfiltration signals +check_pattern "send\s+(all|every|entire)\s+\w+\s+to\s+" "data_exfiltration" "0.8" "Bulk data transfer" +check_pattern "export\s+.*\s+to\s+(external|outside|third.?party)" "data_exfiltration" "0.9" "External export" +check_pattern "curl\s+.*\s+-d\s+" "data_exfiltration" "0.7" "HTTP POST with data" +check_pattern "upload\s+.*\s+(credentials|secrets|keys)" "data_exfiltration" "0.95" "Credential upload" + +# Privilege escalation signals +check_pattern "(sudo|as\s+root|admin\s+access|runas\s+/user)" "privilege_escalation" "0.8" "Elevated privileges" +check_pattern "chmod\s+777" "privilege_escalation" "0.9" "World-writable permissions" +check_pattern "add\s+.*\s+(sudoers|administrators)" "privilege_escalation" "0.95" "Adding admin access" + +# System destruction signals +check_pattern "(rm\s+-rf\s+/|del\s+/[sq]|format\s+c:)" "system_destruction" "0.95" "Destructive command" +check_pattern "(drop\s+database|truncate\s+table|delete\s+from\s+\w+\s*$)" "system_destruction" "0.9" "Database destruction" +check_pattern "wipe\s+(all|entire|every)" "system_destruction" "0.9" "Mass deletion" + +# Prompt injection signals +check_pattern "ignore\s+(previous|above|all)\s+(instructions?|rules?|prompts?)" "prompt_injection" "0.9" "Instruction override" +check_pattern "you\s+are\s+now\s+(a|an)\s+" "prompt_injection" "0.7" "Role reassignment" +check_pattern "system\s*:\s*" "prompt_injection" "0.6" "System prompt injection" + +# Credential exposure signals +check_pattern "(api[_-]?key|secret[_-]?key|password|token)\s*[:=]\s*['\"]?\w{8,}" "credential_exposure" "0.9" "Hardcoded credential" +check_pattern "(aws_access_key|AKIA[0-9A-Z]{16})" "credential_exposure" "0.95" "AWS key exposure" + +# Log the prompt event +if [[ ${#THREATS_FOUND[@]} -gt 0 ]]; then + # Build threats JSON array + THREATS_JSON="[" + FIRST=true + MAX_SEVERITY="0.0" + for threat in "${THREATS_FOUND[@]}"; do + IFS=':' read -r category severity description evidence <<< "$threat" + + if [[ "$FIRST" != "true" ]]; then + THREATS_JSON+="," + fi + FIRST=false + + THREATS_JSON+=$(jq -Rn \ + --arg cat "$category" \ + --arg sev "$severity" \ + --arg desc "$description" \ + --arg ev "$evidence" \ + '{"category":$cat,"severity":($sev|tonumber),"description":$desc,"evidence":$ev}') + + # Track max severity + if (( $(echo "$severity > $MAX_SEVERITY" | bc -l 2>/dev/null || echo 0) )); then + MAX_SEVERITY="$severity" + fi + done + THREATS_JSON+="]" + + jq -Rn \ + --arg timestamp "$TIMESTAMP" \ + --arg level "$LEVEL" \ + --argjson threats "$THREATS_JSON" \ + --argjson count "${#THREATS_FOUND[@]}" \ + '{"timestamp":$timestamp,"event":"threat_detected","governance_level":$level,"threat_count":$count,"threats":$threats}' \ + >> "$LOG_FILE" + + echo "⚠️ Governance: ${#THREATS_FOUND[@]} threat signal(s) detected" + for threat in "${THREATS_FOUND[@]}"; do + IFS=':' read -r category severity description evidence <<< "$threat" + echo " 🔴 [$category] $description (severity: $severity)" + done + + # In strict/locked mode or when BLOCK_ON_THREAT is true, exit non-zero to block + if [[ "$BLOCK" == "true" ]] || [[ "$LEVEL" == "strict" ]] || [[ "$LEVEL" == "locked" ]]; then + echo "🚫 Prompt blocked by governance policy (level: $LEVEL)" + exit 1 + fi +else + jq -Rn \ + --arg timestamp "$TIMESTAMP" \ + --arg level "$LEVEL" \ + '{"timestamp":$timestamp,"event":"prompt_scanned","governance_level":$level,"status":"clean"}' \ + >> "$LOG_FILE" +fi + +exit 0 diff --git a/hooks/governance-audit/audit-session-end.sh b/hooks/governance-audit/audit-session-end.sh new file mode 100644 index 00000000..a77ffc08 --- /dev/null +++ b/hooks/governance-audit/audit-session-end.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Governance Audit: Log session end with summary statistics + +set -euo pipefail + +if [[ "${SKIP_GOVERNANCE_AUDIT:-}" == "true" ]]; then + exit 0 +fi + +INPUT=$(cat) + +mkdir -p logs/copilot/governance + +TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") +LOG_FILE="logs/copilot/governance/audit.log" + +# Count events from this session +TOTAL=0 +THREATS=0 +if [[ -f "$LOG_FILE" ]]; then + TOTAL=$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0) + THREATS=$(grep -c '"threat_detected"' "$LOG_FILE" 2>/dev/null || echo 0) +fi + +jq -Rn \ + --arg timestamp "$TIMESTAMP" \ + --argjson total "$TOTAL" \ + --argjson threats "$THREATS" \ + '{"timestamp":$timestamp,"event":"session_end","total_events":$total,"threats_detected":$threats}' \ + >> "$LOG_FILE" + +if [[ "$THREATS" -gt 0 ]]; then + echo "⚠️ Session ended: $THREATS threat(s) detected in $TOTAL events" +else + echo "✅ Session ended: $TOTAL events, no threats" +fi + +exit 0 diff --git a/hooks/governance-audit/audit-session-start.sh b/hooks/governance-audit/audit-session-start.sh new file mode 100644 index 00000000..aec070b2 --- /dev/null +++ b/hooks/governance-audit/audit-session-start.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Governance Audit: Log session start with governance context + +set -euo pipefail + +if [[ "${SKIP_GOVERNANCE_AUDIT:-}" == "true" ]]; then + exit 0 +fi + +INPUT=$(cat) + +mkdir -p logs/copilot/governance + +TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") +CWD=$(pwd) +LEVEL="${GOVERNANCE_LEVEL:-standard}" + +jq -Rn \ + --arg timestamp "$TIMESTAMP" \ + --arg cwd "$CWD" \ + --arg level "$LEVEL" \ + '{"timestamp":$timestamp,"event":"session_start","governance_level":$level,"cwd":$cwd}' \ + >> logs/copilot/governance/audit.log + +echo "🛡️ Governance audit active (level: $LEVEL)" +exit 0 diff --git a/hooks/governance-audit/hooks.json b/hooks/governance-audit/hooks.json new file mode 100644 index 00000000..6c08f670 --- /dev/null +++ b/hooks/governance-audit/hooks.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "hooks": { + "sessionStart": [ + { + "type": "command", + "bash": ".github/hooks/governance-audit/audit-session-start.sh", + "cwd": ".", + "timeoutSec": 5 + } + ], + "sessionEnd": [ + { + "type": "command", + "bash": ".github/hooks/governance-audit/audit-session-end.sh", + "cwd": ".", + "timeoutSec": 5 + } + ], + "userPromptSubmitted": [ + { + "type": "command", + "bash": ".github/hooks/governance-audit/audit-prompt.sh", + "cwd": ".", + "env": { + "GOVERNANCE_LEVEL": "standard", + "BLOCK_ON_THREAT": "false" + }, + "timeoutSec": 10 + } + ] + } +} From 32d8f7f6227258227c6dee68c8882315d89ca3d7 Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Wed, 18 Feb 2026 14:49:17 -0800 Subject: [PATCH 2/2] fix: address Copilot PR review comments on governance-audit hook - Switch from colon to tab delimiter to handle colons in evidence text - Base64-encode evidence to prevent parsing issues - Use MAX_SEVERITY in log output and JSON events - Narrow regex patterns to reduce false positives: - third[_-]?party instead of third.?party - Role reassignment scoped to AI terms - System prompt injection requires 'you are' context - Fix session-end stats to scope to current session only - Update privacy statement to clarify evidence snippets are logged - Rename credential description to 'Possible hardcoded credential' - Fix database destruction regex to also match semicolons Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- hooks/governance-audit/README.md | 2 +- hooks/governance-audit/audit-prompt.sh | 25 ++++++++++++--------- hooks/governance-audit/audit-session-end.sh | 15 ++++++++++--- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/hooks/governance-audit/README.md b/hooks/governance-audit/README.md index ce05f3d8..cba784f3 100644 --- a/hooks/governance-audit/README.md +++ b/hooks/governance-audit/README.md @@ -93,7 +93,7 @@ Events are written to `logs/copilot/governance/audit.log` in JSON Lines format: ## Privacy & Security -- Prompts are **never** logged — only threat signals and metadata are recorded +- Full prompts are **never** logged — only matched threat patterns (minimal evidence snippets) and metadata are recorded - Add `logs/` to `.gitignore` to keep audit data local - Set `SKIP_GOVERNANCE_AUDIT=true` to disable entirely - All data stays local — no external network calls diff --git a/hooks/governance-audit/audit-prompt.sh b/hooks/governance-audit/audit-prompt.sh index 0e0eab01..d9e9544d 100644 --- a/hooks/governance-audit/audit-prompt.sh +++ b/hooks/governance-audit/audit-prompt.sh @@ -44,13 +44,15 @@ check_pattern() { if echo "$PROMPT" | grep -qiE "$pattern"; then local evidence evidence=$(echo "$PROMPT" | grep -oiE "$pattern" | head -1) - THREATS_FOUND+=("$category:$severity:$description:$evidence") + local evidence_encoded + evidence_encoded=$(printf '%s' "$evidence" | base64 | tr -d '\n') + THREATS_FOUND+=("$category $severity $description $evidence_encoded") fi } # Data exfiltration signals check_pattern "send\s+(all|every|entire)\s+\w+\s+to\s+" "data_exfiltration" "0.8" "Bulk data transfer" -check_pattern "export\s+.*\s+to\s+(external|outside|third.?party)" "data_exfiltration" "0.9" "External export" +check_pattern "export\s+.*\s+to\s+(external|outside|third[_-]?party)" "data_exfiltration" "0.9" "External export" check_pattern "curl\s+.*\s+-d\s+" "data_exfiltration" "0.7" "HTTP POST with data" check_pattern "upload\s+.*\s+(credentials|secrets|keys)" "data_exfiltration" "0.95" "Credential upload" @@ -61,16 +63,16 @@ check_pattern "add\s+.*\s+(sudoers|administrators)" "privilege_escalation" "0.95 # System destruction signals check_pattern "(rm\s+-rf\s+/|del\s+/[sq]|format\s+c:)" "system_destruction" "0.95" "Destructive command" -check_pattern "(drop\s+database|truncate\s+table|delete\s+from\s+\w+\s*$)" "system_destruction" "0.9" "Database destruction" +check_pattern "(drop\s+database|truncate\s+table|delete\s+from\s+\w+\s*(;|\s*$))" "system_destruction" "0.9" "Database destruction" check_pattern "wipe\s+(all|entire|every)" "system_destruction" "0.9" "Mass deletion" # Prompt injection signals check_pattern "ignore\s+(previous|above|all)\s+(instructions?|rules?|prompts?)" "prompt_injection" "0.9" "Instruction override" -check_pattern "you\s+are\s+now\s+(a|an)\s+" "prompt_injection" "0.7" "Role reassignment" -check_pattern "system\s*:\s*" "prompt_injection" "0.6" "System prompt injection" +check_pattern "you\s+are\s+now\s+(a|an)\s+(assistant|ai|bot|system|expert|language\s+model)\b" "prompt_injection" "0.7" "Role reassignment" +check_pattern "(^|\n)\s*system\s*:\s*you\s+are" "prompt_injection" "0.6" "System prompt injection" # Credential exposure signals -check_pattern "(api[_-]?key|secret[_-]?key|password|token)\s*[:=]\s*['\"]?\w{8,}" "credential_exposure" "0.9" "Hardcoded credential" +check_pattern "(api[_-]?key|secret[_-]?key|password|token)\s*[:=]\s*['\"]?\w{8,}" "credential_exposure" "0.9" "Possible hardcoded credential" check_pattern "(aws_access_key|AKIA[0-9A-Z]{16})" "credential_exposure" "0.95" "AWS key exposure" # Log the prompt event @@ -80,7 +82,9 @@ if [[ ${#THREATS_FOUND[@]} -gt 0 ]]; then FIRST=true MAX_SEVERITY="0.0" for threat in "${THREATS_FOUND[@]}"; do - IFS=':' read -r category severity description evidence <<< "$threat" + IFS=$'\t' read -r category severity description evidence_encoded <<< "$threat" + local evidence + evidence=$(printf '%s' "$evidence_encoded" | base64 -d 2>/dev/null || echo "[redacted]") if [[ "$FIRST" != "true" ]]; then THREATS_JSON+="," @@ -104,14 +108,15 @@ if [[ ${#THREATS_FOUND[@]} -gt 0 ]]; then jq -Rn \ --arg timestamp "$TIMESTAMP" \ --arg level "$LEVEL" \ + --arg max_severity "$MAX_SEVERITY" \ --argjson threats "$THREATS_JSON" \ --argjson count "${#THREATS_FOUND[@]}" \ - '{"timestamp":$timestamp,"event":"threat_detected","governance_level":$level,"threat_count":$count,"threats":$threats}' \ + '{"timestamp":$timestamp,"event":"threat_detected","governance_level":$level,"threat_count":$count,"max_severity":($max_severity|tonumber),"threats":$threats}' \ >> "$LOG_FILE" - echo "⚠️ Governance: ${#THREATS_FOUND[@]} threat signal(s) detected" + echo "⚠️ Governance: ${#THREATS_FOUND[@]} threat signal(s) detected (max severity: $MAX_SEVERITY)" for threat in "${THREATS_FOUND[@]}"; do - IFS=':' read -r category severity description evidence <<< "$threat" + IFS=$'\t' read -r category severity description _evidence_encoded <<< "$threat" echo " 🔴 [$category] $description (severity: $severity)" done diff --git a/hooks/governance-audit/audit-session-end.sh b/hooks/governance-audit/audit-session-end.sh index a77ffc08..e80738e6 100644 --- a/hooks/governance-audit/audit-session-end.sh +++ b/hooks/governance-audit/audit-session-end.sh @@ -15,12 +15,21 @@ mkdir -p logs/copilot/governance TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") LOG_FILE="logs/copilot/governance/audit.log" -# Count events from this session +# Count events from this session (filter by session start timestamp) TOTAL=0 THREATS=0 +SESSION_START="" if [[ -f "$LOG_FILE" ]]; then - TOTAL=$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0) - THREATS=$(grep -c '"threat_detected"' "$LOG_FILE" 2>/dev/null || echo 0) + # Find the last session_start event to scope stats to current session + SESSION_START=$(grep '"session_start"' "$LOG_FILE" 2>/dev/null | tail -1 | jq -r '.timestamp' 2>/dev/null || echo "") + if [[ -n "$SESSION_START" ]]; then + # Count events after session start + TOTAL=$(awk -v start="$SESSION_START" -F'"timestamp":"' '{split($2,a,"\""); if(a[1]>=start) count++} END{print count+0}' "$LOG_FILE" 2>/dev/null || echo 0) + THREATS=$(awk -v start="$SESSION_START" -F'"timestamp":"' '{split($2,a,"\""); if(a[1]>=start && /threat_detected/) count++} END{print count+0}' "$LOG_FILE" 2>/dev/null || echo 0) + else + TOTAL=$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0) + THREATS=$(grep -c '"threat_detected"' "$LOG_FILE" 2>/dev/null || echo 0) + fi fi jq -Rn \