Skip to content

Commit f64c8d2

Browse files
authored
Merge pull request #7921 from LawnGnome/affix-checks
typosquat: check for prefixes being manipulated like suffixes
2 parents 0c8c42d + 997d98d commit f64c8d2

File tree

3 files changed

+45
-20
lines changed

3 files changed

+45
-20
lines changed

src/typosquat/cache.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use typomania::{
77
Harness,
88
};
99

10-
use super::{checks::Suffixes, config, database::TopCrates};
10+
use super::{checks::Affixes, config, database::TopCrates};
1111

1212
static NOTIFICATION_EMAILS_ENV: &str = "TYPOSQUAT_NOTIFICATION_EMAILS";
1313

@@ -72,9 +72,9 @@ impl Cache {
7272
.with_check(Typos::new(config::TYPOS.iter().map(|(c, typos)| {
7373
(*c, typos.iter().map(|ss| ss.to_string()).collect())
7474
})))
75-
.with_check(Suffixes::new(
76-
config::SUFFIX_SEPARATORS.iter(),
75+
.with_check(Affixes::new(
7776
config::SUFFIXES.iter(),
77+
config::SUFFIX_SEPARATORS.iter(),
7878
))
7979
.build(top),
8080
),

src/typosquat/checks.rs

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,29 @@ use typomania::{
33
Corpus, Package,
44
};
55

6-
/// A typomania check that checks if commonly used suffixes have been added or removed.
7-
pub struct Suffixes {
6+
/// A typomania check that checks if commonly used prefixes or suffixes have been added to or
7+
/// removed from a package name.
8+
pub struct Affixes {
9+
affixes: Vec<String>,
810
separators: Vec<String>,
9-
suffixes: Vec<String>,
1011
}
1112

12-
impl Suffixes {
13-
pub fn new<Sep, Suf>(separators: Sep, suffixes: Suf) -> Self
13+
impl Affixes {
14+
pub fn new<Aff, Sep>(affixes: Aff, separators: Sep) -> Self
1415
where
16+
Aff: Iterator,
17+
Aff::Item: ToString,
1518
Sep: Iterator,
1619
Sep::Item: ToString,
17-
Suf: Iterator,
18-
Suf::Item: ToString,
1920
{
2021
Self {
22+
affixes: affixes.map(|s| s.to_string()).collect(),
2123
separators: separators.map(|s| s.to_string()).collect(),
22-
suffixes: suffixes.map(|s| s.to_string()).collect(),
2324
}
2425
}
2526
}
2627

27-
impl Check for Suffixes {
28+
impl Check for Affixes {
2829
fn check(
2930
&self,
3031
corpus: &dyn Corpus,
@@ -34,11 +35,32 @@ impl Check for Suffixes {
3435
let mut squats = Vec::new();
3536

3637
for separator in self.separators.iter() {
37-
for suffix in self.suffixes.iter() {
38-
let combo = format!("{separator}{suffix}");
38+
for affix in self.affixes.iter() {
39+
// If the package being examined starts with this prefix and separator combo, then
40+
// we should see if it exists without that prefix in the popular crate corpus.
41+
let combo = format!("{affix}{separator}");
42+
if let Some(stem) = name.strip_prefix(&combo) {
43+
if corpus.possible_squat(stem, name, package)? {
44+
squats.push(Squat::Custom {
45+
message: format!("adds the {combo} prefix"),
46+
package: stem.to_string(),
47+
})
48+
}
49+
}
50+
51+
// Alternatively, let's see if adding the prefix and separator combo to the package
52+
// results in something popular; eg somebody trying to squat `foo` with `rs-foo`.
53+
let prefixed = format!("{combo}{name}");
54+
if corpus.possible_squat(&prefixed, name, package)? {
55+
squats.push(Squat::Custom {
56+
message: format!("removes the {combo} prefix"),
57+
package: prefixed,
58+
});
59+
}
3960

4061
// If the package being examined ends in this separator and suffix combo, then we
41-
// should see if it exists in the popular crate corpus.
62+
// should see if it exists without that suffix in the popular crate corpus.
63+
let combo = format!("{separator}{affix}");
4264
if let Some(stem) = name.strip_suffix(&combo) {
4365
if corpus.possible_squat(stem, name, package)? {
4466
squats.push(Squat::Custom {
@@ -74,16 +96,17 @@ mod tests {
7496
use super::*;
7597

7698
#[test]
77-
fn test_suffixes() -> anyhow::Result<()> {
99+
fn test_affixes() -> anyhow::Result<()> {
78100
let popular = TestCorpus::default()
79101
.with_package(TestPackage::new("foo", "foo", ["Alice", "Bob"]))
80102
.with_package(TestPackage::new("bar-rs", "Rust bar", ["Charlie"]))
81-
.with_package(TestPackage::new("quux_sys", "libquux", ["Alice"]));
103+
.with_package(TestPackage::new("quux_sys", "libquux", ["Alice"]))
104+
.with_package(TestPackage::new("core-xyz", "Core xyz", ["Alice"]));
82105

83106
let harness = Harness::empty_builder()
84-
.with_check(Suffixes::new(
85-
["-", "_"].iter(),
107+
.with_check(Affixes::new(
86108
["core", "rs", "sys"].iter(),
109+
["-", "_"].iter(),
87110
))
88111
.build(popular);
89112

@@ -103,8 +126,10 @@ mod tests {
103126
// Now try some packages that should be.
104127
for package in [
105128
TestPackage::new("foo-rs", "no shared author", ["Charlie"]),
129+
TestPackage::new("rs-foo", "no shared author", ["Charlie"]),
106130
TestPackage::new("quux", "libquux", ["Charlie"]),
107131
TestPackage::new("quux_sys_rs", "libquux... for Rust?", ["Charlie"]),
132+
TestPackage::new("xyz", "unprefixed core-xyz", ["Charlie"]),
108133
]
109134
.into_iter()
110135
{

src/typosquat/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub(super) static CRATE_NAME_ALPHABET: &str =
99
pub(super) static SUFFIX_SEPARATORS: &[&str] = &["-", "_"];
1010

1111
/// Commonly used suffixes when building crate names.
12-
pub(super) static SUFFIXES: &[&str] = &["api", "cli", "core", "lib", "rs", "rust", "sys"];
12+
pub(super) static SUFFIXES: &[&str] = &["api", "cargo", "cli", "core", "lib", "rs", "rust", "sys"];
1313

1414
/// The number of crates to consider in the "top crates" corpus.
1515
pub(super) static TOP_CRATES: i64 = 3000;

0 commit comments

Comments
 (0)