Three tray bugs today, each caused by fixing the previous one without looking at what else the fix touched. Worth writing down as one thing rather than three. **Two dogs, again.** 0.1.4 taught the app to notice its own package being replaced and reopen. 0.1.5 added the same behaviour to `hound update`, which is the better mechanism because it works no matter which version was running — and I did not remove the first. Both fire during an update: the running app re-execs under a new pid while apt is working, so the updater kills a pid that has already moved and starts a second instance beside it. The in-app restart now waits eight seconds before acting. If the updater is doing its job this process is terminated during the pause and never restarts itself; what survives the wait is the only case the updater cannot cover, which is somebody running `apt upgrade` directly. Two mechanisms, one arbiter. **The launcher stopped working.** The single-instance lock added in 0.1.5 to prevent the duplicate icon made a second launch exit quietly, so clicking the desktop shortcut while Hound was open did nothing at all — no window, no error, no feedback. It now hands the request to the running instance, which shows, unminimises and focuses. The same channel the right-click scan already used: a launch with paths means "scan these", a launch without means "show yourself". Also in this release, and the reason the version is what it says it is: the workspace version did not get bumped. Adding the hound-watch crate pushed `version =` from line 6 to line 7 of Cargo.toml, and the bump was a line-numbered sed. The three GUI files went to 0.1.10 and the workspace stayed at 0.1.9, so the package built as 0.1.9 while the manifest would have advertised 0.1.10 — every installed agent would have offered an update forever, installed it, and still seen one available. `the_declared_versions_all_agree` caught it before the build finished. tools/bump-version.sh now does this by matching content and section rather than line numbers, asserts every substitution actually happened, and refuses to write invalid JSON. Adds crates/hound-watch: the registry firehose. npm publishes a CouchDB _changes stream and PyPI an RSS feed of every new project, both public, so this needs no user data at all — which is the answer to how a product with no telemetry grows its detection. Each new package is handed to the detectors Hound already ships, so there is one definition of "this install script is hostile" and a rule that fires in CI fires identically on the firehose. Verified against the live registries: 240 releases in one poll, 60 triaged, parsers tested against captured responses rather than the network. 476 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
38 lines
1.4 KiB
Rust
38 lines
1.4 KiB
Rust
//! Poll the registries and triage what was just published.
|
|
//!
|
|
//! cargo run -p hound-watch --example triage-live -- [how-many]
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
let limit: usize = std::env::args()
|
|
.nth(1)
|
|
.and_then(|a| a.parse().ok())
|
|
.unwrap_or(40);
|
|
|
|
let mut cursor = hound_watch::Cursor::default();
|
|
let releases = hound_watch::poll(&mut cursor)?;
|
|
let npm: Vec<_> = releases.iter().filter(|r| r.ecosystem == "npm").collect();
|
|
println!("polled {} npm releases; triaging {}", npm.len(), limit.min(npm.len()));
|
|
|
|
let mut checked = 0;
|
|
let mut with_scripts = 0;
|
|
let mut findings = 0;
|
|
for r in npm.iter().take(limit) {
|
|
let meta = match hound_watch::npm_metadata(&r.name) {
|
|
Ok(m) => m,
|
|
Err(_) => continue, // unpublished between the poll and now, or rate limited
|
|
};
|
|
checked += 1;
|
|
if !meta.install_scripts.is_empty() {
|
|
with_scripts += 1;
|
|
}
|
|
for f in hound_watch::triage(&meta) {
|
|
findings += 1;
|
|
println!("\n [{}] {} — {}", f.severity.as_str(), f.subject, f.explanation);
|
|
for (hook, cmd) in &meta.install_scripts {
|
|
println!(" {hook}: {}", &cmd[..cmd.len().min(90)]);
|
|
}
|
|
}
|
|
}
|
|
println!("\nchecked {checked}, {with_scripts} run install scripts, {findings} finding(s)");
|
|
Ok(())
|
|
}
|