Routine-Poll-Logs nicht in DB persistieren
- AddLog() neuer Parameter 'persist' (default true) - 4 Routine-Poll-Logs nur noch im In-Memory-Log: - '0 PushEvents empfangen' - 'X Events, 0 neue PushEvents' - 'X Events, X neue PushEvents → X scannen' - 'X Diffs gescannt, 0 neue Keys' - 'X Diffs gescannt, X neue Keys' wird bei Keys > 0 weiterhin persistiert
This commit is contained in:
1 parent
30662caab7
commit
fea605dfe5
3 files changed
+359
-5
No files matched your search
@@ -0,0 +1,165 @@
|
|||||||
|
# Umsetzungsplan: Google Dorks Scanner
|
||||||
|
|
||||||
|
## Zusammenfassung
|
||||||
|
Suche nach geleakten API-Keys über Google Programmable Search Engine (Custom Search JSON API).
|
||||||
|
Google-Dork-Queries wie `"sk-ant-api03"` finden Keys auf beliebigen Webseiten, Foren, Blogs und Dokumentationen.
|
||||||
|
|
||||||
|
## Warum Google Dorks?
|
||||||
|
- Google indexiert Milliarden von Seiten – Pastebin, StackOverflow, Foren, Blogs, Docs
|
||||||
|
- Dork-Queries mit exakten Key-Prefixes sind extrem präzise (wenig False Positives)
|
||||||
|
- Ergänzt Serper: Serper liefert Snippets, Google CSE liefert **andere Ergebnisse** (unterschiedliche Indexierung/Ranking)
|
||||||
|
- Findet Keys auf Seiten die Serper möglicherweise nicht abdeckt
|
||||||
|
|
||||||
|
## ⚠️ Einschränkung: Closed for New Customers
|
||||||
|
- **Die Custom Search JSON API ist für Neukunden gesperrt** (seit ~2026)
|
||||||
|
- Bestehende Kunden haben bis **1. Januar 2027** Zugriff
|
||||||
|
- Google empfiehlt als Alternative: Vertex AI Search (bis 50 Domains) oder kostenpflichtige Full-Web-Search
|
||||||
|
- **Konsequenz:** Nur umsetzbar wenn der User bereits einen CSE-Account hat oder eine Alternative nutzt
|
||||||
|
- Der Plan wird trotzdem erstellt, da bestehende Accounts weiterhin funktionieren
|
||||||
|
|
||||||
|
## API-Analyse
|
||||||
|
|
||||||
|
### Custom Search JSON API
|
||||||
|
```
|
||||||
|
GET https://www.googleapis.com/customsearch/v1?key={API_KEY}&cx={SEARCH_ENGINE_ID}&q="sk-ant-api03"
|
||||||
|
```
|
||||||
|
|
||||||
|
- **100 Queries/Tag gratis** (danach $5 pro 1.000 Queries, max 10.000/Tag)
|
||||||
|
- Benötigt: Google Cloud API Key + Programmable Search Engine ID (cx)
|
||||||
|
- PSE muss auf "Search the entire web" konfiguriert sein
|
||||||
|
- Max 10 Ergebnisse pro Query (paginierbar mit `start` Parameter, max 100 Ergebnisse pro Query)
|
||||||
|
- Response: JSON mit `items[].link`, `items[].snippet`, `items[].title`
|
||||||
|
|
||||||
|
### Suchstrategie
|
||||||
|
Mit 100 Queries/Tag und max 10 Ergebnissen/Query → **max 1.000 URLs pro Tag**.
|
||||||
|
|
||||||
|
Optimale Verteilung der 100 Queries:
|
||||||
|
- Anthropic-Patterns: ~20 Queries (höchste Priorität)
|
||||||
|
- OpenAI-Patterns: ~20 Queries
|
||||||
|
- Andere Key-Typen: ~20 Queries
|
||||||
|
- Varianten/Ergänzungen: ~20 Queries
|
||||||
|
- Reserve: ~20 Queries
|
||||||
|
|
||||||
|
### Dork-Queries (Beispiele)
|
||||||
|
```
|
||||||
|
"sk-ant-oat01-"
|
||||||
|
"sk-ant-api03-"
|
||||||
|
"sk-proj-" "ANTHROPIC_API_KEY"
|
||||||
|
"ANTHROPIC_API_KEY" filetype:env
|
||||||
|
"sk-ant-" site:pastebin.com
|
||||||
|
"sk-ant-" site:gist.github.com
|
||||||
|
"sk-ant-" site:stackoverflow.com
|
||||||
|
"AKIA" "secret" filetype:env
|
||||||
|
"ghp_" site:pastebin.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementierungsplan
|
||||||
|
|
||||||
|
### Phase 1: Config – Server Settings
|
||||||
|
|
||||||
|
Zwei neue Settings in `ServerSettingsService.cs`:
|
||||||
|
```csharp
|
||||||
|
("GoogleCseApiKey", "text", "", "API", "Google CSE API Key", "API Key für Google Custom Search JSON API"),
|
||||||
|
("GoogleCseEngineId", "text", "", "API", "Google CSE Engine ID", "Search Engine ID (cx) der Programmable Search Engine"),
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Neue Scan-Methode `ScanGoogleCse`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private async Task<List<(string Key, string Url)>> ScanGoogleCse(CancellationToken ct)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ablauf:**
|
||||||
|
1. Settings `GoogleCseApiKey` und `GoogleCseEngineId` laden
|
||||||
|
2. Für jeden Suchbegriff aus `KeyPatterns.ForCodeSearch`:
|
||||||
|
- Query bauen: `"sk-ant-oat01"` (exakte Phrase)
|
||||||
|
- `GET https://www.googleapis.com/customsearch/v1?key={key}&cx={cx}&q={query}&num=10`
|
||||||
|
- Response parsen: `items[].snippet` + `items[].link`
|
||||||
|
- Snippets mit `KeyPatterns.Compiled` Regex scannen
|
||||||
|
- Bei Treffer: Key + URL sammeln
|
||||||
|
- Optional: Seite herunterladen und vollständig scannen (bei Snippet-Treffern)
|
||||||
|
3. Query-Budget tracken (max 100/Tag)
|
||||||
|
4. Zwischen Requests: 1s Pause (kein offizielles Rate-Limit, aber Best Practice)
|
||||||
|
|
||||||
|
### Phase 3: Integration in RunScan
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// In RunScan(), nach Serper-Scan:
|
||||||
|
if (mode != "quick")
|
||||||
|
{
|
||||||
|
var cseApiKey = _settings.Get("GoogleCseApiKey");
|
||||||
|
var cseEngineId = _settings.Get("GoogleCseEngineId");
|
||||||
|
if (!string.IsNullOrWhiteSpace(cseApiKey) && !string.IsNullOrWhiteSpace(cseEngineId))
|
||||||
|
{
|
||||||
|
SetProgress("Google Custom Search", "Starte…");
|
||||||
|
var cseKeys = await ScanGoogleCse(ct);
|
||||||
|
allFoundKeys.AddRange(cseKeys.Select(k => (k.Key, k.Url, "google_cse")));
|
||||||
|
AddLog("scan", "Google CSE: " + cseKeys.Count + " Key(s) gefunden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Mode `quick`:** Kein CSE-Scan (Query-Budget schonen)
|
||||||
|
- **Mode `web`:** CSE-Scan aktiv (Web-Quelle)
|
||||||
|
- **Mode `full`:** CSE-Scan aktiv
|
||||||
|
|
||||||
|
### Phase 4: Query-Budget-Management
|
||||||
|
|
||||||
|
Da nur 100 Queries/Tag gratis sind, braucht es ein Budget-Tracking:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Tägliches Query-Budget
|
||||||
|
private int _cseQueriesUsedToday;
|
||||||
|
private string _cseLastResetDate;
|
||||||
|
|
||||||
|
private bool CanUseCseQuery()
|
||||||
|
{
|
||||||
|
var today = DateTime.UtcNow.ToString("yyyy-MM-dd");
|
||||||
|
if (_cseLastResetDate != today)
|
||||||
|
{
|
||||||
|
_cseQueriesUsedToday = 0;
|
||||||
|
_cseLastResetDate = today;
|
||||||
|
}
|
||||||
|
return _cseQueriesUsedToday < 100;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 5: Snippet-Scanning vs. Page-Download
|
||||||
|
|
||||||
|
**Zwei Strategien:**
|
||||||
|
|
||||||
|
1. **Snippet-Only (empfohlen):** Google-Snippets enthalten oft genug vom Key um ihn zu erkennen. Spart Requests und ist schnell.
|
||||||
|
2. **Snippet + Page-Download:** Wenn Snippet einen Partial-Match hat (Key-Prefix aber nicht vollständig), die Seite herunterladen und vollständig scannen. Kostet zusätzliche HTTP-Requests, findet aber mehr Keys.
|
||||||
|
|
||||||
|
**Empfehlung:** Snippet-Only als Standard, Page-Download optional.
|
||||||
|
|
||||||
|
## Response-Format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"title": "Some Forum Post",
|
||||||
|
"link": "https://example.com/post/123",
|
||||||
|
"snippet": "...my API key is sk-ant-api03-abc123..."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"searchInformation": {
|
||||||
|
"totalResults": "42"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Aufwandsschätzung
|
||||||
|
- **Implementierung:** ~150-200 Zeilen Code
|
||||||
|
- **Komplexität:** Niedrig (einfache REST API, Standard JSON)
|
||||||
|
- **Erwarteter Ertrag:** Mittel (ergänzt Serper, andere Ergebnisse möglich)
|
||||||
|
- **Kosten:** Gratis bis 100 Queries/Tag, danach $5/1.000
|
||||||
|
|
||||||
|
## Risiken
|
||||||
|
- ⚠️ **API ist für Neukunden gesperrt** – nur mit bestehendem Account nutzbar
|
||||||
|
- ⚠️ **Deadline: 1. Januar 2027** – danach muss auf Alternative migriert werden
|
||||||
|
- 100 Queries/Tag sind begrenzt → intelligente Query-Auswahl nötig
|
||||||
|
- Snippets können abgeschnittene Keys enthalten → Page-Download als Fallback
|
||||||
|
- Überschneidung mit Serper möglich → Deduplication über `_db.ScannedKeyExists()`
|
||||||
|
- Google kann Keys in Snippets maskieren (unwahrscheinlich, aber möglich)
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
# Umsetzungsplan: Pastebin Scraping Scanner
|
||||||
|
|
||||||
|
## Zusammenfassung
|
||||||
|
Kontinuierliches Scraping neuer öffentlicher Pastebin-Pastes über die Scraping API.
|
||||||
|
Pastebin ist eine der häufigsten Quellen für geleakte API-Keys, Credentials und Secrets.
|
||||||
|
|
||||||
|
## Warum Pastebin?
|
||||||
|
- **Klassische Dump-Plattform** für Secrets, Credentials, Config-Dateien
|
||||||
|
- User posten Keys zum Teilen, Debuggen oder versehentlich
|
||||||
|
- Automated Dumps von Credential-Stealern landen häufig auf Pastebin
|
||||||
|
- Kein Suchindex → man muss den Firehose-Stream der neuen Pastes scannen
|
||||||
|
- Ergänzt alle anderen Scanner die nur per Suchquery arbeiten
|
||||||
|
|
||||||
|
## Voraussetzung: Pastebin PRO Account
|
||||||
|
- **Pastebin PRO** ($29 Lifetime) ist **zwingend erforderlich**
|
||||||
|
- IP-Adresse muss auf https://pastebin.com/doc_scraping_api gewhitelisted werden
|
||||||
|
- Nur 1 IP pro Account, kann aber beliebig oft geändert werden (Änderungen greifen innerhalb 1 Minute)
|
||||||
|
- Ohne Whitelist → API-Zugriff wird blockiert
|
||||||
|
|
||||||
|
## API-Analyse
|
||||||
|
|
||||||
|
### Scraping API – 3 Endpoints
|
||||||
|
|
||||||
|
#### 1. Recent Pastes (Firehose)
|
||||||
|
```
|
||||||
|
GET https://scrape.pastebin.com/api_scraping.php?limit=250
|
||||||
|
```
|
||||||
|
- Liefert die **neuesten öffentlichen** Pastes (max 250 pro Request)
|
||||||
|
- Response: JSON-Array mit `scrape_url`, `full_url`, `date`, `key`, `size`, `expire`, `title`, `syntax`, `user`
|
||||||
|
- Caching: ~2 Minuten Verzögerung
|
||||||
|
- **Kein Suchfilter** – man bekommt ALLE neuen Pastes und muss lokal filtern
|
||||||
|
|
||||||
|
#### 2. Paste Content
|
||||||
|
```
|
||||||
|
GET https://scrape.pastebin.com/api_scrape_item.php?i={paste_key}
|
||||||
|
```
|
||||||
|
- Liefert den Rohtext eines Pastes
|
||||||
|
- Nur über die Scraping-API abrufbar (nicht /raw/)
|
||||||
|
|
||||||
|
#### 3. Paste Metadata
|
||||||
|
```
|
||||||
|
GET https://scrape.pastebin.com/api_scrape_item_meta.php?i={paste_key}
|
||||||
|
```
|
||||||
|
- Liefert erweiterte Metadaten (Hits, Syntax, etc.)
|
||||||
|
- Für unseren Use Case nicht zwingend nötig
|
||||||
|
|
||||||
|
### Rate Limits
|
||||||
|
- Kein hartes Rate Limit dokumentiert
|
||||||
|
- Empfehlung: Max 1 Request/Sekunde
|
||||||
|
- IP-basiert – nur gewhitelistete IP hat Zugriff
|
||||||
|
- Bei Missbrauch → IP-Block
|
||||||
|
|
||||||
|
## Implementierungsplan
|
||||||
|
|
||||||
|
### Phase 1: Config – Server Settings
|
||||||
|
|
||||||
|
Neues Setting in `ServerSettingsService.cs`:
|
||||||
|
```csharp
|
||||||
|
("PastebinScrapeEnabled", "toggle", "false", "Scanner", "Pastebin Scraping", "Pastebin Scraping API aktivieren (erfordert PRO Account + IP Whitelist)"),
|
||||||
|
```
|
||||||
|
|
||||||
|
Kein API-Key nötig – die Authentifizierung läuft über IP-Whitelist.
|
||||||
|
|
||||||
|
### Phase 2: Neue Scan-Methode `ScanPastebin`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private async Task<List<(string Key, string Url)>> ScanPastebin(CancellationToken ct)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ablauf:**
|
||||||
|
1. Neueste Pastes abrufen: `GET api_scraping.php?limit=250`
|
||||||
|
2. Für jeden Paste:
|
||||||
|
- Prüfen ob `paste_key` bereits gescannt (lokales HashSet + DB-Check)
|
||||||
|
- Paste-Content abrufen: `GET api_scrape_item.php?i={key}`
|
||||||
|
- Content mit `KeyPatterns.Compiled` Regex scannen
|
||||||
|
- Bei Treffer: Key + `full_url` sammeln
|
||||||
|
3. Gescannte Paste-Keys merken (um Re-Scans zu vermeiden)
|
||||||
|
4. Zwischen Content-Requests: 1s Pause
|
||||||
|
|
||||||
|
### Phase 3: Paste-Key Tracking (Deduplication)
|
||||||
|
|
||||||
|
Da die Firehose dieselben Pastes mehrfach liefern kann, brauchen wir ein Tracking:
|
||||||
|
|
||||||
|
**Option A: In-Memory HashSet** (einfach, geht bei Neustart verloren)
|
||||||
|
```csharp
|
||||||
|
private readonly HashSet<string> _scannedPasteKeys = new();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: DB-Tabelle** (persistent, verhindert Re-Scans nach Neustart)
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS pastebin_scanned (
|
||||||
|
paste_key TEXT PRIMARY KEY,
|
||||||
|
scanned_at TEXT NOT NULL
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Empfehlung:** Option A (In-Memory) mit TTL – die Pastes laufen sowieso ab.
|
||||||
|
Das HashSet wird beim Scan-Start befüllt und nach 24h geleert.
|
||||||
|
|
||||||
|
### Phase 4: Integration in RunScan
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// In RunScan(), nach HuggingFace:
|
||||||
|
if (mode != "quick")
|
||||||
|
{
|
||||||
|
var pastebinEnabled = _settings.GetBool("PastebinScrapeEnabled", false);
|
||||||
|
if (pastebinEnabled)
|
||||||
|
{
|
||||||
|
SetProgress("Pastebin Scraping", "Starte…");
|
||||||
|
var pbKeys = await ScanPastebin(ct);
|
||||||
|
allFoundKeys.AddRange(pbKeys.Select(k => (k.Key, k.Url, "pastebin")));
|
||||||
|
AddLog("scan", "Pastebin-Scan: " + pbKeys.Count + " Key(s) gefunden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Mode `quick`:** Kein Pastebin-Scan (zu viele Requests)
|
||||||
|
- **Mode `web`:** Pastebin-Scan aktiv (Web-Quelle)
|
||||||
|
- **Mode `full`:** Pastebin-Scan aktiv
|
||||||
|
|
||||||
|
### Phase 5: Intelligentes Filtern
|
||||||
|
|
||||||
|
Nicht jeder Paste muss heruntergeladen werden. Vor-Filterung über Metadaten:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Relevante Syntax-Typen (höhere Wahrscheinlichkeit für Keys)
|
||||||
|
private static readonly HashSet<string> RelevantSyntax = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
"", "text", "python", "javascript", "bash", "json", "yaml", "ini",
|
||||||
|
"php", "ruby", "java", "csharp", "go", "rust", "typescript",
|
||||||
|
"powershell", "shell", "markdown", "xml", "toml", "properties"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Relevante Titel-Keywords
|
||||||
|
private static readonly string[] TitleKeywords =
|
||||||
|
{
|
||||||
|
"key", "api", "token", "secret", "credential", "config", "env",
|
||||||
|
"anthropic", "openai", "aws", "claude", "password", "leak"
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Strategie:**
|
||||||
|
1. **Titel-Match:** Wenn Titel ein Keyword enthält → immer scannen
|
||||||
|
2. **Syntax-Match:** Wenn Syntax relevant (Code/Text) → scannen
|
||||||
|
3. **Größen-Filter:** Nur Pastes < 500 KB (keine riesigen Dumps)
|
||||||
|
4. **Syntax-Ausschluss:** Binär-Formate, Bilder, etc. → überspringen
|
||||||
|
|
||||||
|
### Phase 6: Scan-Frequenz
|
||||||
|
|
||||||
|
Da der tägliche Full-Scan nur einmal läuft, würden wir zwischen zwei Scans viele Pastes verpassen (Pastebin hat ~10.000+ neue Pastes/Tag).
|
||||||
|
|
||||||
|
**Zwei Optionen:**
|
||||||
|
|
||||||
|
**A. Einmaliger Snapshot pro Full-Scan (einfach)**
|
||||||
|
- Beim Full-Scan einmal 250 neueste Pastes abrufen + scannen
|
||||||
|
- Verpfasst die meisten Pastes, aber fängt zumindest aktuelle ab
|
||||||
|
- Empfohlen für den Anfang
|
||||||
|
|
||||||
|
**B. Kontinuierlicher Hintergrund-Scanner (komplex)**
|
||||||
|
- Eigener BackgroundService der alle 2-3 Minuten pollt
|
||||||
|
- Fängt nahezu alle neuen Pastes ab
|
||||||
|
- Deutlich mehr Requests (~720/Tag × 250 = 180.000 Pastes/Tag)
|
||||||
|
- Kann später hinzugefügt werden
|
||||||
|
|
||||||
|
**Empfehlung:** Option A für Phase 1, Option B als optionales Follow-up.
|
||||||
|
|
||||||
|
## Scan-Volumina (geschätzt)
|
||||||
|
|
||||||
|
| Szenario | Pastes gescannt | API Requests | Dauer |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Einmal 250 Pastes | 250 | ~251 (1 Liste + 250 Content) | ~5 Min |
|
||||||
|
| Einmal 250, nur gefiltert | ~100 | ~101 | ~2 Min |
|
||||||
|
| Kontinuierlich (alle 3 Min) | ~180.000/Tag | ~720 Listen + ~180.000 Content | Dauerhaft |
|
||||||
|
|
||||||
|
## Aufwandsschätzung
|
||||||
|
- **Implementierung:** ~200 Zeilen Code
|
||||||
|
- **Komplexität:** Niedrig-Mittel (einfache API, aber Dedup + Filterung)
|
||||||
|
- **Erwarteter Ertrag:** 🔥 Hoch (Pastebin = klassische Secret-Dump-Plattform)
|
||||||
|
- **Kosten:** $29 einmalig für Pastebin PRO
|
||||||
|
|
||||||
|
## Risiken
|
||||||
|
- IP-Whitelist → funktioniert nur von einer festen IP (Server)
|
||||||
|
- Nur **öffentliche** Pastes sind sichtbar (unlisted/private nicht)
|
||||||
|
- Pastes können schnell gelöscht werden → zeitnahes Scraping wichtig
|
||||||
|
- Hohe Paste-Menge → viele Keys sind False Positives (Test-Keys, bereits revoked)
|
||||||
|
- Bei zu aggressivem Polling → IP-Block möglich
|
||||||
|
- Pastebin kann API-Bedingungen jederzeit ändern
|
||||||
@@ -66,7 +66,7 @@ public class GitHubEventScannerService : BackgroundService
|
|||||||
lock (_lock) return _logEntries.ToList();
|
lock (_lock) return _logEntries.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddLog(string type, string message, string details = null)
|
private void AddLog(string type, string message, string details = null, bool persist = true)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
@@ -80,6 +80,8 @@ public class GitHubEventScannerService : BackgroundService
|
|||||||
_logEntries.Dequeue();
|
_logEntries.Dequeue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!persist) return;
|
||||||
|
|
||||||
// Persistieren in DB (fire-and-forget)
|
// Persistieren in DB (fire-and-forget)
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@@ -163,7 +165,7 @@ public class GitHubEventScannerService : BackgroundService
|
|||||||
var events = await FetchPublicEvents(client, ct);
|
var events = await FetchPublicEvents(client, ct);
|
||||||
if (events.Count == 0)
|
if (events.Count == 0)
|
||||||
{
|
{
|
||||||
AddLog("poll", "0 PushEvents empfangen");
|
AddLog("poll", "0 PushEvents empfangen", persist: false);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +180,7 @@ public class GitHubEventScannerService : BackgroundService
|
|||||||
|
|
||||||
if (pushEvents.Count == 0)
|
if (pushEvents.Count == 0)
|
||||||
{
|
{
|
||||||
AddLog("poll", events.Count + " Events, 0 neue PushEvents");
|
AddLog("poll", events.Count + " Events, 0 neue PushEvents", persist: false);
|
||||||
return events.Count;
|
return events.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +189,7 @@ public class GitHubEventScannerService : BackgroundService
|
|||||||
? pushEvents.Take(MaxCommitsPerPoll).ToList()
|
? pushEvents.Take(MaxCommitsPerPoll).ToList()
|
||||||
: pushEvents;
|
: pushEvents;
|
||||||
|
|
||||||
AddLog("poll", events.Count + " Events, " + pushEvents.Count + " neue PushEvents → " + toScan.Count + " scannen");
|
AddLog("poll", events.Count + " Events, " + pushEvents.Count + " neue PushEvents → " + toScan.Count + " scannen", persist: false);
|
||||||
|
|
||||||
// Head-Commit-Diffs scannen
|
// Head-Commit-Diffs scannen
|
||||||
var newKeyIds = new List<string>();
|
var newKeyIds = new List<string>();
|
||||||
@@ -259,7 +261,7 @@ public class GitHubEventScannerService : BackgroundService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AddLog("poll", scannedCount + " Diffs gescannt, " + newKeyIds.Count + " neue Keys");
|
AddLog("poll", scannedCount + " Diffs gescannt, " + newKeyIds.Count + " neue Keys", persist: newKeyIds.Count > 0);
|
||||||
|
|
||||||
if (newKeyIds.Count > 0)
|
if (newKeyIds.Count > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user