Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 61fad87f23 | |||
| 556e698151 |
Generated
+1
-1
@@ -4,7 +4,7 @@ version = 3
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aegisaur"
|
name = "aegisaur"
|
||||||
version = "2.0.0"
|
version = "2.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "aegisaur"
|
name = "aegisaur"
|
||||||
version = "2.1.0"
|
version = "2.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Quasi & Thuumate 👻"]
|
authors = ["Quasi & Thuumate 👻"]
|
||||||
description = "Trust-Scoring + IOC-Scanner für Arch Linux AUR-Pakete"
|
description = "Trust-Scoring + IOC-Scanner für Arch Linux AUR-Pakete"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# AegisAUR v2.0.0 👻
|
# AegisAUR v2.1.0 👻
|
||||||
|
|
||||||
**Vollständiger AUR Security Scanner für Arch Linux**
|
**Vollständiger AUR Security Scanner für Arch Linux**
|
||||||
|
|
||||||
@@ -8,14 +8,24 @@ Schutz gegen Supply-Chain-Angriffe, Malware und kompromittierte Pakete im AUR.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Download (Tarball — Git-Clone hat bekannte Probleme mit großen Dateien)
|
# Download (Tarball — Git-Clone hat bekannte Probleme mit großen Dateien)
|
||||||
wget https://gitea.die-heimatlosen.eu/arch_agent/aegisaur/archive/main.tar.gz
|
wget https://gitea.die-heimatlosen.eu/arch_agent/aegisaur/archive/v2.1.0.tar.gz
|
||||||
tar xzf main.tar.gz && cd aegisaur
|
tar xzf v2.1.0.tar.gz && cd aegisaur
|
||||||
cargo build --release
|
cargo build --release
|
||||||
sudo cp target/release/aegisaur /usr/local/bin/
|
sudo cp target/release/aegisaur /usr/local/bin/
|
||||||
sudo aegisaur install-hook
|
sudo aegisaur install-hook
|
||||||
|
sudo aegisaur install-timer
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🎯 Features v2.0.0
|
## 🎯 Features v2.1.0
|
||||||
|
|
||||||
|
### Systemd Timer — Automatische Scans
|
||||||
|
```bash
|
||||||
|
sudo aegisaur install-timer # Tägliche Scans aktivieren
|
||||||
|
sudo aegisaur remove-timer # Timer deaktivieren
|
||||||
|
```
|
||||||
|
- **Zeit:** Täglich um 03:00 Uhr (mit 1h RandomizedDelay)
|
||||||
|
- **Logs:** `journalctl -u aegisaur-scan.service`
|
||||||
|
- **Status:** `systemctl status aegisaur-scan.timer`
|
||||||
|
|
||||||
### Multi-Source Threat Intelligence
|
### Multi-Source Threat Intelligence
|
||||||
- **HedgeDoc (Live)**: Sofort aktuell
|
- **HedgeDoc (Live)**: Sofort aktuell
|
||||||
@@ -23,18 +33,13 @@ sudo aegisaur install-hook
|
|||||||
- **Arch Security**: Offizielle Arch Linux Advisories
|
- **Arch Security**: Offizielle Arch Linux Advisories
|
||||||
- **Atomic Arch Gist**: Community-Listen
|
- **Atomic Arch Gist**: Community-Listen
|
||||||
|
|
||||||
### Erweiterte Erkennung
|
|
||||||
- **12 Threat-Typen**: Rootkit, Cryptominer, Backdoor, Ransomware, Infostealer, etc.
|
|
||||||
- **AUR-Spezifisch**: Typosquatting, Orphan-Takeover, Maintainer-Kompromittierung
|
|
||||||
- **CVE-Tracking**: Verknüpfung mit bekannten CVEs
|
|
||||||
- **Advisory-URLs**: Direkte Links zu Security-Advisories
|
|
||||||
|
|
||||||
### Smartes Scoring
|
### Smartes Scoring
|
||||||
- **AUR vs. Offizielles Repo**: Keine False-Positives für Repo-Pakete
|
- **AUR vs. Offizielles Repo**: Keine False-Positives für Repo-Pakete
|
||||||
- **Trust-Scoring**: 0-100 mit 12 Heuristiken
|
- **Trust-Scoring**: 0-100 mit 12 Heuristiken
|
||||||
|
- **AUR-Info Score**: Votes, Popularität, Maintainer-Status
|
||||||
- **Cache**: 5-Minuten-TTL für maximale Aktualität
|
- **Cache**: 5-Minuten-TTL für maximale Aktualität
|
||||||
|
|
||||||
### Cache-Status (v2.0.1+)
|
### Cache-Status
|
||||||
```bash
|
```bash
|
||||||
aegisaur check-ioc
|
aegisaur check-ioc
|
||||||
🛡️ Prüfe IOC-Listen: all
|
🛡️ Prüfe IOC-Listen: all
|
||||||
@@ -43,22 +48,16 @@ aegisaur check-ioc
|
|||||||
🔴 gtkimageview - MaliciousBuildScript
|
🔴 gtkimageview - MaliciousBuildScript
|
||||||
```
|
```
|
||||||
|
|
||||||
Cache-Miss zeigt Live-Reload:
|
|
||||||
```
|
|
||||||
⏰ Cache veraltet (6m 15s alt) — Live-Reload...
|
|
||||||
🟢 1931 IOCs von HedgeDoc (LIVE)
|
|
||||||
🟢 0 IOCs von Arch Security
|
|
||||||
📊 Gesamt: 1931 IOCs aus Quellen: ["hedgedoc_live", "arch_security"]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 Befehle
|
## 🚀 Befehle
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
aegisaur scan <paket> # Einzelnes Paket (AUR-spezifisch)
|
aegisaur scan <paket> # Einzelnes Paket (AUR-spezifisch)
|
||||||
aegisaur scan-all # Alle AUR-Pakete
|
aegisaur scan-all # Alle AUR-Pakete (~30s für 125 Pakete)
|
||||||
aegisaur check-ioc # IOC-Listen prüfen
|
aegisaur check-ioc # IOC-Listen prüfen
|
||||||
sudo aegisaur install-hook # ALPM-Hook installieren
|
sudo aegisaur install-hook # ALPM-Hook installieren
|
||||||
sudo aegisaur remove-hook # ALPM-Hook entfernen
|
sudo aegisaur remove-hook # ALPM-Hook entfernen
|
||||||
|
sudo aegisaur install-timer # Systemd-Timer installieren
|
||||||
|
sudo aegisaur remove-timer # Systemd-Timer entfernen
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔧 Hook-Verhalten
|
## 🔧 Hook-Verhalten
|
||||||
@@ -82,6 +81,13 @@ Siehe TODO.md für geplante Features.
|
|||||||
|
|
||||||
## 📜 Changelog
|
## 📜 Changelog
|
||||||
|
|
||||||
|
### v2.1.0 (2026-06-15)
|
||||||
|
- Systemd Timer für tägliche automatische Scans
|
||||||
|
- Performance: ~7x schneller durch gecachte AUR-Prüfung
|
||||||
|
- Cache-Status-Anzeige mit Alter und TTL
|
||||||
|
- AUR-Score aus Paket-Info (Votes, Popularität, Maintainer)
|
||||||
|
- PKGBUILD-Analyse nur bei verbose oder IOC-Match
|
||||||
|
|
||||||
### v2.0.0 (2026-06-15)
|
### v2.0.0 (2026-06-15)
|
||||||
- Vollständiger Rewrite mit Multi-Source IOCs
|
- Vollständiger Rewrite mit Multi-Source IOCs
|
||||||
- CVE und Advisory-URL Support
|
- CVE und Advisory-URL Support
|
||||||
|
|||||||
+58
-8
@@ -262,12 +262,15 @@ impl PackageScanner {
|
|||||||
let foreign_packages = self.get_foreign_packages().await?;
|
let foreign_packages = self.get_foreign_packages().await?;
|
||||||
let mut results = Vec::with_capacity(foreign_packages.len());
|
let mut results = Vec::with_capacity(foreign_packages.len());
|
||||||
|
|
||||||
// SEQUENZIELLE SCANS mit gecachten IOCs
|
// SEQUENZIELLE SCANS mit gecachten IOCs und Rate-Limiting
|
||||||
// (Parallele Scans würden Rate-Limits bei AUR RPC triggern)
|
// (Parallele Scans würden Rate-Limits bei AUR RPC triggern)
|
||||||
let iocs = self.ioc_fetcher.get_cached_iocs().await?;
|
let iocs = self.ioc_fetcher.get_cached_iocs().await?;
|
||||||
let ioc_count = iocs.len();
|
let ioc_count = iocs.len();
|
||||||
info!("{} IOCs geladen, starte Scan von {} Paketen...", ioc_count, foreign_packages.len());
|
info!("{} IOCs geladen, starte Scan von {} Paketen...", ioc_count, foreign_packages.len());
|
||||||
|
|
||||||
|
let mut consecutive_errors = 0;
|
||||||
|
let max_consecutive_errors = 5;
|
||||||
|
|
||||||
for (idx, (pkg, _)) in foreign_packages.iter().enumerate() {
|
for (idx, (pkg, _)) in foreign_packages.iter().enumerate() {
|
||||||
if idx % 10 == 0 {
|
if idx % 10 == 0 {
|
||||||
println!(" [{}/{}] Scanne {}...", idx + 1, foreign_packages.len(), pkg);
|
println!(" [{}/{}] Scanne {}...", idx + 1, foreign_packages.len(), pkg);
|
||||||
@@ -276,9 +279,18 @@ impl PackageScanner {
|
|||||||
match self.scan_package_with_iocs(pkg, verbose, &iocs).await {
|
match self.scan_package_with_iocs(pkg, verbose, &iocs).await {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
results.push(result);
|
results.push(result);
|
||||||
|
consecutive_errors = 0; // Reset bei Erfolg
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Fehler beim Scannen von {}: {}", pkg, e);
|
warn!("Fehler beim Scannen von {}: {}", pkg, e);
|
||||||
|
consecutive_errors += 1;
|
||||||
|
|
||||||
|
if consecutive_errors >= max_consecutive_errors {
|
||||||
|
warn!("Zu viele aufeinanderfolgende Fehler — Rate-Limiting vermutet. Pause...");
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
||||||
|
consecutive_errors = 0;
|
||||||
|
}
|
||||||
|
|
||||||
results.push(ScanResult {
|
results.push(ScanResult {
|
||||||
package: pkg.clone(),
|
package: pkg.clone(),
|
||||||
version: String::new(),
|
version: String::new(),
|
||||||
@@ -292,6 +304,11 @@ impl PackageScanner {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kleines Delay zwischen Paketen um Rate-Limiting zu vermeiden
|
||||||
|
if idx % 5 == 4 {
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
results.sort_by(|a, b| a.score.cmp(&b.score));
|
results.sort_by(|a, b| a.score.cmp(&b.score));
|
||||||
@@ -395,18 +412,51 @@ impl PackageScanner {
|
|||||||
package
|
package
|
||||||
);
|
);
|
||||||
|
|
||||||
let response = reqwest::get(&url).await?;
|
let mut retries = 0;
|
||||||
if !response.status().is_success() {
|
let max_retries = 3;
|
||||||
return Ok(None);
|
let mut last_error = None;
|
||||||
}
|
|
||||||
|
|
||||||
let rpc_response: AurRpcResponse = response.json().await?;
|
|
||||||
|
|
||||||
|
while retries < max_retries {
|
||||||
|
match reqwest::get(&url).await {
|
||||||
|
Ok(response) => {
|
||||||
|
if response.status().is_success() {
|
||||||
|
match response.json::<AurRpcResponse>().await {
|
||||||
|
Ok(rpc_response) => {
|
||||||
if rpc_response.resultcount == 0 || rpc_response.results.is_empty() {
|
if rpc_response.resultcount == 0 || rpc_response.results.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
return Ok(Some(rpc_response.results[0].clone()));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
last_error = Some(format!("JSON parse error: {}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if response.status().as_u16() == 429 {
|
||||||
|
// Rate limited — warte und retry
|
||||||
|
let delay_ms = 1000 * (retries + 1);
|
||||||
|
warn!("Rate limited für {}, warte {}ms...", package, delay_ms);
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(delay_ms)).await;
|
||||||
|
retries += 1;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
last_error = Some(format!("HTTP {}", response.status()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
last_error = Some(format!("Request failed: {}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Some(rpc_response.results[0].clone()))
|
retries += 1;
|
||||||
|
if retries < max_retries {
|
||||||
|
let delay_ms = 500 * retries;
|
||||||
|
debug!("Retry {} für {} nach {}ms", retries, package, delay_ms);
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(delay_ms)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
warn!("AUR RPC für {} fehlgeschlagen nach {} retries: {}", package, max_retries, last_error.unwrap_or_default());
|
||||||
|
Ok(None) // Nicht hard-fail, sondern None zurückgeben
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_pkgbuild(
|
async fn fetch_pkgbuild(
|
||||||
|
|||||||
Reference in New Issue
Block a user