Skip to content

Commit c50ccbd

Browse files
authored
feat: add support for yarn.lock files (#308)
1 parent 5686910 commit c50ccbd

25 files changed

Lines changed: 402 additions & 108 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ The following dependency file formats are supported:
114114
- `poetry.lock`
115115
- `uv.lock`
116116
- `package-lock.json` (v1, v2, v3)
117+
- `yarn.lock` (v1, v2)
117118

118119
### Check dependencies introduced through the CLI
119120

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ dependencies = [
1919
"tomlkit<0.14.0,>=0.11.6",
2020
"tomli<3.0.0,>=2.2.1; python_version < \"3.13\"",
2121
"pydantic>=2.11.7,<3.0.0",
22+
"pyyaml>=6.0.2",
2223
]
2324
name = "twyn"
2425
description = "Security tool against dependency typosquatting attacks"
@@ -47,6 +48,7 @@ dev = [
4748
"types-requests<3.0.0.0,>=2.32.4.20250611",
4849
"types-python-dateutil>=2.9.0.20250809",
4950
"freezegun>=1.5.5",
51+
"types-pyyaml>=6.0.12.20250822",
5052
]
5153
local = ["ipdb<1.0.0,>=0.13.9", "commitizen<5.0,>=2.38", "pdbpp<1.0.0,>=0.11.6"]
5254

src/twyn/base/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"poetry.lock": dependency_parser.PoetryLockParser,
2626
"uv.lock": dependency_parser.UvLockParser,
2727
"package-lock.json": dependency_parser.PackageLockJsonParser,
28+
"yarn.lock": dependency_parser.YarnLockParser,
2829
}
2930

3031

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from twyn.dependency_managers.managers.npm_dependency_manager import NpmDependencyManager
2+
from twyn.dependency_managers.managers.pypi_dependency_manager import PypiDependencyManager
3+
from twyn.dependency_managers.utils import (
4+
PACKAGE_ECOSYSTEMS,
5+
get_dependency_manager_from_file,
6+
get_dependency_manager_from_name,
7+
)
8+
9+
__all__ = [
10+
"NpmDependencyManager",
11+
"PypiDependencyManager",
12+
"get_dependency_manager_from_file",
13+
"get_dependency_manager_from_name",
14+
"PACKAGE_ECOSYSTEMS",
15+
]

src/twyn/dependency_managers/managers/__init__.py

Whitespace-only changes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from dataclasses import dataclass
2+
from pathlib import Path
3+
4+
from twyn.trusted_packages.references.base import AbstractPackageReference
5+
6+
7+
@dataclass
8+
class BaseDependencyManager:
9+
"""Base class for all `DependencyManagers`.
10+
11+
It acts as a repository, linking programming languages with trusted packages sources and dependency file names.
12+
"""
13+
14+
name: str
15+
trusted_packages_source: type[AbstractPackageReference]
16+
dependency_files: set[str]
17+
18+
@classmethod
19+
def matches_dependency_file(cls, dependency_file: str) -> bool:
20+
return Path(dependency_file).name in cls.dependency_files
21+
22+
@classmethod
23+
def matches_language_name(cls, name: str) -> bool:
24+
return cls.name == Path(name).name.lower()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from dataclasses import dataclass
2+
3+
from twyn.dependency_managers.managers.base import BaseDependencyManager
4+
from twyn.dependency_parser.parsers.constants import PACKAGE_LOCK_JSON, YARN_LOCK
5+
from twyn.trusted_packages import TopNpmReference
6+
7+
8+
@dataclass
9+
class NpmDependencyManager(BaseDependencyManager):
10+
name = "npm"
11+
trusted_packages_source = TopNpmReference
12+
dependency_files = {PACKAGE_LOCK_JSON, YARN_LOCK}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from dataclasses import dataclass
2+
3+
from twyn.dependency_managers.managers.base import BaseDependencyManager
4+
from twyn.dependency_parser.parsers.constants import POETRY_LOCK, REQUIREMENTS_TXT, UV_LOCK
5+
from twyn.trusted_packages import TopPyPiReference
6+
7+
8+
@dataclass
9+
class PypiDependencyManager(BaseDependencyManager):
10+
name = "pypi"
11+
trusted_packages_source = TopPyPiReference
12+
dependency_files = {UV_LOCK, POETRY_LOCK, REQUIREMENTS_TXT}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from twyn.dependency_managers.exceptions import NoMatchingDependencyManagerError
2+
from twyn.dependency_managers.managers.base import BaseDependencyManager
3+
from twyn.dependency_managers.managers.npm_dependency_manager import NpmDependencyManager
4+
from twyn.dependency_managers.managers.pypi_dependency_manager import PypiDependencyManager
5+
6+
7+
def get_dependency_manager_from_file(dependency_file: str) -> type[BaseDependencyManager]:
8+
for manager in DEPENDENCY_MANAGERS:
9+
if manager.matches_dependency_file(dependency_file):
10+
return manager
11+
raise NoMatchingDependencyManagerError
12+
13+
14+
def get_dependency_manager_from_name(name: str) -> type[BaseDependencyManager]:
15+
for manager in DEPENDENCY_MANAGERS:
16+
if manager.matches_language_name(name):
17+
return manager
18+
raise NoMatchingDependencyManagerError
19+
20+
21+
DEPENDENCY_MANAGERS: list[type[BaseDependencyManager]] = [
22+
PypiDependencyManager,
23+
NpmDependencyManager,
24+
]
25+
PACKAGE_ECOSYSTEMS = {x.name for x in DEPENDENCY_MANAGERS}

src/twyn/dependency_parser/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
from twyn.dependency_parser.parsers.lock_parser import PoetryLockParser, UvLockParser
44
from twyn.dependency_parser.parsers.package_lock_json import PackageLockJsonParser
55
from twyn.dependency_parser.parsers.requirements_txt_parser import RequirementsTxtParser
6+
from twyn.dependency_parser.parsers.yarn_lock_parser import YarnLockParser
67

7-
__all__ = ["RequirementsTxtParser", "PoetryLockParser", "UvLockParser", "PackageLockJsonParser"]
8+
__all__ = ["RequirementsTxtParser", "PoetryLockParser", "UvLockParser", "PackageLockJsonParser", "YarnLockParser"]

0 commit comments

Comments
 (0)