Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog.d/155-pnpm-yarn-cache-rules.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Add safe rules for the pnpm package store and the Yarn download caches
(Yarn 1 global cache, Yarn Berry per-project and global caches),
classified as regenerable package-manager cache.
21 changes: 21 additions & 0 deletions crates/diskern-core/rules/base.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,27 @@
"verdict": "safe",
"description": "npm's download cache. Re-fetched on demand; removing it only makes the next install download packages again."
},
{
"id": "pnpm-store",
"patterns": ["**/pnpm/store/**", "**/.pnpm-store/**"],
"category": "package_manager_cache",
"verdict": "safe",
"description": "pnpm's content-addressable package store, under the pnpm home directory or a configured store dir. Re-fetched on demand; removing it only makes the next install download and unpack packages again."
},
{
"id": "yarn-cache",
"patterns": ["**/.cache/yarn/**", "**/library/caches/yarn/**", "**/appdata/local/yarn/cache/**"],
"category": "package_manager_cache",
"verdict": "safe",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Use a conservative verdict for Yarn PnP caches. The PnP loader reads installed packages directly from their cache paths (https://yarnpkg.com/features/pnp), including .yarn/cache and the global Berry cache. Quarantining these ZIPs breaks application imports immediately, even without a committed zero-install cache; it does more than slow the next install. Please use review for paths that can back PnP installs, consistent with the existing node-modules rule, explain the reinstall/restore requirement, and add local/global PnP fixtures that must not classify as Safe. Retain Safe only where the cache is demonstrably separate from runtime dependencies.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — d95bcc4 splits **/.yarn/cache/** and the global **/.yarn/berry/cache/** out of yarn-cache into a new yarn-pnp-cache rule at review, mirroring the node-modules treatment. The description calls out the PnP loader reading packages straight from the archives and that yarn install must restore them. Yarn 1's global download caches stay safe. Local and global PnP fixtures now assert Review in yarn_pnp_caches_are_review_not_safe, plus a yarn-pnp-cache row in the embedded-rule fixture table.

"description": "Yarn 1's global download cache. Re-fetched on demand; removing it only makes the next install download packages again."
},
{
"id": "yarn-pnp-cache",
"patterns": ["**/.yarn/cache/**", "**/.yarn/berry/cache/**"],
"category": "package_manager_cache",
"verdict": "review",
"description": "Yarn Berry's package cache — a project's .yarn/cache or the global .yarn/berry/cache. With Plug'n'Play these archives back live imports, so quarantining them breaks the app until yarn install restores them; only a pure download cache under the node-modules linker."
},
{
"id": "unix-system-logs",
"patterns": ["/var/log/**", "/private/var/log/**"],
Expand Down
109 changes: 109 additions & 0 deletions crates/diskern-core/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,97 @@ mod tests {
}
}

#[test]
fn pnpm_store_is_safe_to_remove() {
let db = RulesDb::embedded();
for path in [
"/home/u/.local/share/pnpm/store/v3/files/aa/bb",
"/Users/u/Library/pnpm/store/v3/files/aa/bb",
"C:\\Users\\u\\AppData\\Local\\pnpm\\store\\v3\\files\\aa\\bb",
"/home/u/.pnpm-store/v3/files/aa/bb",
] {
let (cat, verdict, rule) = db.classify(std::path::Path::new(path));
assert_eq!(
cat,
Category::PackageManagerCache,
"{path} matched {rule:?}"
);
assert_eq!(verdict, Verdict::Safe, "{path} matched {rule:?}");
}
}

/// The pnpm home directory holds the store, but also the pnpm binary
/// itself and globally installed packages — neither is a download
/// cache.
#[test]
fn pnpm_home_outside_the_store_is_not_marked_safe() {
let db = RulesDb::embedded();
for path in [
"/Users/u/Library/pnpm/pnpm",
"/home/u/.local/share/pnpm/global/5/.npmrc",
] {
let (cat, verdict, rule) = db.classify(std::path::Path::new(path));
assert_eq!(cat, Category::Unknown, "{path} matched {rule:?}");
assert_ne!(verdict, Verdict::Safe, "{path} matched {rule:?}");
}
}

#[test]
fn yarn_cache_is_safe_to_remove() {
let db = RulesDb::embedded();
for path in [
"/home/u/.cache/yarn/v6/npm-react-18.2.0.zip",
"/Users/u/Library/Caches/Yarn/v6/npm-react-18.2.0.zip",
"C:\\Users\\u\\AppData\\Local\\Yarn\\Cache\\v6\\npm-react-18.2.0.zip",
] {
let (cat, verdict, rule) = db.classify(std::path::Path::new(path));
assert_eq!(
cat,
Category::PackageManagerCache,
"{path} matched {rule:?}"
);
assert_eq!(verdict, Verdict::Safe, "{path} matched {rule:?}");
}
}

/// Yarn Berry's Plug'n'Play loader reads packages straight out of the
/// cache archives — a project's `.yarn/cache` or the global `.yarn/berry/cache`
/// — so these paths are `review`, not `safe`: quarantining them breaks
/// imports until `yarn install` restores the cache.
#[test]
fn yarn_pnp_caches_are_review_not_safe() {
let db = RulesDb::embedded();
for path in [
"/home/u/proj/.yarn/cache/react-npm-18.2.0.zip",
"/home/u/.yarn/berry/cache/react-npm-18.2.0.zip",
] {
let (cat, verdict, rule) = db.classify(std::path::Path::new(path));
assert_eq!(
cat,
Category::PackageManagerCache,
"{path} matched {rule:?}"
);
assert_eq!(verdict, Verdict::Review, "{path} matched {rule:?}");
}
}

/// A project's `.yarn` directory keeps the Yarn release binary,
/// plugins and editor SDKs next to `cache` — only `cache` is
/// re-downloaded packages.
#[test]
fn yarn_support_files_outside_the_cache_are_not_marked_safe() {
let db = RulesDb::embedded();
for path in [
"/home/u/proj/.yarn/releases/yarn-4.5.0.cjs",
"/home/u/proj/.yarn/sdks/typescript/bin/tsc",
"/home/u/proj/.yarnrc.yml",
] {
let (cat, verdict, rule) = db.classify(std::path::Path::new(path));
assert_eq!(cat, Category::Unknown, "{path} matched {rule:?}");
assert_ne!(verdict, Verdict::Safe, "{path} matched {rule:?}");
}
}

/// An installed application's own repair binary is not a reclaimable
/// download. `unknown` is the right answer: report::build drops those,
/// so it never reaches the user as an actionable row.
Expand Down Expand Up @@ -539,6 +630,24 @@ mod tests {
Category::PackageManagerCache,
Verdict::Safe,
),
(
"/home/u/.local/share/pnpm/store/v3/files/aa/bb",
"pnpm-store",
Category::PackageManagerCache,
Verdict::Safe,
),
(
"/Users/u/Library/Caches/Yarn/v6/npm-react-18.2.0.zip",
"yarn-cache",
Category::PackageManagerCache,
Verdict::Safe,
),
(
"/home/u/proj/.yarn/cache/react-npm-18.2.0.zip",
"yarn-pnp-cache",
Category::PackageManagerCache,
Verdict::Review,
),
(
"/var/log/apt/history.log",
"unix-system-logs",
Expand Down
Loading