Skip to content

Commit 95ea73b

Browse files
committed
changes
1 parent 9f550bd commit 95ea73b

12 files changed

Lines changed: 263 additions & 1846 deletions

File tree

Plugins/IDSPlugin/IDSPlugin.stub.cs

Lines changed: 0 additions & 64 deletions
This file was deleted.

SentryCore/IPC/ProcessRunner.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,8 @@ public async Task<string> RunInitDbAsync(string dbPath, string nvdKey, Action<st
7575
var script = Path.Combine(_pythonScriptsPath, "init_db.py");
7676
// Use -u to force unbuffered stdout/stderr so the WPF UI receives logs in real-time
7777
var args = $"-u \"{script}\" --db \"{dbPath}\" --days-back 30";
78-
if (!string.IsNullOrWhiteSpace(nvdKey))
79-
{
80-
args += $" --nvd-key \"{nvdKey}\"";
81-
}
78+
// Always pass --nvd-key to override any system environment variables if the user cleared it in the UI
79+
args += $" --nvd-key \"{nvdKey ?? ""}\"";
8280
return await RunPythonAsync(args, 300_000, onOutputData); // Allow 5 minutes for massive NVD sync
8381
}
8482

SentryLegacyService/LegacyServiceHost.cs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using SentryShield.Core.Models;
55
using System.ServiceProcess;
66
using System.Threading;
7+
using System.Runtime.InteropServices;
78
using Microsoft.Extensions.Configuration;
89
using Microsoft.Extensions.Logging;
910
using Microsoft.Extensions.Logging.Abstractions;
@@ -38,6 +39,22 @@ internal sealed class LegacyServiceHost : ServiceBase
3839
private readonly EventLogWriter _eventLog;
3940
private readonly LegacyYaraGuard _yaraGuard;
4041

