dennisdoomen / dennisdoomen/packageguard

[Feature]: Detect typosquatting and dependency-confusion risks

Open
#220 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
C#
Stars
74
Forks
4
Avg merge
3h
Merged PRs (30d)
20

Description

### Background and motivation

Two well-documented supply-chain attacks are not currently detectable by PackageGuard, and neither can be caught by name-based allow/deny rules, because the whole premise of both attacks is that the malicious package has a name you were not expecting to see.

**Typosquatting.** An attacker publishes a package whose name is one or two characters away from a popular one — `Newtonsoft.Json` vs `Newtonsoft.Jsconsole`, `lodash` vs `1odash`, `moment` vs `momentjs`. A developer mistypes, or copies from a poisoned answer online, and the malicious package is installed. It looks entirely legitimate in a diff.

**Dependency confusion.** An organisation has an internal package, say `Contoso.Internal.Auth`, published to a private feed. An attacker publishes a package with the same name to nuget.org or npmjs.com at a higher version. Depending on feed configuration and version resolution, the public package wins. This is exactly the class of bug that Alex Birsan demonstrated against dozens of large companies, and it remains effective.

PackageGuard is unusually well placed to detect the second case, because it already tracks the resolved feed for every package (`PolicyViolation` carries `FeedName` and `FeedUrl`).

**Proposal**

*Dependency confusion* — this is the higher-value half and the easier one:

```json
{
"settings": {
"namespaces": {
"Contoso.*": { "requireFeed": "*pkgs.dev.azure.com/contoso*" }
}
}
}
```

Any package matching an internal namespace that resolves from a public feed is a violation. As a lower-effort default, warn whenever a package matching a configured internal prefix comes from a public registry. Related, it is worth flagging when a package resolves from a *different* feed than on the previous run, which the cache already has the data to detect.

*Typosquatting* — inherently heuristic and needs care:

- Compare package names against a list of popular packages using Damerau-Levenshtein distance, flagging distance 1–2 where the candidate has dramatically fewer downloads than its neighbour.
- Include the common substitution patterns: `1`/`l`, `0`/`o`, `rn`/`m`, added or removed hyphens and dots, singular/plural.
- Also worth flagging: a package whose name is a popular name plus a suffix such as `-js`, `.core` or `2`.

Points to consider:

- **False positives are the whole problem.** `System.Text.Json` and `System.Text.Json.Nodes` are both legitimate. The download-count differential is what separates a squat from a sibling package, so this heuristic should not fire without it.
- **Where does the popular-package list come from?** Downloading top-N lists at runtime adds a network dependency and a trust question. Shipping a periodically refreshed embedded list is more predictable.
- **Warn, do not fail, by default.** A heuristic that breaks builds will be turned off, and then it protects nobody.
- **Package age helps here.** Typosquats are usually recent, so the `minPackageAgeDays` rule proposed in the risk-gating issue is a useful companion signal.

### Alternative Concerns

- Registries themselves do some typosquat detection, but it is reactive and inconsistent.
- Correctly configured upstream feed policies prevent dependency confusion at source, but many organisations do not have this configured correctly, and PackageGuard is well positioned to tell them so.

### Are you willing help with a pull-request?

No

Contributor guide

Open the contributing guide

Research direction

Start by tracing PolicyViolation, especially its FeedName and FeedUrl fields, then inspect the existing resolved-package cache and the minPackageAgeDays risk-gating work. Define how configured internal namespaces, feed changes, and typosquatting signals should be represented before implementing detection. Done means the behavior is specified, warnings are non-blocking by default, and coverage exists for dependency-confusion and heuristic false-positive cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
security, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.