Add shadow security finding inbox
CI / Rust checks (push) Canceled after 0s
CI / Docs and registry checks (push) Canceled after 0s
CI / Smoke checks (push) Canceled after 0s
Coverage / Coverage baseline (push) Canceled after 0s
Security / Cargo audit (push) Canceled after 0s
Security / Cargo deny (push) Canceled after 0s
Security / Secret pattern check (push) Canceled after 0s
Security / Dependency review (push) Canceled after 0s
CI / Rust checks (push) Canceled after 0s
CI / Docs and registry checks (push) Canceled after 0s
CI / Smoke checks (push) Canceled after 0s
Coverage / Coverage baseline (push) Canceled after 0s
Security / Cargo audit (push) Canceled after 0s
Security / Cargo deny (push) Canceled after 0s
Security / Secret pattern check (push) Canceled after 0s
Security / Dependency review (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ENGINE="${CONTAINMENT_ENGINE_BIN:-}"
|
||||
POLICY="${1:-$ROOT_DIR/configs/containment-policy.example.json}"
|
||||
FINDING="${2:-$ROOT_DIR/configs/containment-finding.example.json}"
|
||||
FIREWALL_REQUEST="${3:-$ROOT_DIR/configs/windows-firewall-containment-request.example.json}"
|
||||
TMP_DIR="$(mktemp -d /tmp/containment-shadow-smoke.XXXXXX)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
if [[ -z "$ENGINE" ]]; then
|
||||
for candidate in \
|
||||
"${CARGO_TARGET_DIR:-}/debug/containment-engine" \
|
||||
"${CARGO_TARGET_DIR:-}/release/containment-engine" \
|
||||
"$ROOT_DIR/adk-rust/target/debug/containment-engine" \
|
||||
"$ROOT_DIR/adk-rust/target/release/containment-engine" \
|
||||
"/usr/local/bin/containment-engine"; do
|
||||
if [[ -n "$candidate" && -x "$candidate" ]]; then
|
||||
ENGINE="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z "$ENGINE" ]]; then
|
||||
printf 'containment-engine binary not found. Build: cargo build --manifest-path adk-rust/Cargo.toml -p containment-engine\n' >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
validate_payload() {
|
||||
local expected_status="$1"
|
||||
python3 -c '
|
||||
import json
|
||||
import sys
|
||||
|
||||
expected_status = sys.argv[1]
|
||||
payload = json.load(sys.stdin)
|
||||
if payload.get("would_mutate") is not False:
|
||||
raise SystemExit("containment smoke failed: would_mutate must be false")
|
||||
status = payload.get("decision_status")
|
||||
if status != expected_status:
|
||||
raise SystemExit(f"containment smoke failed: expected {expected_status!r}, got {status!r}")
|
||||
print(f"containment_shadow_smoke=ok status={status}")
|
||||
' "$expected_status"
|
||||
}
|
||||
|
||||
disabled_out="$("$ENGINE" decide --policy "$POLICY" --finding "$FINDING" --pretty)"
|
||||
printf '%s\n' "$disabled_out"
|
||||
validate_payload "disabled" <<<"$disabled_out"
|
||||
|
||||
shadow_policy="$TMP_DIR/containment-policy-shadow.json"
|
||||
python3 - "$POLICY" "$shadow_policy" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
payload = json.load(open(sys.argv[1], encoding="utf-8"))
|
||||
payload["enabled"] = True
|
||||
payload["mode"] = "shadow"
|
||||
json.dump(payload, open(sys.argv[2], "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
||||
PY
|
||||
|
||||
shadow_out="$("$ENGINE" decide --policy "$shadow_policy" --finding "$FINDING" --pretty)"
|
||||
printf '%s\n' "$shadow_out"
|
||||
validate_payload "shadow_recommended" <<<"$shadow_out"
|
||||
|
||||
firewall_plan="$TMP_DIR/windows-firewall-plan.json"
|
||||
"$ENGINE" windows-firewall plan --request "$FIREWALL_REQUEST" --pretty >"$firewall_plan"
|
||||
cat "$firewall_plan"
|
||||
|
||||
python3 - "$firewall_plan" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
payload = json.load(open(sys.argv[1], encoding="utf-8"))
|
||||
if payload.get("executor") != "windows_firewall":
|
||||
raise SystemExit("firewall smoke failed: executor must be windows_firewall")
|
||||
if payload.get("blockers"):
|
||||
raise SystemExit(f"firewall smoke failed: unexpected blockers {payload['blockers']!r}")
|
||||
if not payload.get("apply_commands") or not payload.get("rollback_commands"):
|
||||
raise SystemExit("firewall smoke failed: apply/rollback commands must exist")
|
||||
print("windows_firewall_plan_smoke=ok")
|
||||
PY
|
||||
|
||||
firewall_apply_out="$("$ENGINE" windows-firewall apply --plan "$firewall_plan" --confirm-apply YES --pretty)"
|
||||
printf '%s\n' "$firewall_apply_out"
|
||||
|
||||
python3 -c '
|
||||
import json
|
||||
import sys
|
||||
|
||||
payload = json.load(sys.stdin)
|
||||
if payload.get("execution_status") != "dry_run_commands_ready":
|
||||
raise SystemExit("firewall apply smoke failed: expected dry_run_commands_ready")
|
||||
if payload.get("would_mutate") is not False:
|
||||
raise SystemExit("firewall apply smoke failed: dry-run must not mutate")
|
||||
print("windows_firewall_apply_dry_run_smoke=ok")
|
||||
' <<<"$firewall_apply_out"
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
BIN="${SECURITY_FINDING_INBOX_BIN:-}"
|
||||
SAMPLE="${1:-$ROOT_DIR/configs/security/security-finding.example.json}"
|
||||
TMP_DIR="$(mktemp -d /tmp/security-finding-inbox-smoke.XXXXXX)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
if [[ -z "$BIN" ]]; then
|
||||
for candidate in \
|
||||
"${CARGO_TARGET_DIR:-}/debug/security-finding-inbox" \
|
||||
"${CARGO_TARGET_DIR:-}/release/security-finding-inbox" \
|
||||
"$ROOT_DIR/adk-rust/target/debug/security-finding-inbox" \
|
||||
"$ROOT_DIR/adk-rust/target/release/security-finding-inbox" \
|
||||
"/usr/local/bin/security-finding-inbox"; do
|
||||
if [[ -n "$candidate" && -x "$candidate" ]]; then
|
||||
BIN="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z "$BIN" ]]; then
|
||||
printf 'security-finding-inbox binary not found. Build: cargo build --manifest-path adk-rust/Cargo.toml -p security-finding-inbox\n' >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
"$BIN" schema | grep -q 'CREATE TABLE IF NOT EXISTS analytics_1c.security_findings'
|
||||
"$BIN" validate --input "$SAMPLE" | python3 -c '
|
||||
import json
|
||||
import sys
|
||||
payload = json.load(sys.stdin)
|
||||
assert payload["ok"] is True
|
||||
assert payload["rows"] == 1
|
||||
assert payload["finding_ids"][0].startswith("sf-")
|
||||
print("security_finding_validate=ok")
|
||||
'
|
||||
|
||||
"$BIN" ingest --input "$SAMPLE" --dry-run | python3 -c '
|
||||
import json
|
||||
import sys
|
||||
payload = json.load(sys.stdin)
|
||||
assert payload["ok"] is True
|
||||
assert payload["dry_run"] is True
|
||||
assert payload["rows"] == 1
|
||||
print("security_finding_ingest_dry_run=ok")
|
||||
'
|
||||
|
||||
mkdir -p "$TMP_DIR/hayabusa-report"
|
||||
cat >"$TMP_DIR/hayabusa-report/timeline.jsonl" <<'EOF'
|
||||
{"Level":"high","RuleTitle":"PowerShell Credential Dump","Timestamp":"2026-06-25T10:00:00Z"}
|
||||
{"Level":"crit","RuleTitle":"Suspicious Credential Access","Timestamp":"2026-06-25T10:01:00Z"}
|
||||
EOF
|
||||
cat >"$TMP_DIR/hayabusa-report/logon-summary-failed.csv" <<'EOF'
|
||||
header
|
||||
1
|
||||
2
|
||||
EOF
|
||||
cat >"$TMP_DIR/latest-intake.json" <<EOF
|
||||
{
|
||||
"host": "HOST-EXAMPLE",
|
||||
"status": "ok",
|
||||
"intake_id": "smoke-intake-001",
|
||||
"package_path": "$TMP_DIR/HOST-EXAMPLE.zip",
|
||||
"sha256": "demo",
|
||||
"report_dir": "$TMP_DIR/hayabusa-report"
|
||||
}
|
||||
EOF
|
||||
"$BIN" ingest-hayabusa --intake "$TMP_DIR/latest-intake.json" --min-severity low --dry-run | python3 -c '
|
||||
import json
|
||||
import sys
|
||||
payload = json.load(sys.stdin)
|
||||
assert payload["ok"] is True
|
||||
assert payload["dry_run"] is True
|
||||
assert payload["rows"] == 1
|
||||
print("security_finding_hayabusa_ingest_dry_run=ok")
|
||||
'
|
||||
|
||||
cat >"$TMP_DIR/velociraptor.jsonl" <<'EOF'
|
||||
{"Hostname":"HOST-EXAMPLE","Artifact":"Windows.Hayabusa.Monitoring","Severity":"high","Message":"Velociraptor smoke finding","User":"user-example"}
|
||||
EOF
|
||||
"$BIN" ingest-velociraptor-json --input "$TMP_DIR/velociraptor.jsonl" --dry-run | python3 -c '
|
||||
import json
|
||||
import sys
|
||||
payload = json.load(sys.stdin)
|
||||
assert payload["ok"] is True
|
||||
assert payload["dry_run"] is True
|
||||
assert payload["rows"] == 1
|
||||
print("security_finding_velociraptor_ingest_dry_run=ok")
|
||||
'
|
||||
|
||||
"$BIN" workflow \
|
||||
--finding-id sf-demo \
|
||||
--event-type approved \
|
||||
--actor smoke \
|
||||
--comment "dry-run approval" \
|
||||
--dry-run | python3 -c '
|
||||
import json
|
||||
import sys
|
||||
payload = json.load(sys.stdin)
|
||||
assert payload["ok"] is True
|
||||
assert payload["dry_run"] is True
|
||||
assert payload["event_type"] == "approved"
|
||||
print("security_finding_workflow_dry_run=ok")
|
||||
'
|
||||
|
||||
"$BIN" executor --help >/dev/null
|
||||
printenv SECURITY_FINDING_INBOX_SKIP_EXECUTOR_SMOKE >/dev/null 2>&1 || \
|
||||
printf 'security_finding_executor_cli=ok\n'
|
||||
Reference in New Issue
Block a user