Skip to content

Commit 162b880

Browse files
committed
fix(protocol): take the checksum from the registry, not from the caller
fast-install accepted a checksum as a URI parameter, and the handler is reachable from page JS through Spicetify.Daemon, so anything loaded in the client could have its own bytes verified against its own hash. The checksum is now resolved from the registry for that id and version, and an artifact the registry does not carry installs with a warning that nothing verified it.
1 parent 00ba7fa commit 162b880

2 files changed

Lines changed: 25 additions & 4 deletions

File tree

rust/crates/spicetify/src/commands/pkg.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ pub(crate) fn list(ctx: &AppContext) -> Result<()> {
9292
Ok(())
9393
}
9494

95+
/// The checksum the registry recorded for `id@version`, if it carries that
96+
/// version at all.
97+
///
98+
/// Callers that did not resolve the module themselves (the `spicetify://`
99+
/// handler, and so anything in the client that reaches it) use this rather
100+
/// than a checksum handed to them, so the bytes are held to what was
101+
/// published rather than to whatever the caller claims they should hash to.
102+
pub(crate) fn registry_checksum(identifier: &str, version: &str) -> Option<String> {
103+
let vault = fetch_vault(DEFAULT_VAULT).ok()?;
104+
let entry = vault.modules.get(identifier)?.v.get(version)?;
105+
(!entry.checksum.is_empty()).then(|| entry.checksum.clone())
106+
}
107+
95108
pub(crate) fn install(ctx: &AppContext, identifier: &str) -> Result<()> {
96109
let vault = fetch_vault(DEFAULT_VAULT)?;
97110
let Some(module) = vault.modules.get(identifier) else {

rust/crates/spicetify/src/commands/protocol.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,18 @@ fn perform(ctx: &AppContext, action: ProtocolAction, uri: &Url) -> Result<()> {
8484
ProtocolAction::Add | ProtocolAction::FastInstall | ProtocolAction::FastEnable => {
8585
let id = require_id(&query)?;
8686
let artifacts = get_all_params(&query, "artifacts");
87-
let checksum = get_param(&query, "checksum").unwrap_or_default();
87+
// The checksum comes from the registry, never from the caller.
88+
// Anything that can reach this handler (page JS through
89+
// Spicetify.Daemon, a spicetify:// link) could otherwise supply
90+
// the hash of its own bytes and have them verified against
91+
// themselves, which is no verification at all.
92+
let checksum = crate::commands::pkg::registry_checksum(&id.module_identifier, &id.version)
93+
.unwrap_or_default();
94+
if checksum.is_empty() {
95+
tracing::warn!(
96+
"{id}: not in the registry, so there is no checksum to verify these bytes against"
97+
);
98+
}
8899
module::add_store(&paths, &id, Store { installed: false, artifacts, checksum })?;
89100
if matches!(action, ProtocolAction::Add) {
90101
return Ok(());
@@ -165,9 +176,6 @@ fn require_param(query: &[(Cow<'_, str>, Cow<'_, str>)], key: &str) -> Result<St
165176
.ok_or_else(|| anyhow::anyhow!("missing '{key}' query parameter"))
166177
}
167178

168-
fn get_param(query: &[(Cow<'_, str>, Cow<'_, str>)], key: &str) -> Option<String> {
169-
query.iter().find(|(k, _)| k == key).map(|(_, v)| v.to_string())
170-
}
171179

172180
fn get_all_params(query: &[(Cow<'_, str>, Cow<'_, str>)], key: &str) -> Vec<String> {
173181
query.iter().filter(|(k, _)| k == key).map(|(_, v)| v.to_string()).collect()

0 commit comments

Comments
 (0)