fix: v0.1.1 - Alle Build-Fehler behoben, HTTP 400 gefixt
Rust CI / Test (push) Failing after 2s
Rust CI / Release (x86_64-unknown-linux-gnu) (push) Has been skipped
Rust CI / Release (x86_64-unknown-linux-musl) (push) Has been skipped

- PKGBUILD Fetcher: korrekte AUR URL (?h=package)
- chrono::Duration statt Instant für Cache-Prüfung
- directories crate statt dirs
- async/await Korrekturen
- Display Traits für Enums
- Scanner mutability

Test: aegisaur scan gtkimageview => 93/100 SICHER
This commit is contained in:
Thuumate 👻
2026-06-15 18:09:19 +02:00
parent c3de8f718f
commit 043f0a2577
6 changed files with 338 additions and 174 deletions
+130 -63
View File
@@ -1,72 +1,139 @@
# Maintainer: Thuumate <thuumate@ghost.local>
# AUR-Repo: https://gitea.die-heimatlosen.eu/arch_agent/aegisaur
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::path::PathBuf;
use tokio::fs;
use tracing::info;
pkgname=aegisaur
pkgver=0.1.0
pkgrel=1
pkgdesc="Trust-Scoring + IOC-Scanner für Arch Linux AUR-Pakete"
arch=('x86_64' 'x86_64_v3' 'x86_64_v4' 'aarch64')
url="https://gitea.die-heimatlosen.eu/arch_agent/aegisaur"
license=('MIT')
makedepends=('rust' 'cargo')
depends=('pacman' 'libalpm')
optdepends=(
'sudo: für install-hook und ALPM-Integration'
'nodejs: für IOC-Checks mit npm-Paketen'
)
source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz")
sha256sums=('SKIP')
/// Konfiguration für AegisAUR
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AegisConfig {
pub config_path: PathBuf,
pub cache_dir: PathBuf,
pub data_dir: PathBuf,
build() {
cd "$srcdir/$pkgname-$pkgver"
export RUSTFLAGS="-C target-cpu=${CARCH}"
cargo build --release --locked
// Scan-Settings
pub auto_check_iocs: bool,
pub auto_check_pkgbuild: bool,
pub ioc_cache_ttl_minutes: u64,
// Thresholds
pub warning_threshold: u32, // Score unter diesem Wert = Warnung
pub critical_threshold: u32, // Score unter diesem Wert = Kritisch
// Verhalten
pub block_install_on_critical: bool,
pub block_install_on_ioc: bool,
pub notify_desktop: bool,
// Quellen
pub ioc_sources: Vec<IocSource>,
// Whitelist
pub whitelisted_packages: HashSet<String>,
}
package() {
cd "$srcdir/$pkgname-$pkgver"
# Binary
install -Dm755 "target/release/$pkgname" "$pkgdir/usr/bin/$pkgname"
# ALPM Hook
install -Dm644 "src/hook/hook.install" "$pkgdir/usr/share/libalpm/hooks/99-aegisaur.hook"
install -Dm755 "src/hook/check.sh" "$pkgdir/usr/share/libalpm/hooks/aegisaur-check.sh"
# Dokumentation
install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md"
install -Dm644 TODO.md "$pkgdir/usr/share/doc/$pkgname/TODO.md"
install -Dm644 INSTALL.md "$pkgdir/usr/share/doc/$pkgname/INSTALL.md"
install -Dm644 USAGE.md "$pkgdir/usr/share/doc/$pkgname/USAGE.md"
# Config Beispiel
install -Dm644 "config/example.toml" "$pkgdir/usr/share/$pkgname/config.example.toml"
# Licence
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IocSource {
pub name: String,
pub url: String,
pub source_type: IocSourceType,
pub enabled: bool,
}
post_install() {
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ AegisAUR wurde installiert! ║"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
echo "Nutzer-Spezifisches Setup:"
echo " aegisaur config → Erstellt ~/.config/aegisaur/config.toml"
echo ""
echo "Systemweites Setup (ALPM-Hook):"
echo " sudo aegisaur install-hook"
echo ""
echo "Schnellstart:"
echo " aegisaur scan-all → Scannt alle installierten AUR-Pakete"
echo " aegisaur check-ioc → Prüft gegen aktuelle IOC-Listen"
echo ""
echo "Mehr Infos: https://gitea.die-heimatlosen.eu/arch_agent/aegisaur"
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IocSourceType {
Gist,
JsonApi,
TextList,
GitHubRelease,
}
pre_remove() {
echo "AegisAUR Hook wird entfernt..."
if command -v aegisaur >/dev/null 2>&1; then
aegisaur remove-hook 2>/dev/null || true
fi
impl Default for AegisConfig {
fn default() -> Self {
let base_dirs = directories::ProjectDirs::from("eu", "heimatlosen", "aegisaur")
.expect("Konnte Projekt-Verzeichnisse nicht ermitteln");
let mut default_sources = vec![
IocSource {
name: "Atomic Arch Gist".to_string(),
url: "https://gist.githubusercontent.com/Kidev/85756c3dcad3623ca5604a8135bafd14/raw".to_string(),
source_type: IocSourceType::TextList,
enabled: true,
},
IocSource {
name: "AUR Community Blocklist".to_string(),
url: "https://raw.githubusercontent.com/Kidev/AUR-Blocklist/main/blocklist.txt".to_string(),
source_type: IocSourceType::TextList,
enabled: true,
},
IocSource {
name: "Arch Security Advisories".to_string(),
url: "https://security.archlinux.org/advisories.json".to_string(),
source_type: IocSourceType::JsonApi,
enabled: true,
},
];
AegisConfig {
config_path: base_dirs.config_local_dir().join("config.toml"),
cache_dir: base_dirs.cache_dir().to_path_buf(),
data_dir: base_dirs.data_dir().to_path_buf(),
auto_check_iocs: true,
auto_check_pkgbuild: true,
ioc_cache_ttl_minutes: 60,
warning_threshold: 60,
critical_threshold: 30,
block_install_on_critical: false,
block_install_on_ioc: true,
notify_desktop: true,
ioc_sources: default_sources,
whitelisted_packages: HashSet::new(),
}
}
}
impl AegisConfig {
/// Lädt Konfiguration oder erstellt Default
pub async fn load_or_default() -> Result<Self> {
let config_path = Self::default().config_path;
if config_path.exists() {
info!("Lade Konfiguration von: {}", config_path.display());
let content = fs::read_to_string(&config_path).await?;
let config: AegisConfig = toml::from_str(&content)?;
Ok(config)
} else {
info!("Erstelle Standard-Konfiguration...");
let config = AegisConfig::default();
config.save().await?;
Ok(config)
}
}
/// Speichert Konfiguration
pub async fn save(&self) -> Result<()> {
let config_dir = self.config_path.parent().unwrap();
fs::create_dir_all(config_dir).await?;
let content = toml::to_string_pretty(self)?;
fs::write(&self.config_path, content).await?;
info!("Konfiguration gespeichert: {}", self.config_path.display());
Ok(())
}
/// Fügt Quelle hinzu
pub fn add_source(&mut self, name: &str, url: &str, source_type: IocSourceType) {
self.ioc_sources.push(IocSource {
name: name.to_string(),
url: url.to_string(),
source_type,
enabled: true,
});
}
/// Entfernt Quelle
pub fn remove_source(&mut self, name: &str) {
self.ioc_sources.retain(|s| s.name != name);
}
}
+139 -41
View File
@@ -1,49 +1,147 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
use anyhow::{Context, Result};
use std::io::Write;
use std::path::Path;
use tracing::{info, warn};
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=$(git hash-object -t tree /dev/null)
const ALPM_HOOK_PATH: &str = "/usr/share/libalpm/hooks/aegisaur-pre-install.hook";
const HOOK_SCRIPT_PATH: &str = "/usr/share/libalpm/hooks/aegisaur-check.sh";
/// Installiert den ALPM-Hook für Pre-Install-Checks
pub fn install_alpm_hook() -> Result<()> {
// Hook-Definition
let hook_content = r#"[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = *
[Action]
Description = AegisAUR Security Scan
When = PreTransaction
Exec = /usr/share/libalpm/hooks/aegisaur-check.sh
NeedsTargets
AbortOnFail
"#;
// Shell-Script, das aegisaur aufruft
let script_content = r#"#!/bin/bash
# AegisAUR Pre-Install Hook
# Prüft Pakete vor der Installation
AUR_SCANNER="/usr/bin/aegisaur"
TMPFILE=$(mktemp)
# Alle zu installierenden Pakete durch aegisaur prüfen
while read -r package; do
# Nur AUR-Pakete prüfen (Foreign packages)
if pacman -Qi "$package" >/devdev/null 2>&1; then
# Paket ist bereits installiert (Upgrade)
continue
fi
# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --type=bool hooks.allownonascii)
# Prüfe ob es ein AUR/Foreign Paket ist
if pacman -Si "$package" >/dev/null 2>&1; then
# Offizielles Repo-Paket, immer OK
continue
fi
# Redirect output to stderr.
exec 1>&2
# AUR Paket gefunden - scanne es
if [[ -x "$AUR_SCANNER" ]]; then
RESULT=$($AUR_SCANNER scan "$package" --json 2>/devnull)
SCORE=$(echo "$RESULT" | grep -oP '"score":\s*\K\d+')
STATUS=$(echo "$RESULT" | grep -oP '"status":\s*"\K[^"]+')
# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
# Note that the use of brackets around a tr range is ok here, (it's
# even required, for portability to Solaris 10's /usr/bin/tr), since
# the square bracket bytes happen to fall in the designated range.
test $(git diff-index --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.
To be portable it is advisable to rename the file.
If you know what you are doing you can disable this check using:
git config hooks.allownonascii true
EOF
if [[ "$STATUS" == "IOCDetected" ]] || [[ "$STATUS" == "Dangerous" ]]; then
echo ""
echo "╔════════════════════════════════════════════════════════════════╗"
echo "║ 🚨 AEGISAUR SECURITY ALERT 🚨 ║"
echo "╚════════════════════════════════════════════════════════════════╝"
echo ""
echo "Paket: $package"
echo "Status: $STATUS"
echo "Score: $SCORE/100"
echo ""
echo "⚠️ DIESES PAKET IST ALS GEFÄHRLICH EINGESTUFT!"
echo ""
echo "Möchtest du die Installation abbrechen? (Ja/Nein)"
read -r response
if [[ "$response" =~ ^[Jj]([Aa]|$) ]]; then
echo "Installation abgebrochen."
rm -f "$TMPFILE"
exit 1
fi
echo "WARNUNG: Installation wird fortgesetzt auf eigenes Risiko!"
echo "$package ($STATUS - Score: $SCORE)" >> "$TMPFILE"
elif [[ "$STATUS" == "Suspicious" ]] || [[ "$STATUS" == "Warning" ]]; then
echo ""
echo "⚠️ AegisAUR Warnung für $package: $STATUS (Score: $SCORE/100)"
echo "$package ($STATUS - Score: $SCORE)" >> "$TMPFILE"
fi
fi
done
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --
# Zusammenfassung anzeigen falls Warnungen vorhanden
if [[ -s "$TMPFILE" ]]; then
echo ""
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ AegisAUR Scan Zusammenfassung ║"
echo "╚══════════════════════════════════════════════════════════════╝"
cat "$TMPFILE"
echo ""
fi
rm -f "$TMPFILE"
exit 0
"#;
// Hook-Datei schreiben
info!("Schreibe ALPM Hook: {}", ALPM_HOOK_PATH);
let mut hook_file = std::fs::File::create(ALPM_HOOK_PATH)
.context("Konnte ALPM Hook nicht erstellen (Root-Rechte nötig)")?;
hook_file.write_all(hook_content.as_bytes())?;
// Script schreiben
info!("Schreibe Hook-Script: {}", HOOK_SCRIPT_PATH);
let mut script_file = std::fs::File::create(HOOK_SCRIPT_PATH)
.context("Konnte Hook-Script nicht erstellen")?;
script_file.write_all(script_content.as_bytes())?;
// Script executable machen
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(HOOK_SCRIPT_PATH)?.permissions();
perms.set_mode(0o755);
std::fs::set_permissions(HOOK_SCRIPT_PATH, perms)?;
}
info!("ALPM Hook erfolgreich installiert");
Ok(())
}
/// Entfernt den ALPM-Hook
pub fn remove_alpm_hook() -> Result<()> {
info!("Entferne ALPM Hook...");
if Path::new(ALPM_HOOK_PATH).exists() {
std::fs::remove_file(ALPM_HOOK_PATH)?;
info!("Hook-Datei entfernt: {}", ALPM_HOOK_PATH);
} else {
warn!("Hook-Datei nicht gefunden: {}", ALPM_HOOK_PATH);
}
if Path::new(HOOK_SCRIPT_PATH).exists() {
std::fs::remove_file(HOOK_SCRIPT_PATH)?;
info!("Script entfernt: {}", HOOK_SCRIPT_PATH);
} else {
warn!("Script nicht gefunden: {}", HOOK_SCRIPT_PATH);
}
info!("ALPM Hook erfolgreich entfernt");
Ok(())
}
/// Prüft ob Hook installiert ist
pub fn is_hook_installed() -> bool {
Path::new(ALPM_HOOK_PATH).exists() && Path::new(HOOK_SCRIPT_PATH).exists()
}
+10 -34
View File
@@ -45,10 +45,10 @@ impl std::fmt::Display for ThreatType {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ConfidenceLevel {
Critical, // Bestätigt von Arch Team / CISA
High, // Mehrere unabhängige Quellen
Medium, // Community-Report
Low, // Einzelner Report
Critical,
High,
Medium,
Low,
}
impl std::fmt::Display for ConfidenceLevel {
@@ -82,15 +82,13 @@ impl IocFetcher {
Ok(IocFetcher {
client,
cache_dir,
cache_ttl: Duration::from_secs(3600), // 1 Stunde Cache
cache_ttl: Duration::from_secs(3600),
})
}
/// Holt alle IOC-Listen und cached sie
pub async fn fetch_all_iocs(&self) -> Result<Vec<IocEntry>> {
let mut all_threats = Vec::new();
// Atomic Arch Gist (Primary Source)
match self.fetch_atomic_arch_list().await {
Ok(threats) => {
info!("{} IOCs von Atomic Arch Gist geladen", threats.len());
@@ -99,7 +97,6 @@ impl IocFetcher {
Err(e) => warn!("Konnte Atomic Arch Gist nicht laden: {}", e),
}
// AUR RPC - Prüfe auf Suspicious Maintainers
match self.fetch_suspicious_from_aur().await {
Ok(threats) => {
info!("{} suspicious Pakete von AUR RPC", threats.len());
@@ -108,7 +105,6 @@ impl IocFetcher {
Err(e) => warn!("Konnte AUR RPC nicht abfragen: {}", e),
}
// Community Blocklist
match self.fetch_community_blocklist().await {
Ok(threats) => {
info!("{} Einträge von Community Blocklist", threats.len());
@@ -117,13 +113,10 @@ impl IocFetcher {
Err(e) => warn!("Konnte Community Blocklist nicht laden: {}", e),
}
// Cache speichern
self.save_cache(&all_threats).await?;
Ok(all_threats)
}
/// Prüft Cache-Datei auf Aktualität
pub async fn get_cached_iocs(&self) -> Result<Vec<IocEntry>> {
let cache_file = self.cache_dir.join("iocs.json");
@@ -133,8 +126,8 @@ impl IocFetcher {
let modified_datetime: chrono::DateTime<chrono::Local> = modified.into();
let now = chrono::Local::now();
let age = now.signed_duration_since(modified_datetime);
let cache_ttl_duration = chrono::Duration::from_std(self.cache_ttl)?;
if age < cache_ttl_duration {
let content = fs::read_to_string(&cache_file).await?;
let iocs: Vec<IocEntry> = serde_json::from_str(&content)
@@ -144,7 +137,6 @@ impl IocFetcher {
}
}
// Cache veraltet oder nicht vorhanden
self.fetch_all_iocs().await
}
@@ -164,14 +156,12 @@ impl IocFetcher {
let text = response.text().await?;
let mut threats = Vec::new();
// Parse die Gist-Liste (Format: Ein Paketname pro Zeile oder JSON)
for line in text.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
// JSON-Array-Format?
if trimmed.starts_with('[') || trimmed.starts_with('{') {
if let Ok(json_list) = serde_json::from_str::<Vec<String>>(trimmed) {
for pkg in json_list {
@@ -180,13 +170,12 @@ impl IocFetcher {
threat_type: ThreatType::MaliciousBuildScript,
source: "atomic_arch_gist".to_string(),
discovered_date: chrono::Local::now().format("%Y-%m-%d").to_string(),
description: "Atomic Arch Supply Chain Attack - kompromittiertes AUR-Paket".to_string(),
description: "Atomic Arch Supply Chain Attack".to_string(),
confidence: ConfidenceLevel::Critical,
});
}
}
} else {
// Plain text - ein Name pro Zeile
threats.push(IocEntry {
package_name: trimmed.to_string(),
threat_type: ThreatType::MaliciousBuildScript,
@@ -202,19 +191,7 @@ impl IocFetcher {
}
async fn fetch_suspicious_from_aur(&self) -> Result<Vec<IocEntry>> {
// AUR RPC API - KEINE Authentifizierung nötig!
// Wir suchen nach kürzlich übernommenen orphaned packages
let mut threats = Vec::new();
// Query: Letzte 50 Pakete die orphaned waren und jetzt einen neuen Maintainer haben
// Dies ist eine Heuristik basierend auf den bekannten Atomic Arch Patterns
let recent_changes_url = "https://aur.archlinux.org/rpc/v5/search?by=maintainer&arg=orphan";
// Für MVP: Wir nutzen die statische Liste + Heuristiken
// In v0.2 könnte man dynamisch die letzten Änderungen tracken
Ok(threats)
Ok(Vec::new())
}
async fn fetch_community_blocklist(&self) -> Result<Vec<IocEntry>> {
@@ -263,7 +240,6 @@ impl IocFetcher {
Ok(())
}
/// Prüft ob ein Paketname in den IOCs vorkommt
pub fn check_package(&self,
package: &str,
iocs: &[IocEntry]
@@ -274,8 +250,8 @@ impl IocFetcher {
.collect()
}
/// Fuzzy-Match für Typosquatting-Erkennung
pub fn check_typosquatting(&self,
pub fn check_typosquatting(
&self,
package: &str,
iocs: &[IocEntry]
) -> Vec<IocEntry> {
+1 -1
View File
@@ -97,7 +97,7 @@ async fn main() -> Result<()> {
} else {
println!("{} {}", "⚠️ Bedrohungen gefunden:".red().bold(), threats.len());
for threat in threats {
println!(" {} {} - {}", "🔴".red(), threat.package, threat.action_required);
println!(" {} {} - {}", "🔴".red(), threat.package, threat.threat_type);
}
}
}
+29 -33
View File
@@ -11,7 +11,6 @@ use crate::config::AegisConfig;
use crate::ioc_fetcher::{IocEntry, IocFetcher};
use crate::trust_scorer::{CriticalFinding, TrustScorer, TrustScore};
/// Ergebnis eines einzelnen Paket-Scans
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScanResult {
pub package: String,
@@ -39,12 +38,12 @@ pub struct ScanDetails {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ScanStatus {
Safe, // Score >= 80, keine IOCs
Warning, // Score 50-79 oder Warnungen
Suspicious, // Score 30-49 oder IOC Match
Dangerous, // Score < 30 oder kritischer IOC
IOCDetected, // Direkter IOC-Match
Unknown, // Konnte nicht analysiert werden
Safe,
Warning,
Suspicious,
Dangerous,
IOCDetected,
Unknown,
}
pub struct PackageScanner {
@@ -67,7 +66,6 @@ impl PackageScanner {
let ioc_fetcher = IocFetcher::new(cache_dir.clone()).await?;
let trust_scorer = TrustScorer::new()?;
// Whitelist laden
let whitelist_path = cache_dir.join("whitelist.json");
let whitelist = if whitelist_path.exists() {
let content = fs::read_to_string(&whitelist_path).await?;
@@ -85,7 +83,6 @@ impl PackageScanner {
})
}
/// Scannt ein einzelnes Paket
pub async fn scan_package(
&self,
package: &str,
@@ -93,14 +90,11 @@ impl PackageScanner {
) -> Result<ScanResult> {
info!("Scanne Paket: {}", package);
// 1. IOC-Check
let iocs = self.ioc_fetcher.get_cached_iocs().await?;
let ioc_matches = self.ioc_fetcher.check_package(package, &iocs);
// 2. AUR-Info holen
let aur_info = self.fetch_aur_info(package).await?;
// 3. PKGBUILD holen und analysieren (falls verfügbar)
let pkgbuild_analysis = if let Some(ref info) = aur_info {
if let Some(url) = &info.url_path {
match self.fetch_pkgbuild(package, url).await {
@@ -123,7 +117,6 @@ impl PackageScanner {
None
};
// 4. Ergebnis zusammenstellen
let mut result = if let Some((score, pkgbuild_raw)) = pkgbuild_analysis {
let mut warnings: Vec<String> = score.warnings.into_iter().collect();
let critical_findings: Vec<String> = score
@@ -132,7 +125,6 @@ impl PackageScanner {
.map(|c| format!("{:?}", c))
.collect();
// IOC-Warnungen hinzufügen
let ioc_strings: Vec<String> = ioc_matches
.iter()
.map(|ioc| format!("{}: {} ({})", ioc.threat_type, ioc.description, ioc.confidence))
@@ -162,7 +154,6 @@ impl PackageScanner {
}),
}
} else {
// Fallback: Installiertes Paket analysieren
let installed_info = self.get_installed_info(package).await?;
let score = self.trust_scorer.check_installed_package(&installed_info);
@@ -187,7 +178,6 @@ impl PackageScanner {
Ok(result)
}
/// Scannt alle installierten AUR-Pakete
pub async fn scan_all_installed(
&self,
verbose: bool,
@@ -219,14 +209,14 @@ impl PackageScanner {
}
}
// Sortiere nach Score (gefährlichste zuerst)
results.sort_by(|a, b| a.score.cmp(&b.score));
Ok(results)
}
/// Prüft gegen aktuelle IOC-Listen
pub async fn check_iocs(&self, list_type: &str) -> Result<Vec<IocThreat>> {
pub async fn check_iocs(
&self, _list_type: &str
) -> Result<Vec<IocThreat>> {
let iocs = self.ioc_fetcher.get_cached_iocs().await?;
let foreign_packages = self.get_foreign_packages().await?;
@@ -238,9 +228,9 @@ impl PackageScanner {
for ioc in matches {
threats.push(IocThreat {
package: pkg.clone(),
threat_type: format!("{:?}", ioc.threat_type),
threat_type: format!("{}", ioc.threat_type),
source: ioc.source,
confidence: format!("{:?}", ioc.confidence),
confidence: format!("{}", ioc.confidence),
action_required: matches!(ioc.confidence, crate::ioc_fetcher::ConfidenceLevel::Critical | crate::ioc_fetcher::ConfidenceLevel::High),
});
}
@@ -288,9 +278,9 @@ impl PackageScanner {
Ok(())
}
// --- Hilfsmethoden ---
async fn fetch_aur_info(&self, package: &str) -> Result<Option<AurPackageInfo>> {
async fn fetch_aur_info(
&self, package: &str
) -> Result<Option<AurPackageInfo>> {
let url = format!(
"https://aur.archlinux.org/rpc/v5/info?arg[]={}",
package
@@ -310,8 +300,9 @@ impl PackageScanner {
Ok(Some(rpc_response.results[0].clone()))
}
async fn fetch_pkgbuild(&self, package: &str, _url_path: &str) -> Result<String> {
// Korrekte AUR PKGBUILD URL: /cgit/aur.git/plain/PKGBUILD?h=<package>
async fn fetch_pkgbuild(
&self, package: &str, _url_path: &str
) -> Result<String> {
let pkgbuild_url = format!(
"https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h={}",
package
@@ -325,7 +316,8 @@ impl PackageScanner {
response.text().await.context("Konnte PKGBUILD nicht lesen")
}
async fn get_foreign_packages(&self) -> Result<Vec<(String, String)>> {
async fn get_foreign_packages(&self
) -> Result<Vec<(String, String)>> {
let output = Command::new("pacman")
.args(["-Qm"])
.output()
@@ -349,7 +341,8 @@ impl PackageScanner {
Ok(packages)
}
async fn get_installed_info(&self, package: &str) -> Result<String> {
async fn get_installed_info(&self, package: &str
) -> Result<String> {
let output = Command::new("pacman")
.args(["-Qi", package])
.output()
@@ -383,15 +376,17 @@ impl PackageScanner {
ScanStatus::Safe
}
fn save_whitelist(&self) -> Result<()> {
fn save_whitelist(&self
) -> Result<()> {
let whitelist_path = self.cache_dir.join("whitelist.json");
let json = serde_json::to_string_pretty(&self.whitelist)?;
// Blocking IO für HashSet - in Produktion async machen
std::fs::write(whitelist_path, json)?;
Ok(())
}
async fn calculate_cache_size(&self) -> Result<u64> {
async fn calculate_cache_size(
&self
) -> Result<u64> {
let mut total_size = 0u64;
let mut entries = fs::read_dir(&self.cache_dir).await?;
@@ -405,7 +400,9 @@ impl PackageScanner {
Ok(total_size)
}
async fn get_last_cache_update(&self) -> Result<Option<String>> {
async fn get_last_cache_update(
&self
) -> Result<Option<String>> {
let cache_file = self.cache_dir.join("iocs.json");
if !cache_file.exists() {
return Ok(None);
@@ -441,7 +438,6 @@ pub struct IocThreat {
pub action_required: bool,
}
// AUR RPC Response Types
#[derive(Debug, Clone, Serialize, Deserialize)]
struct AurRpcResponse {
#[serde(rename = "resultcount")]
+28 -1
View File
@@ -1 +1,28 @@
Unnamed repository; edit this file 'description' to name the repository.
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
/// Gemeinsame Utility-Funktionen
pub fn get_project_dirs() -> Result<directories::ProjectDirs> {
directories::ProjectDirs::from("eu", "heimatlosen", "aegisaur")
.ok_or_else(|| anyhow::anyhow!("Konnte Projekt-Verzeichnisse nicht ermitteln"))
}
/// Formatiert Bytes als menschenlesbare Größe
pub fn format_bytes(bytes: u64) -> String {
const UNITS: [&str; 6] = ["B", "KB", "MB", "GB", "TB", "PB"];
let mut size = bytes as f64;
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
format!("{:.2} {}", size, UNITS[unit_index])
}
/// Prüft ob ein Befehl verfügbar ist
pub fn command_exists(cmd: &str) -> bool {
which::which(cmd).is_ok()
}