Files
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
23 KiB
JSON

{"repo": "alfredooe/aurdit", "files": {"cmd/aurdit/main.go": {"18347b0f8dad5acb518bda4bf916c04d51aca071": {"t": 1785745650.480243, "n": 6124, "h": "b3b4db3f9ecaa247", "c": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\n\t\"github.com/Alfredooe/aurdit/internal/audit\"\n)\n\nfunc main() {\n\tif len(os.Args) < 2 {\n\t\tusage()\n\t\tos.Exit(1)\n\t}\n\n\tcmd := os.Args[1]\n\n\tswitch cmd {\n\tcase \"check\":\n\t\tcmdCheck()\n\tcase \"help\", \"-h\", \"--help\":\n\t\tusage()\n\tdefault:\n\t\tcmdAudit(cmd)\n\t}\n}\n\nfunc cmdAudit(pkg string) {\n\thistory := 5\n\tskillsDir := \"./skills\"\n\tverbose := false\n\tjsonOut := false\n\tcommit := \"\"\n\n\t// Parse remaining flags after the package name\n\targs := os.Args[2:]\n\tfor i := 0; i < len(args); i++ {\n\t\tswitch args[i] {\n\t\tcase \"--history\":\n\t\t\tif i+1 < len(args) {\n\t\t\t\thistory, _ = strconv.Atoi(args[i+1])\n\t\t\t\ti++\n\t\t\t}\n\t\tcase \"--skills-dir\":\n\t\t\tif i+1 < len(args) {\n\t\t\t\tskillsDir = args[i+1]\n\t\t\t\ti++\n\t\t\t}\n\t\tcase \"--commit\":\n\t\t\tif i+1 < len(args) {\n\t\t\t\tcommit = args[i+1]\n\t\t\t\ti++\n\t\t\t}\n\t\tcase \"--json\":\n\t\t\tjsonOut = true\n\t\tcase \"-v\", \"--verbose\":\n\t\t\tverbose = true\n\t\t}\n\t}\n\tif history < 2 {\n\t\thistory = 2\n\t}\n\n\tapiKey := audit.APIKey()\n\tif apiKey == \"\" {\n\t\tfmt.Fprintln(os.Stderr, \"error: DEEPSEEK_API_KEY not set\")\n\t\tos.Exit(1)\n\t}\n\n\ta := audit.New(apiKey, skillsDir, loadConfig())\n\tif verbose {\n\t\ta.Verbose(os.Stderr)\n\t}\n\tctx := context.Background()\n\n\tvar result *audit.PackageResult\n\tvar err error\n\tif commit != \"\" {\n\t\tresult, err = a.AuditCommit(ctx, pkg, commit, history)\n\t} else {\n\t\tresult, err = a.AuditHistory(ctx, pkg, history)\n\t}\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tif jsonOut {\n\t\tprintResultJSON(result)\n\t} else {\n\t\tprintResult(result)\n\t}\n}\n\nfunc cmdCheck() {\n\tskillsDir := \"./skills\"\n\tverbose := false\n\tjsonOut := false\n\targs := os.Args[2:]\n\tfor i := 0; i < len(args); i++ {\n\t\tif args[i] == \"--skills-dir\" && i+1 < len(args) {\n\t\t\tskillsDir = args[i+1]\n\t\t\ti++\n\t\t} else if args[i] == \"-v\" || args[i] == \"--verbose\" {\n\t\t\tverbose = true\n\t\t} else if args[i] == \"--json\" {\n\t\t\tjsonOut = true\n\t\t}\n\t}\n\n\tapiKey := audit.APIKey()\n\tif apiKey == \"\" {\n\t\tfmt.Fprintln(os.Stderr, \"error: DEEPSEEK_API_KEY not set\")\n\t\tos.Exit(1)\n\t}\n\n\tupdates, err := audit.ListUpdates()\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tif len(updates) == 0 {\n\t\tif !jsonOut {\n\t\t\tfmt.Println(\"No AUR packages with pending updates found.\")\n\t\t}\n\t\treturn\n\t}\n\n\tif !jsonOut {\n\t\tfmt.Printf(\"\\nFound %d package(s) with pending updates:\\n\", len(updates))\n\t\tfor _, u := range updates {\n\t\t\tfmt.Printf(\" %s %s → %s\\n\", u.Name, u.InstalledVersion, u.AURVersion)\n\t\t}\n\t\tfmt.Println()\n\t}\n\n\ta := audit.New(apiKey, skillsDir, loadConfig())\n\tif verbose {\n\t\ta.Verbose(os.Stderr)\n\t}\n\n\tprogress := func(pkg, verdict string) {\n\t\tif jsonOut {\n\t\t\treturn\n\t\t}\n\t\tif verdict == \"ERROR\" {\n\t\t\tfmt.Printf(\" %s — ERROR\\n\", pkg)\n\t\t} else {\n\t\t\tfmt.Printf(\" %s — %s\\n\", pkg, verdict)\n\t\t}\n\t}\n\n\tctx := context.Background()\n\tresults := a.AuditUpdateList(ctx, updates, progress)\n\n\tif len(results) == 0 {\n\t\tfmt.Println(\"No AUR packages with pending updates found.\")\n\t\treturn\n\t}\n\n\tif jsonOut {\n\t\tprintResultsJSON(results)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"\\nAudited %d package(s) with pending updates:\\n\\n\", len(results))\n\n\t// Summary table\n\tw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)\n\tfmt.Fprintln(w, \"PKG\\tVERDICT\\tCONFIDENCE\\tSUMMARY\")\n\tfmt.Fprintln(w, \"---\\t---\\t---\\t---\")\n\tfor _, r := range results {\n\t\tif r.Error != \"\" {\n\t\t\tfmt.Fprintf(w, \"%s\\tERROR\\t-\\t%s\\n\", r.Package, r.Error)\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Fprintf(w, \"%s\\t%s\\t%s\\t%s\\n\", r.Package, r.Verdict.Verdict, r.Verdict.Confidence, truncate(r.Verdict.Summary, 80))\n\t}\n\tw.Flush()\n\n\t// Detailed findings\n\tfmt.Println()\n\tfor _, r := range results {\n\t\tif r.Error != \"\" {\n\t\t\tfmt.Printf(\"\\n── %s (ERROR) ──\\n %s\\n\", r.Package, r.Error)\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Printf(\"\\n── %s (%s, %s confidence) ──\\n\", r.Package, r.Verdict.Verdict, r.Verdict.Confidence)\n\t\tfmt.Println(r.Verdict.Summary)\n\t\tif len(r.Verdict.Findings) == 0 {\n\t\t\tfmt.Println(\"\\n No findings.\")\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Println()\n\t\tfor _, f := range r.Verdict.Findings {\n\t\t\tseverity := severityLabel(f.Severity)\n\t\t\tfmt.Printf(\" %s [%s] line %d: %s\\n\", severity, f.TTP, f.Line, f.Detail)\n\t\t}\n\t}\n}\n\nfunc printResult(r *audit.PackageResult) {\n\ticon := verdictLabel(r.Verdict.Verdict)\n\tfmt.Printf(\"\\n%s %s — %s (confidence: %s)\\n\", icon, r.Package, r.Verdict.Verdict, r.Verdict.Confidence)\n\tfmt.Println(strings.Repeat(\"─\", 60))\n\tfmt.Println(r.Verdict.Summary)\n\tfmt.Println()\n\n\tif len(r.Verdict.Findings) == 0 {\n\t\tfmt.Println(\"No findings.\")\n\t\treturn\n\t}\n\n\tfor _, f := range r.Verdict.Findings {\n\t\tif f.Detail == \"\" && f.TTP == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tseverity := severityLabel(f.Severity)\n\t\tfmt.Printf(\" %s [%s] line %d: %s\\n\", severity, f.TTP, f.Line, f.Detail)\n\t}\n\tfmt.Println()\n}\n\nfunc severityLabel(s string) string {\n\tswitch s {\n\tcase \"CRITICAL\":\n\t\treturn \"CRITICAL\"\n\tcase \"HIGH\":\n\t\treturn \"HIGH\"\n\tcase \"MEDIUM\":\n\t\treturn \"MEDIUM\"\n\tcase \"LOW\":\n\t\treturn \"LOW\"\n\tdefault:\n\t\treturn s\n\t}\n}\n\nfunc verdictLabel(v string) string {\n\tswitch v {\n\tcase \"SAFE\":\n\t\treturn \"[SAFE]\"\n\tcase \"SUSPICIOUS\":\n\t\treturn \"[SUSPICIOUS]\"\n\tcase \"MALICIOUS\":\n\t\treturn \"[MALICIOUS]\"\n\tdefault:\n\t\treturn \"[?]\"\n\t}\n}\n\nfunc truncate(s string, n int) string {\n\tif len(s) <= n {\n\t\treturn s\n\t}\n\treturn s[:n-3] + \"...\"\n}\n\nfunc printResultJSON(r *audit.PackageResult) {\n\tjson.NewEncoder(os.Stdout).Encode(r)\n}\n\nfunc printResultsJSON(results []*audit.PackageResult) {\n\tjson.NewEncoder(os.Stdout).Encode(results)\n}\n\nfunc loadConfig() audit.Config {\n\treturn audit.LoadConfig(\n\t\t\"./configs/aurdit.yaml\",\n\t\tos.ExpandEnv(\"$HOME/.config/aurdit/config.yaml\"),\n\t)\n}\n\nfunc usage() {\n\tfmt.Println(`aurdit — audit AUR PKGBUILDs for malicious changes\n\nUsage:\n aurdit <pkgname> [--history 5] [--commit <hash>] [--skills-dir ./skills] [--verbose]\n Audit a package by comparing recent PKGBUILD versions to detect\n anomalous changes. Defaults to the last 5 versions.\n With --commit, center the comparison around a specific commit hash.\n\n aurdit check [--skills-dir ./skills] [--verbose]\n Discover all installed AUR packages with pending updates and audit\n each one, printing a summary.\n\nEnvironment:\n DEEPSEEK_API_KEY DeepSeek API key (required)`)\n}\n"}, "ec5b9401e1310a78e245ce40e469ca9639f490db": {"t": 1785755375.290546, "n": 6124, "h": "b3b4db3f9ecaa247", "c": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\n\t\"github.com/Alfredooe/aurdit/internal/audit\"\n)\n\nfunc main() {\n\tif len(os.Args) < 2 {\n\t\tusage()\n\t\tos.Exit(1)\n\t}\n\n\tcmd := os.Args[1]\n\n\tswitch cmd {\n\tcase \"check\":\n\t\tcmdCheck()\n\tcase \"help\", \"-h\", \"--help\":\n\t\tusage()\n\tdefault:\n\t\tcmdAudit(cmd)\n\t}\n}\n\nfunc cmdAudit(pkg string) {\n\thistory := 5\n\tskillsDir := \"./skills\"\n\tverbose := false\n\tjsonOut := false\n\tcommit := \"\"\n\n\t// Parse remaining flags after the package name\n\targs := os.Args[2:]\n\tfor i := 0; i < len(args); i++ {\n\t\tswitch args[i] {\n\t\tcase \"--history\":\n\t\t\tif i+1 < len(args) {\n\t\t\t\thistory, _ = strconv.Atoi(args[i+1])\n\t\t\t\ti++\n\t\t\t}\n\t\tcase \"--skills-dir\":\n\t\t\tif i+1 < len(args) {\n\t\t\t\tskillsDir = args[i+1]\n\t\t\t\ti++\n\t\t\t}\n\t\tcase \"--commit\":\n\t\t\tif i+1 < len(args) {\n\t\t\t\tcommit = args[i+1]\n\t\t\t\ti++\n\t\t\t}\n\t\tcase \"--json\":\n\t\t\tjsonOut = true\n\t\tcase \"-v\", \"--verbose\":\n\t\t\tverbose = true\n\t\t}\n\t}\n\tif history < 2 {\n\t\thistory = 2\n\t}\n\n\tapiKey := audit.APIKey()\n\tif apiKey == \"\" {\n\t\tfmt.Fprintln(os.Stderr, \"error: DEEPSEEK_API_KEY not set\")\n\t\tos.Exit(1)\n\t}\n\n\ta := audit.New(apiKey, skillsDir, loadConfig())\n\tif verbose {\n\t\ta.Verbose(os.Stderr)\n\t}\n\tctx := context.Background()\n\n\tvar result *audit.PackageResult\n\tvar err error\n\tif commit != \"\" {\n\t\tresult, err = a.AuditCommit(ctx, pkg, commit, history)\n\t} else {\n\t\tresult, err = a.AuditHistory(ctx, pkg, history)\n\t}\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tif jsonOut {\n\t\tprintResultJSON(result)\n\t} else {\n\t\tprintResult(result)\n\t}\n}\n\nfunc cmdCheck() {\n\tskillsDir := \"./skills\"\n\tverbose := false\n\tjsonOut := false\n\targs := os.Args[2:]\n\tfor i := 0; i < len(args); i++ {\n\t\tif args[i] == \"--skills-dir\" && i+1 < len(args) {\n\t\t\tskillsDir = args[i+1]\n\t\t\ti++\n\t\t} else if args[i] == \"-v\" || args[i] == \"--verbose\" {\n\t\t\tverbose = true\n\t\t} else if args[i] == \"--json\" {\n\t\t\tjsonOut = true\n\t\t}\n\t}\n\n\tapiKey := audit.APIKey()\n\tif apiKey == \"\" {\n\t\tfmt.Fprintln(os.Stderr, \"error: DEEPSEEK_API_KEY not set\")\n\t\tos.Exit(1)\n\t}\n\n\tupdates, err := audit.ListUpdates()\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tif len(updates) == 0 {\n\t\tif !jsonOut {\n\t\t\tfmt.Println(\"No AUR packages with pending updates found.\")\n\t\t}\n\t\treturn\n\t}\n\n\tif !jsonOut {\n\t\tfmt.Printf(\"\\nFound %d package(s) with pending updates:\\n\", len(updates))\n\t\tfor _, u := range updates {\n\t\t\tfmt.Printf(\" %s %s → %s\\n\", u.Name, u.InstalledVersion, u.AURVersion)\n\t\t}\n\t\tfmt.Println()\n\t}\n\n\ta := audit.New(apiKey, skillsDir, loadConfig())\n\tif verbose {\n\t\ta.Verbose(os.Stderr)\n\t}\n\n\tprogress := func(pkg, verdict string) {\n\t\tif jsonOut {\n\t\t\treturn\n\t\t}\n\t\tif verdict == \"ERROR\" {\n\t\t\tfmt.Printf(\" %s — ERROR\\n\", pkg)\n\t\t} else {\n\t\t\tfmt.Printf(\" %s — %s\\n\", pkg, verdict)\n\t\t}\n\t}\n\n\tctx := context.Background()\n\tresults := a.AuditUpdateList(ctx, updates, progress)\n\n\tif len(results) == 0 {\n\t\tfmt.Println(\"No AUR packages with pending updates found.\")\n\t\treturn\n\t}\n\n\tif jsonOut {\n\t\tprintResultsJSON(results)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"\\nAudited %d package(s) with pending updates:\\n\\n\", len(results))\n\n\t// Summary table\n\tw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)\n\tfmt.Fprintln(w, \"PKG\\tVERDICT\\tCONFIDENCE\\tSUMMARY\")\n\tfmt.Fprintln(w, \"---\\t---\\t---\\t---\")\n\tfor _, r := range results {\n\t\tif r.Error != \"\" {\n\t\t\tfmt.Fprintf(w, \"%s\\tERROR\\t-\\t%s\\n\", r.Package, r.Error)\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Fprintf(w, \"%s\\t%s\\t%s\\t%s\\n\", r.Package, r.Verdict.Verdict, r.Verdict.Confidence, truncate(r.Verdict.Summary, 80))\n\t}\n\tw.Flush()\n\n\t// Detailed findings\n\tfmt.Println()\n\tfor _, r := range results {\n\t\tif r.Error != \"\" {\n\t\t\tfmt.Printf(\"\\n── %s (ERROR) ──\\n %s\\n\", r.Package, r.Error)\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Printf(\"\\n── %s (%s, %s confidence) ──\\n\", r.Package, r.Verdict.Verdict, r.Verdict.Confidence)\n\t\tfmt.Println(r.Verdict.Summary)\n\t\tif len(r.Verdict.Findings) == 0 {\n\t\t\tfmt.Println(\"\\n No findings.\")\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Println()\n\t\tfor _, f := range r.Verdict.Findings {\n\t\t\tseverity := severityLabel(f.Severity)\n\t\t\tfmt.Printf(\" %s [%s] line %d: %s\\n\", severity, f.TTP, f.Line, f.Detail)\n\t\t}\n\t}\n}\n\nfunc printResult(r *audit.PackageResult) {\n\ticon := verdictLabel(r.Verdict.Verdict)\n\tfmt.Printf(\"\\n%s %s — %s (confidence: %s)\\n\", icon, r.Package, r.Verdict.Verdict, r.Verdict.Confidence)\n\tfmt.Println(strings.Repeat(\"─\", 60))\n\tfmt.Println(r.Verdict.Summary)\n\tfmt.Println()\n\n\tif len(r.Verdict.Findings) == 0 {\n\t\tfmt.Println(\"No findings.\")\n\t\treturn\n\t}\n\n\tfor _, f := range r.Verdict.Findings {\n\t\tif f.Detail == \"\" && f.TTP == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tseverity := severityLabel(f.Severity)\n\t\tfmt.Printf(\" %s [%s] line %d: %s\\n\", severity, f.TTP, f.Line, f.Detail)\n\t}\n\tfmt.Println()\n}\n\nfunc severityLabel(s string) string {\n\tswitch s {\n\tcase \"CRITICAL\":\n\t\treturn \"CRITICAL\"\n\tcase \"HIGH\":\n\t\treturn \"HIGH\"\n\tcase \"MEDIUM\":\n\t\treturn \"MEDIUM\"\n\tcase \"LOW\":\n\t\treturn \"LOW\"\n\tdefault:\n\t\treturn s\n\t}\n}\n\nfunc verdictLabel(v string) string {\n\tswitch v {\n\tcase \"SAFE\":\n\t\treturn \"[SAFE]\"\n\tcase \"SUSPICIOUS\":\n\t\treturn \"[SUSPICIOUS]\"\n\tcase \"MALICIOUS\":\n\t\treturn \"[MALICIOUS]\"\n\tdefault:\n\t\treturn \"[?]\"\n\t}\n}\n\nfunc truncate(s string, n int) string {\n\tif len(s) <= n {\n\t\treturn s\n\t}\n\treturn s[:n-3] + \"...\"\n}\n\nfunc printResultJSON(r *audit.PackageResult) {\n\tjson.NewEncoder(os.Stdout).Encode(r)\n}\n\nfunc printResultsJSON(results []*audit.PackageResult) {\n\tjson.NewEncoder(os.Stdout).Encode(results)\n}\n\nfunc loadConfig() audit.Config {\n\treturn audit.LoadConfig(\n\t\t\"./configs/aurdit.yaml\",\n\t\tos.ExpandEnv(\"$HOME/.config/aurdit/config.yaml\"),\n\t)\n}\n\nfunc usage() {\n\tfmt.Println(`aurdit — audit AUR PKGBUILDs for malicious changes\n\nUsage:\n aurdit <pkgname> [--history 5] [--commit <hash>] [--skills-dir ./skills] [--verbose]\n Audit a package by comparing recent PKGBUILD versions to detect\n anomalous changes. Defaults to the last 5 versions.\n With --commit, center the comparison around a specific commit hash.\n\n aurdit check [--skills-dir ./skills] [--verbose]\n Discover all installed AUR packages with pending updates and audit\n each one, printing a summary.\n\nEnvironment:\n DEEPSEEK_API_KEY DeepSeek API key (required)`)\n}\n"}, "a695e50a3131ffe3091cc5f69782ceebd9b73959": {"t": 1785755376.9046326, "n": 6235, "h": "a017b6280ac58ccf", "c": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\n\t\"github.com/Alfredooe/aurdit/internal/audit\"\n)\n\nfunc main() {\n\tif len(os.Args) < 2 {\n\t\tusage()\n\t\tos.Exit(1)\n\t}\n\n\tcmd := os.Args[1]\n\n\tswitch cmd {\n\tcase \"check\":\n\t\tcmdCheck()\n\tcase \"help\", \"-h\", \"--help\":\n\t\tusage()\n\tdefault:\n\t\tcmdAudit(cmd)\n\t}\n}\n\nfunc cmdAudit(pkg string) {\n\thistory := 5\n\tskillsDir := \"./skills\"\n\tverbose := false\n\tjsonOut := false\n\tcommit := \"\"\n\n\t// Parse remaining flags after the package name\n\targs := os.Args[2:]\n\tfor i := 0; i < len(args); i++ {\n\t\tswitch args[i] {\n\t\tcase \"--history\":\n\t\t\tif i+1 < len(args) {\n\t\t\t\thistory, _ = strconv.Atoi(args[i+1])\n\t\t\t\ti++\n\t\t\t}\n\t\tcase \"--skills-dir\":\n\t\t\tif i+1 < len(args) {\n\t\t\t\tskillsDir = args[i+1]\n\t\t\t\ti++\n\t\t\t}\n\t\tcase \"--commit\":\n\t\t\tif i+1 < len(args) {\n\t\t\t\tcommit = args[i+1]\n\t\t\t\ti++\n\t\t\t}\n\t\tcase \"--json\":\n\t\t\tjsonOut = true\n\t\tcase \"-v\", \"--verbose\":\n\t\t\tverbose = true\n\t\t}\n\t}\n\tif history < 2 {\n\t\thistory = 2\n\t}\n\n\tapiKey := audit.APIKey()\n\tif apiKey == \"\" {\n\t\tfmt.Fprintln(os.Stderr, \"error: DEEPSEEK_API_KEY not set\")\n\t\tos.Exit(1)\n\t}\n\n\ta := audit.New(apiKey, skillsDir, loadConfig())\n\tif verbose {\n\t\ta.Verbose(os.Stderr)\n\t}\n\tctx := context.Background()\n\n\tvar result *audit.PackageResult\n\tvar err error\n\tif history < 2 {\n\t\thistory = 2\n\t}\n\tif commit != \"\" {\n\t\tresult, err = a.AuditCommit(ctx, pkg, commit, history)\n\t} else {\n\t\tresult, err = a.AuditHistory(ctx, pkg, history)\n\t}\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tif jsonOut {\n\t\tprintResultJSON(result)\n\t} else {\n\t\tprintResult(result)\n\t}\n}\n\nfunc cmdCheck() {\n\tskillsDir := \"./skills\"\n\tverbose := false\n\tjsonOut := false\n\targs := os.Args[2:]\n\tfor i := 0; i < len(args); i++ {\n\t\tif args[i] == \"--skills-dir\" && i+1 < len(args) {\n\t\t\tskillsDir = args[i+1]\n\t\t\ti++\n\t\t} else if args[i] == \"-v\" || args[i] == \"--verbose\" {\n\t\t\tverbose = true\n\t\t} else if args[i] == \"--json\" {\n\t\t\tjsonOut = true\n\t\t}\n\t}\n\n\tapiKey := audit.APIKey()\n\tif apiKey == \"\" {\n\t\tfmt.Fprintln(os.Stderr, \"error: DEEPSEEK_API_KEY not set\")\n\t\tos.Exit(1)\n\t}\n\n\tupdates, err := audit.ListUpdates()\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tif len(updates) == 0 {\n\t\tif !jsonOut {\n\t\t\tfmt.Println(\"No AUR packages with pending updates found.\")\n\t\t}\n\t\treturn\n\t}\n\n\tif !jsonOut {\n\t\tfmt.Printf(\"\\nFound %d package(s) with pending updates:\\n\", len(updates))\n\t\tfor _, u := range updates {\n\t\t\tfmt.Printf(\" %s %s → %s\\n\", u.Name, u.InstalledVersion, u.AURVersion)\n\t\t}\n\t\tfmt.Println()\n\t}\n\n\ta := audit.New(apiKey, skillsDir, loadConfig())\n\tif verbose {\n\t\ta.Verbose(os.Stderr)\n\t}\n\n\tprogress := func(pkg, verdict string) {\n\t\tif jsonOut {\n\t\t\treturn\n\t\t}\n\t\tif verdict == \"ERROR\" {\n\t\t\tfmt.Printf(\" %s — ERROR\\n\", pkg)\n\t\t} else {\n\t\t\tfmt.Printf(\" %s — %s\\n\", pkg, verdict)\n\t\t}\n\t}\n\n\tctx := context.Background()\n\tresults := a.AuditUpdateList(ctx, updates, progress)\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tif len(results) == 0 {\n\t\tfmt.Println(\"No AUR packages with pending updates found.\")\n\t\treturn\n\t}\n\n\tif jsonOut {\n\t\tprintResultsJSON(results)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"\\nAudited %d package(s) with pending updates:\\n\\n\", len(results))\n\n\t// Summary table\n\tw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)\n\tfmt.Fprintln(w, \"PKG\\tVERDICT\\tCONFIDENCE\\tSUMMARY\")\n\tfmt.Fprintln(w, \"---\\t---\\t---\\t---\")\n\tfor _, r := range results {\n\t\tif r.Error != \"\" {\n\t\t\tfmt.Fprintf(w, \"%s\\tERROR\\t-\\t%s\\n\", r.Package, r.Error)\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Fprintf(w, \"%s\\t%s\\t%s\\t%s\\n\", r.Package, r.Verdict.Verdict, r.Verdict.Confidence, truncate(r.Verdict.Summary, 80))\n\t}\n\tw.Flush()\n\n\t// Detailed findings\n\tfmt.Println()\n\tfor _, r := range results {\n\t\tif r.Error != \"\" {\n\t\t\tfmt.Printf(\"\\n── %s (ERROR) ──\\n %s\\n\", r.Package, r.Error)\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Printf(\"\\n── %s (%s, %s confidence) ──\\n\", r.Package, r.Verdict.Verdict, r.Verdict.Confidence)\n\t\tfmt.Println(r.Verdict.Summary)\n\t\tif len(r.Verdict.Findings) == 0 {\n\t\t\tfmt.Println(\"\\n No findings.\")\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Println()\n\t\tfor _, f := range r.Verdict.Findings {\n\t\t\tseverity := colorSeverity(f.Severity)\n\t\t\tfmt.Printf(\" %s [%s] line %d: %s\\n\", severity, f.TTP, f.Line, f.Detail)\n\t\t}\n\t}\n}\n\nfunc printResult(r *audit.PackageResult) {\n\ticon := verdictIcon(r.Verdict.Verdict)\n\tfmt.Printf(\"\\n%s %s — %s (confidence: %s)\\n\", icon, r.Package, r.Verdict.Verdict, r.Verdict.Confidence)\n\tfmt.Println(strings.Repeat(\"─\", 60))\n\tfmt.Println(r.Verdict.Summary)\n\tfmt.Println()\n\n\tif len(r.Verdict.Findings) == 0 {\n\t\tfmt.Println(\"No findings.\")\n\t\treturn\n\t}\n\n\tfor _, f := range r.Verdict.Findings {\n\t\tif f.Detail == \"\" && f.TTP == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tseverity := colorSeverity(f.Severity)\n\t\tfmt.Printf(\" %s [%s] line %d: %s\\n\", severity, f.TTP, f.Line, f.Detail)\n\t}\n\tfmt.Println()\n}\n\nfunc colorSeverity(s string) string {\n\tswitch s {\n\tcase \"CRITICAL\":\n\t\treturn \"CRITICAL\"\n\tcase \"HIGH\":\n\t\treturn \"HIGH\"\n\tcase \"MEDIUM\":\n\t\treturn \"MEDIUM\"\n\tcase \"LOW\":\n\t\treturn \"LOW\"\n\tdefault:\n\t\treturn s\n\t}\n}\n\nfunc verdictIcon(v string) string {\n\tswitch v {\n\tcase \"SAFE\":\n\t\treturn \"[SAFE]\"\n\tcase \"SUSPICIOUS\":\n\t\treturn \"[SUSPICIOUS]\"\n\tcase \"MALICIOUS\":\n\t\treturn \"[MALICIOUS]\"\n\tdefault:\n\t\treturn \"[?]\"\n\t}\n}\n\nfunc truncate(s string, n int) string {\n\tif len(s) <= n {\n\t\treturn s\n\t}\n\treturn s[:n-3] + \"...\"\n}\n\nfunc printResultJSON(r *audit.PackageResult) {\n\tjson.NewEncoder(os.Stdout).Encode(r)\n}\n\nfunc printResultsJSON(results []*audit.PackageResult) {\n\tjson.NewEncoder(os.Stdout).Encode(results)\n}\n\nfunc loadConfig() audit.Config {\n\treturn audit.LoadConfig(\n\t\t\"./configs/aurdit.yaml\",\n\t\tos.ExpandEnv(\"$HOME/.config/aurdit/config.yaml\"),\n\t)\n}\n\nfunc usage() {\n\tfmt.Println(`aurdit — audit AUR PKGBUILDs for malicious changes\n\nUsage:\n aurdit <pkgname> [--history 5] [--commit <hash>] [--skills-dir ./skills] [--verbose]\n Audit a package by comparing recent PKGBUILD versions to detect\n anomalous changes. Defaults to the last 5 versions.\n With --commit, center the comparison around a specific commit hash.\n\n aurdit check [--skills-dir ./skills] [--verbose]\n Discover all installed AUR packages with pending updates and audit\n each one, printing a summary.\n\nEnvironment:\n DEEPSEEK_API_KEY DeepSeek API key (required)`)\n}\n"}}, "configs/aurdit.yaml": {"18347b0f8dad5acb518bda4bf916c04d51aca071": {"t": 1786068419.7674937, "n": 1812, "h": "a466e7170c6acd42", "c": "# aurdit configuration — loaded at runtime, overrides built-in defaults\n# ./configs/aurdit.yaml is checked first, then ~/.config/aurdit/config.yaml\n#\n# All fields optional. Omit to use built-in defaults.\n\ninstruction: |\n You are a security auditor specialized in analyzing Arch Linux AUR PKGBUILD files.\n You identify malicious patterns, supply chain risks, and suspicious code in package build scripts.\n\n Before analyzing, use list_skills to see what reference material is available,\n then use load_skill to load any relevant TTP documentation.\n\n When reporting findings, use severity levels: CRITICAL, HIGH, MEDIUM, LOW, or INFO.\n Include the approximate line number in the PKGBUILD for each finding.\n For each finding, label it with the most specific MITRE ATT&CK technique ID\n (e.g. T1195.002 for supply chain compromise, T1059.004 for shell execution,\n T1543.002 for systemd persistence, T1027 for obfuscation, T1041 for exfiltration).\n\n When comparing versions, flag what CHANGED between older and newer versions.\n New additions that are high-signal indicators of compromise:\n - npm, bun, or pip newly added to depends=() or makedepends=() — treat as HIGH\n - A new install= file that didn't exist in previous versions — treat as MEDIUM or higher\n - A new maintainer email or contributor line — note it\n - New source URLs, especially non-HTTPS or unfamiliar domains\n - Removal of checksums (switching to SKIP) — treat as MEDIUM\n\n After completing your analysis, call the report_audit function with your findings.\n\n Important: only report actual suspicious findings. Do NOT create INFO findings\n that merely confirm something is safe. If everything is clean, return an empty findings\n array and a single summary sentence.\n\n# model: deepseek-v4-pro\n# base_url: https://api.deepseek.com/v1\n"}}}}