42+
[DllImport("wtsapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
43+
private static extern bool WTSSendMessage(
44+
IntPtr hServer,
45+
int SessionId,
46+
string pTitle,
47+
int TitleLength,
48+
string pMessage,
49+
int MessageLength,
50+
int Style,
51+
int Timeout,
52+
out int pResponse,
53+
bool bWait);
54+
55+
[DllImport("kernel32.dll")]
56+
private static extern int WTSGetActiveConsoleSessionId();
57+
4158
private Timer? _timer;
4259
private DateTime _lastVulnScan = DateTime.MinValue;
4360
private DateTime _lastDriverAudit = DateTime.MinValue;
@@ -73,6 +90,8 @@ protected override void OnStart(string[] args)
7390
_eventLog.WriteInfo("SentryShield legacy service starting.", eventId: 1000);
7491
_logger.LogInformation("SentryShield legacy service starting at {Time}", DateTime.Now);
7592

93+
ThreadPool.QueueUserWorkItem(_ => NotifyLegacyUserAsync().GetAwaiter().GetResult());
94+
7695
// Probe Python availability once before starting the timer
7796
_yaraGuard.Probe();
7897

@@ -138,6 +157,37 @@ private void RunSafe(string scanName, Func<System.Threading.Tasks.Task> action)
138157
// Individual scan runners (mirror SentryWorker logic)
139158
// -------------------------------------------------------------------------
140159

160+
private async System.Threading.Tasks.Task NotifyLegacyUserAsync()
161+
{
162+
try
163+
{
164+
string title = "SentryShield Security Alert";
165+
string message = "WARNING: This machine is running a legacy operating system.\nPlease update to a modern OS to ensure security.";
166+
int sessionId = WTSGetActiveConsoleSessionId();
167+
168+
// 0x30 = MB_ICONEXCLAMATION (Warning icon)
169+
WTSSendMessage(IntPtr.Zero, sessionId, title, title.Length * 2, message, message.Length * 2, 0x30, 0, out _, true);
170+
171+
var finding = new Core.Models.Finding
172+
{
173+
FindingType = "hardening",
174+
Severity = "CRITICAL",
175+
Title = "Legacy Operating System Detected",
176+
Description = message,
177+
AffectedComponent = Environment.OSVersion.VersionString,
178+
Remediation = "Upgrade to Windows 10/11 IoT Enterprise.",
179+
DetectionTimestamp = DateTime.UtcNow
180+
};
181+
182+
await _historyDb.SaveFindingsAsync(new List<Core.Models.Finding> { finding });
183+
_logger.LogWarning("Legacy OS notification sent and finding logged.");
184+
}
185+
catch (Exception ex)
186+
{
187+
_logger.LogError(ex, "Failed to notify legacy user");
188+
}
189+
}
190+
141191
private async System.Threading.Tasks.Task RunPluginsAsync()
142192
{
143193
var start = DateTime.UtcNow;
@@ -189,19 +239,37 @@ await _historyDb.RecordScanAsync("dynamic_plugins", findings.Count,
189239

190240
private async System.Threading.Tasks.Task RunDriverAuditAsync()
191241
{
242+
var startTime = DateTime.UtcNow;
192243
_logger.LogInformation("Starting driver audit...");
193244
var auditor = new DriverAuditor(_logger);
194245
var findings = await auditor.AuditAsync();
195246
await _historyDb.SaveFindingsAsync(findings);
196-
_logger.LogInformation("Driver audit complete: {Count} findings", findings.Count);
247+
248+
var elapsed = (int)(DateTime.UtcNow - startTime).TotalSeconds;
249+
await _historyDb.RecordScanAsync("driver", findings.Count,
250+
findings.Count(f => f.Severity == "CRITICAL"),
251+
findings.Count(f => f.Severity == "HIGH"),
252+
findings.Count(f => f.Severity == "MEDIUM"),
253+
elapsed);
254+
255+
_logger.LogInformation("Driver audit complete: {Count} findings in {Elapsed}s", findings.Count, elapsed);
197256
}
198257

199258
private async System.Threading.Tasks.Task RunHardeningCheckAsync()
200259
{
260+
var startTime = DateTime.UtcNow;
201261
_logger.LogInformation("Starting hardening check...");
202262
var audit = new HardeningAudit(_logger);
203263
var findings = await audit.CheckAsync();
204264
await _historyDb.SaveFindingsAsync(findings);
205-
_logger.LogInformation("Hardening check complete: {Count} findings", findings.Count);
265+
266+
var elapsed = (int)(DateTime.UtcNow - startTime).TotalSeconds;
267+
await _historyDb.RecordScanAsync("hardening", findings.Count,
268+
findings.Count(f => f.Severity == "CRITICAL"),
269+
findings.Count(f => f.Severity == "HIGH"),
270+
findings.Count(f => f.Severity == "MEDIUM"),
271+
elapsed);
272+
273+
_logger.LogInformation("Hardening check complete: {Count} findings in {Elapsed}s", findings.Count, elapsed);
206274
}
207275
}

SentryPython/cert_in_parser.py

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
SentryShield v1.0 — CERT-In Advisory Parser (cert_in_parser.py)
2+
SentryShield v2.5 — CERT-In Advisory Parser (cert_in_parser.py)
33
44
Fetches live vulnerability advisories from India's CERT-In:
55
https://www.cert-in.org.in/
@@ -35,13 +35,14 @@
3535
import sqlite3
3636
import sys
3737
import time
38-
import ssl
38+
import http.client
3939
from datetime import datetime, timedelta
4040
from html.parser import HTMLParser
4141
from pathlib import Path
42+
from urllib.parse import quote
4243
from urllib.error import HTTPError, URLError
43-
from urllib.parse import urlencode, quote
4444
from urllib.request import Request, urlopen
45+
import ssl
4546
from xml.etree import ElementTree
4647

4748
logging.basicConfig(
@@ -71,6 +72,33 @@
7172
CIVN_PATTERN = re.compile(r"CIVN-\d{4}-\d{4}", re.IGNORECASE)
7273

7374

75+
def verify_nvd_key():
76+
nvd_key = os.environ.get("NVD_API_KEY", "").strip()
77+
if not nvd_key:
78+
log.warning("[NVD] WARNING: No API key set — rate limited to 5 req/30s")
79+
return
80+
81+
log.info("[NVD] Verifying API key...")
82+
headers = {"User-Agent": "SentryShield/1.0", "apiKey": nvd_key}
83+
try:
84+
ctx = ssl._create_unverified_context() if sys.platform == "darwin" else None
85+
conn = http.client.HTTPSConnection("services.nvd.nist.gov", context=ctx, timeout=30)
86+
conn.request("GET", "/rest/json/cves/2.0?resultsPerPage=1", headers=headers)
87+
resp = conn.getresponse()
88+
resp.read()
89+
90+
if resp.status in (403, 404):
91+
raise Exception("NVD API key rejected (NIST returns 404/403 for invalid keys) — please verify your key or clear the NVD_API_KEY variable")
92+
elif resp.status != 200:
93+
log.warning(f"[NVD] API key verification failed: HTTP {resp.status}")
94+
else:
95+
log.info("[NVD] API key verified")
96+
conn.close()
97+
except Exception as e:
98+
if "rejected" in str(e): raise
99+
log.warning(f"[NVD] API key verification failed: {e}")
100+
101+
74102
# ---------------------------------------------------------------------------
75103
# CERT-In RSS parser
76104
# ---------------------------------------------------------------------------
@@ -86,7 +114,6 @@ def parse(self, xml_text: str) -> list[dict]:
86114
channel = root.find("channel")
87115
if channel is None:
88116
# Try with namespace
89-
ns = {"": "http://www.w3.org/2005/Atom"}
90117
items = root.findall(".//item") or root.findall(".//entry")
91118
else:
92119
items = channel.findall("item")
@@ -208,43 +235,51 @@ def fetch_nvd_cve(cve_id: str, retry: int = 3) -> dict | None:
208235
Fetch a single CVE's full record from NVD API.
209236
Returns the parsed CVE dict or None on failure.
210237
"""
238+
nvd_key = os.environ.get("NVD_API_KEY", "").strip()
211239
headers = {"User-Agent": "SentryShield/1.0 (Security Scanner)"}
212-
if NVD_API_KEY:
213-
headers["apiKey"] = NVD_API_KEY
240+
if nvd_key:
241+
headers["apiKey"] = nvd_key
214242

215243
url = f"{NVD_CVE_URL}?cveId={cve_id}"
216244

217245
for attempt in range(retry):
218246
try:
219-
req = Request(url, headers=headers)
220-
ctx = ssl._create_unverified_context()
221-
with urlopen(req, timeout=20, context=ctx) as resp:
222-
data = json.loads(resp.read().decode("utf-8"))
247+
ctx = ssl._create_unverified_context() if sys.platform == "darwin" else None
248+
conn = http.client.HTTPSConnection("services.nvd.nist.gov", context=ctx, timeout=20)
249+
conn.request("GET", f"/rest/json/cves/2.0?cveId={cve_id}", headers=headers)
250+
resp = conn.getresponse()
251+
body = resp.read().decode("utf-8")
252+
code = resp.status
253+
conn.close()
254+
except Exception as e:
255+
log.warning("NVD fetch error for %s: %s", cve_id, e)
256+
break
223257

258+
if code == 200:
259+
data = json.loads(body)
224260
vulns = data.get("vulnerabilities", [])
225261
if not vulns:
226262
return None
227-
228263
return vulns[0].get("cve", {})
229264

230-
except HTTPError as e:
231-
if e.code == 404:
232-
return None # CVE doesn't exist in NVD
233-
if e.code == 429:
234-
log.warning("NVD rate limit hit — waiting 35s...")
235-
time.sleep(35)
265+
if code == 403 or (code == 404 and "cveId=" not in url):
266+
raise Exception("NVD API key rejected (NIST returned 404/403) — verify your key or clear NVD_API_KEY")
267+
if code == 404:
268+
log.error("NVD HTTP 404 Not Found for %s", url)
269+
return None # CVE doesn't exist in NVD
270+
if code == 429:
271+
if attempt < retry - 1:
272+
wait_time = 6 * (2 ** attempt)
273+
log.warning("NVD rate limit hit (HTTP 429). Waiting %ds before retrying...", wait_time)
274+
time.sleep(wait_time)
275+
continue
236276
else:
237-
log.warning("NVD HTTP %d for %s", e.code, cve_id)
277+
log.error("NVD HTTP error: 429 Too Many Requests (max retries reached)")
238278
break
239-
except URLError as e:
240-
log.warning("NVD connection error for %s: %s", cve_id, e.reason)
241-
break
242-
except Exception as e:
243-
log.warning("NVD fetch error for %s: %s", cve_id, e)
279+
else:
280+
log.warning("NVD HTTP %d for %s", code, cve_id)
244281
break
245282

246-
time.sleep(2 ** attempt) # Exponential backoff
247-
248283
return None
249284

250285

@@ -394,7 +429,7 @@ def sync(self, days_back: int = 30) -> dict:
394429
log.debug(" %s already in DB — skipped", cve_id)
395430

396431
# NVD rate limit: 5 req/30s without key, 50 req/30s with key
397-
time.sleep(0.8 if NVD_API_KEY else 6.5)
432+
time.sleep(1.0 if os.environ.get("NVD_API_KEY", "").strip() else 6.5)
398433

399434
elapsed = (datetime.utcnow() - start).total_seconds()
400435
summary = {
@@ -428,7 +463,7 @@ def sync_single_advisory(self, civn_id: str) -> int:
428463
record = nvd_cve_to_record(cve_data, civn_id=civn_id)
429464
if record and self._insert_record(record):
430465
inserted += 1
431-
time.sleep(0.8 if NVD_API_KEY else 6.5)
466+
time.sleep(1.0 if os.environ.get("NVD_API_KEY", "").strip() else 6.5)
432467

433468
return inserted
434469

@@ -600,6 +635,8 @@ def main():
600635

601636
args = parser.parse_args()
602637

638+
verify_nvd_key()
639+
603640
if not Path(args.db).exists():
604641
log.error("Database not found: %s — run init_db.py first", args.db)
605642
sys.exit(1)

0 commit comments

Comments
 (0)