
공급망, slopsquatting, typosquatting 공격으로부터 의존성과 코드를 보호합니다.
cargo install sloppy-joe
2026년 3월의 LiteLLM 공급망 공격은 월간 9700만 다운로드의 패키지를 손상시켰습니다. 공격자는 배포 자격 증명을 탈취하고, SSH 키, 클라우드 자격 증명, K8s 비밀을 수집하는 악성 버전을 푸시했습니다. sloppy-joe의 기본 72시간 버전 연령 게이트는 두 개의 감염된 버전을 모두 차단했을 것입니다. 이들은 게이트가 열리기 훨씬 전인 몇 시간 이내에 발견되었습니다. CI에서
sloppy-joe check를 실행하면 이 공격은 실패합니다. 전체 분석
AI 코드 생성기는 약 20%의 확률로 패키지 이름을 환각합니다. 공격자는 그 이름들을 등록하고 기다립니다. sloppy-joe는 npm install 또는 pip install이 실행되기 전에 CI에서 이를 잡아냅니다.
cargo install sloppy-joe
sloppy-joe check
sloppy-joe check --full
sloppy-joe check --ci
sloppy-joe check --dir ./my-project
sloppy-joe check --type npm
sloppy-joe check --python-groups dev,test --python-version 3.12 sloppy-joe check --python-extras docs --python-platform linux --python-version 3.12
sloppy-joe check --config /etc/sloppy-joe/config.json
sloppy-joe check --config https://raw.githubusercontent.com/yourorg/security-configs/main/sloppy-joe.json
sloppy-joe check --json
sloppy-joe check --review-exceptions
sloppy-joe init --register
sloppy-joe init --greenfield --ecosystem npm
sloppy-joe init --from-current
sloppy-joe init --from-current --register
sloppy-joe init > /secure/location/sloppy-joe.json
### Nix```bash
nix profile install github:brennhill/sloppy-joe
스캔 모드:
sloppy-joe check는 빠른 로컬 가드레일을 실행합니다. 항상 매니페스트 파싱, lockfile/동기화, 출처 증명, 지원되지 않는 소스 정책을 적용합니다. 종속성 또는 정책 상태가 변경되었거나 마지막 성공적인 전체 스캔이 24시간보다 오래된 경우 sloppy-joe check --full을 권장합니다.sloppy-joe check --full은 엄격한 온라인 스캔을 실행하고 기록된 성공적인 전체 스캔 상태를 새로고침합니다.sloppy-joe check --ci는 --full과 동일한 엄격한 적용 범위를 CI 지향 목적으로 실행합니다.sloppy-joe check는 기본적으로 runtime 프로필을 평가합니다. 범위가 지정된 종속성이 있으면 경고를 표시하고 CI/빌드 패리티를 위해 명시적 --python-groups, --python-extras, --python-platform 및/또는 --python-version 플래그를 전달하도록 안내합니다.sloppy-joe check 출력은 항상 CI 및 프로덕션 게이팅에 --ci 또는 --full을 사용하라고 알립니다.종료 코드: 0 = 선택한 모드에서 차단 문제 없음, 1 = 차단 문제 발견, 2 = 런타임 오류.
지원 대상: JavaScript (npm, pnpm, Yarn, Bun), Python, Rust, Go, Ruby, PHP, JVM (Gradle/Maven), .NET — 매니페스트 파일에서 자동 감지.
에코시스템 가이드: 각 에코시스템의 현재 신뢰 모델, 지원 기능, fail-closed 제한에 대한 자세한 내용은 docs/ecosystems/README.md를 참조하세요.
| 에코시스템 | 필수 매니페스트 | 신뢰하는 lockfile / 프로젝트 상태 |
|---|---|---|
| JavaScript / npm | package.json | package-lock.json 또는 npm-shrinkwrap.json; 레거시 npm v1은 기본적으로 차단됨 |
| JavaScript / pnpm | package.json | pnpm-lock.yaml |
| JavaScript / Yarn | package.json | yarn.lock |
| JavaScript / Bun | package.json | bun.lock |
| Python | pyproject.toml, requirements*.txt, Pipfile, setup.cfg 또는 setup.py | 신뢰하는 Poetry 경로는 poetry.lock 사용, 신뢰하는 uv 경로는 uv.lock 사용, 완전 해시 잠금된 pip-tools는 커밋된 요구사항 그래프가 --index-url과 모든 --extra-index-url 값을 정확히 바인딩할 때만 신뢰됨; 저장소에 표시되는 Python 인덱스는 trusted_indexes.pypi를 통해 허용 목록에 추가 가능; 신뢰하는 Python 모드는 한 번에 선택된 하나의 설치 프로필을 평가함(기본값 runtime, 명시적 groups/extras/platform/arch/version은 CLI를 통해 전달); 레거시 매니페스트는 기본적으로 경고와 함께 허용됨 |
| Rust | Cargo.toml | Cargo.lock |
| Go | go.mod | 외부 종속성을 위해 go.sum 필요 |
| Ruby | Gemfile | Gemfile.lock |
| PHP / Composer | composer.json | composer.lock |
| JVM / Gradle | build.gradle 또는 build.gradle.kts | gradle.lockfile |
| JVM / Maven | pom.xml | 경고 전용: 아직 신뢰할 수 있는 프로젝트 로컬 lockfile 경로 없음 |
| .NET / NuGet | .csproj | packages.lock.json |
설정 소스: 로컬 파일 경로, HTTPS URL 또는 SLOPPY_JOE_CONFIG 환경 변수. 설정은 프로젝트 디렉터리에서 읽지 않습니다(이유는 CONFIG.md 참조).
온보딩: 저장소와 일치하는 부트스트랩 모드를 사용하세요.
sloppy-joe init --greenfield --ecosystem <eco>는 새 프로젝트를 위한 에코시스템별 시작 정책을 출력합니다. 현재 npm, pypi, cargo에 대해 그린필드 프리셋이 구현되어 있습니다. 다른 에코시스템은 "아직 지원되지 않음" 오류와 함께 실패합니다. --register를 추가하면 저장소 외부에 쓰고 안전하게 등록합니다.sloppy-joe init --from-current는 현재 저장소를 검사하고 검토 전용 부트스트랩 제안을 출력합니다. 현재 --from-current는 first-party 코드가 npm 및/또는 cargo인 저장소에서만 구현되어 있으며, 다른 에코시스템은 "아직 구현되지 않음" 오류와 함께 fail-closed됩니다. --register를 추가하면 생성된 설정을 쓰고 등록합니다.sloppy-joe init을 실행하면 중립적인 수동 템플릿이 출력됩니다.단일 바이너리. 8개 에코시스템. 16가지 공격 유형. 생성적 검사에서 거짓 긍정 없음. AI 에이전트가 변조할 수 없는 설정.
대부분의 종속성 보안 도구는 한두 가지만 확인합니다. 존재 여부 또는 편집 거리입니다. sloppy-joe는 한 번의 패스로 16가지 공격 벡터를 확인합니다: 존재하지 않는 패키지, 10가지 유형의 타이포스쿼팅(호모글리프, 범위 스쿼팅, 문자 반복, 구분자 혼동, 단어 재배열, 인접 스왑, 문자 생략, 혼동된 형태, 대소문자 변형, 버전 접미사), 표준 적용, 버전 연령 게이트, 설치 스크립트 증폭, 종속성 폭발, 유지 관리자 변경, OSV.dev를 통한 알려진 취약점.
단일 Rust 바이너리로 실행되며 런타임 종속성이 없습니다. 모든 8개 주요 패키지 에코시스템을 지원합니다. 그리고 설정은 보안을 위해 설계되었습니다: 프로젝트 디렉터리에서 읽지 않으며, CI를 위해 URL에서 로드 가능하며, 문제가 있을 때 명확한 오류 메시지를 제공합니다.
| sloppy-joe | Socket.dev | GuardDog | Phantom Guard | antislopsquat | |
|---|---|---|---|---|---|
| 존재 확인 | ✅ | ✅ | ❌ | ✅ | ✅ |
| 유사성 / 타이포스쿼트 | ✅ | ✅ | ✅ | ✅ | ❌ |
| 호모글리프 탐지 | ✅ | ❌ | ❌ | ❌ | ❌ |
| 범위 스쿼팅 | ✅ | ❌ | ❌ | ❌ | ❌ |
| 표준 적용 | ✅ | ❌ | ❌ | ❌ | ❌ |
| 버전 연령 게이트 | ✅ | ❌ | ❌ | ❌ | ❌ |
| 설치 스크립트 증폭기 | ✅ | ✅ | ❌ | ❌ | ❌ |
| 종속성 폭발 | ✅ | ❌ | ❌ | ❌ | ❌ |
| 유지 관리자 변경 | ✅ | ✅ | ❌ | ❌ | ❌ |
| OSV 취약점 확인 | ✅ | ✅ | ❌ | ❌ | ❌ |
| 설정 보안 (저장소 외부) | ✅ | N/A | ❌ | ❌ | ❌ |
| 내부 + 허용 목록 | ✅ | ❌ | ❌ |
🔶 = 베타/실험적
공격: AI가 import ai_json_helper를 생성합니다. 패키지가 존재하지 않습니다. 공격자가 PyPI에 ai-json-helper라는 악성 패키지를 등록합니다. 다음 번에 누군가 pip install을 실행하면 악성 패키지를 설치하게 됩니다.
sloppy-joe가 차단하는 방법: 존재 확인이 PyPI API에 접근하여 404를 받습니다. 빌드가 차단됩니다.``` ERROR ai-json-helper [existence] Package 'ai-json-helper' does not exist on the pypi registry. It may be hallucinated by an AI code generator. Fix: Remove 'ai-json-helper' from your dependencies.
### 2. 타이포스쿼팅 (생성적 검사 + 편집 거리 대체)
**공격:** 공격자가 `expresz`를 npm에 등록합니다. 이것은 `express`에서 한 글자가 변경된 것입니다. AI가 생성하거나 개발자가 오타를 냅니다. 이 패키지는 존재하며, 존재 여부 검사를 통과하고 맬웨어를 설치합니다.
**sloppy-joe가 이를 차단하는 방법:** sloppy-joe는 편집 거리로 대체되기 전에 10개의 생성적 검사를 실행합니다. 각 생성적 검사는 종속성 이름의 특정 변형을 생성합니다 (문자 교환, 반복 축소, 접미사 제거, 단어 재정렬, 구분자 정규화, 호모글리프 대체, 스코프 확인) 그리고 알려진 인기 패키지와 정확히 일치하는지 테스트합니다. 이 접근 방식은 [Rust Foundation의 Typomania](https://github.com/rustfoundation/typomania) 라이브러리에서 영감을 받았으며, 변형 후 정확히 일치하는 경우에만 작동하므로 오탐이 거의 없습니다.
Levenshtein 편집 거리는 특정 검사에서 예상하지 못한 새로운 변형에 대한 안전망으로 마지막에 실행됩니다. 이들은 함께 알려진 공격 패턴(정확히)과 알려지지 않은 패턴(광범위하게)을 모두 커버합니다.```
ERROR expresz [similarity/edit-distance]
'expresz' is 1 character away from 'express'. This could be a typosquat.
Fix: If you meant 'express', fix the name in your manifest.
공격: expresss (s 중복) 또는 reeact (e 중복). 이는 일반적인 AI 환각 패턴입니다 — 모델이 반복 문자를 포함한 그럴듯한 이름을 생성합니다.
sloppy-joe가 차단하는 방법: 반복 문자 검사는 한 번에 하나의 중복을 제거하고 결과가 알려진 패키지와 일치하는지 확인합니다. expresss → s 하나 제거 → express → 일치.```
ERROR expresss [similarity/repeated-chars]
'expresss' matches 'express' after removing a repeated character.
Fix: Use 'express' — remove the repeated characters.
### 4. 구분자 혼동
**공격:** `python-dateutil` 대 `python_dateutil` 대 `pythondateutil`. 일부 레지스트리에서는 이들이 서로 다른 패키지입니다. 공격자가 변형을 등록합니다.
**sloppy-joe가 차단하는 방법:** 비교 전에 모든 구분자(`-`, `_`, `.`)를 정규화합니다. 정규화된 형태가 알려진 패키지와 일치하면 플래그가 지정됩니다.```
ERROR socket_io [similarity/separator-confusion]
'socket_io' matches 'socket.io' after normalizing separators.
Fix: Use the canonical name 'socket.io' with the correct separators.
공격: parse-json vs json-parse. Levenshtein 거리는 8입니다 — 편집 거리 검사로는 보이지 않습니다. 그러나 공격자는 재배열된 이름을 등록할 수 있습니다.
sloppy-joe가 차단하는 방법: 구분자로 분할하고, 세그먼트의 모든 순열을 생성한 다음 각각을 코퍼스와 대조합니다. parse-json → 순열 → json-parse → 일치.```
ERROR parse-json [similarity/word-reorder]
'parse-json' is a reordering of 'json-parse'.
Fix: Use 'json-parse' — the segments are in the wrong order.
### 6. 인접 문자 교환
**공격:** `reqeust` 대신 `request`. 두 인접 문자가 전치된 일반적인 오타로, 공격자가 악용합니다.
**sloppy-joe가 이를 차단하는 방법:** 의존성 이름의 모든 인접 교환 변형을 생성하여 각각을 코퍼스와 대조합니다.```
ERROR reqeusts [similarity/char-swap]
'reqeusts' matches 'requests' with two adjacent characters swapped.
Fix: Use 'requests' — two characters are transposed.
공격: reqests (u 누락) 대신 requests. AI가 문자 하나를 빠뜨려 유효해 보이는 이름이 생성됩니다.
sloppy-joe가 차단하는 방법: 이름의 모든 위치에 a-z 문자를 하나씩 삽입하고 결과가 알려진 패키지와 일치하는지 확인합니다. reqests + 위치 3에 u → requests → 일치.```
ERROR reqests [similarity/omitted-char]
'reqests' matches 'requests' with one character inserted.
Fix: Use 'requests' — a character appears to be missing.
### 8. 호모글리프(시각적 유사 문자)
**공격:** 키릴 문자 `е`(U+0435)가 라틴 문자 `e`(U+0065) 대신 사용된 `rеquests`. 시각적으로 동일합니다. 패키지 이름이 `requests`와 똑같이 보이지만 다른 악성 패키지로 연결됩니다.
**sloppy-joe가 차단하는 방법:** 17개의 알려진 호모글리프 문자(키릴 문자, 전각 문자, 필기체 변형)를 해당 라틴 문자로 대체한 후 결과가 알려진 패키지와 일치하는지 확인합니다.```
ERROR rеquests [similarity/homoglyph]
'rеquests' contains characters that look identical to 'requests'
but are different Unicode codepoints (homoglyphs).
Fix: Replace the lookalike characters with standard ASCII.
공격: py-utils vs python-utils. PyPI에서는 이들은 서로 다른 패키지입니다. AI가 하나를 생성할 때 다른 것을 의도한 경우가 발생합니다. 마찬가지로 Go 모듈에서는 github.com vs gitlab.com 문제가 있습니다.
sloppy-joe가 이를 차단하는 방법: 생태계별 대체 규칙을 적용합니다(PyPI의 경우 py↔python, Go의 경우 github↔gitlab). 그런 다음 어떤 변형이 알려진 패키지와 일치하는지 확인합니다.``` ERROR py-flask [similarity/confused-form] 'py-flask' is a confused form of 'flask'. Fix: Use the canonical name 'flask'.
### 10. 대소문자 변형 공격 (대소문자 구분 레지스트리)
**공격:** Go, Maven, Ruby에서는 `Rails`와 `rails`가 서로 다른 패키지입니다. 공격자는 대문자 변형을 등록합니다.
**sloppy-joe가 차단하는 방법:** 대소문자를 구분하는 레지스트리에서는 알려진 패키지의 대소문자 변형이 오류로 플래그 지정됩니다. 대소문자를 구분하지 않는 레지스트리(npm, PyPI, Cargo, NuGet, PHP)에서는 대소문자 변형이 안전하므로 건너뜁니다.```
ERROR Rails [similarity/case-variant]
'Rails' differs from 'rails' only in letter casing.
On case-sensitive registries (ruby) these resolve to different packages.
Fix: Use the exact casing 'rails' in your manifest.
공격: requests2 또는 lodash-4. AI가 패키지 이름에 버전 번호를 붙여서 적절한 버전을 지정하지 않습니다.
sloppy-joe가 이를 차단하는 방법: 끝자리 숫자와 구분자를 제거하고 기본 이름이 알려진 패키지와 일치하는지 확인합니다.``` ERROR requests2 [similarity/version-suffix] 'requests2' looks like 'requests' with a version suffix appended. Fix: Use 'requests' and specify the version in your manifest's version field.
### 12. 스코프 스쿼팅 (npm, PHP, Go, JVM)
**공격:** 공격자는 `@typos/lodash`를 npm에 등록합니다 — `@types/lodash`와 한 글자 차이입니다. 또는 Packagist에 `larvael/framework` — `laravel/framework`와 두 글자 차이입니다. 또는 Go에 `github.com/gooogle/protobuf` — `o`가 하나 더 많습니다. 스코프가 언뜻 보기에는 합법적으로 보입니다. 패키지가 확인됩니다. 악성코드가 설치됩니다.
이는 드물지만 가능성이 있는 일이며 — "드물지만 가능성 있는" 것이 바로 sloppy-joe가 존재하는 이유입니다. 2021년 `ua-parser-js` 사건은 스코프 관련이었습니다. 주간 다운로드 수가 수백만인 패키지에서 발생할 수 있다면, 여러분의 패키지에서도 발생할 수 있습니다.
**sloppy-joe가 차단하는 방법:** 의존성 이름에서 스코프/네임스페이스를 추출하고 편집 거리를 사용하여 알려진 양호한 스코프 목록과 비교합니다. npm(`@scope`), PHP(`vendor/`), Go(`github.com/org`), JVM(`com.group`)에서 작동합니다.```
ERROR @typos/lodash [similarity/scope-squatting]
Scope '@typos' is 1 character away from the known scope '@types'.
Scope squatting is a known supply chain attack vector.
Fix: If you meant '@types/lodash', fix the scope in your manifest.
.``` ERROR github.com/gooogle/protobuf [similarity/scope-squatting] Scope 'github.com/gooogle' is 1 character away from 'github.com/google'. Fix: If you meant 'github.com/google/protobuf', fix the org name.
### 13. 비정규 패키지 (공격이 아님 — 일관성 게이트)
**공격:** 공격이 아님 — 일관성 문제입니다. AI가 학습 데이터에서 인기가 많았던 `moment`를 선택했지만, 팀은 `dayjs`를 사용합니다. 동일한 작업에 대해 서로 다른 팀이 다른 패키지를 사용하면 유지보수 부채와 의존성 팽창이 발생합니다.
**sloppy-joe가 차단하는 방법:** 구성 파일에서 각 표준 패키지를 거부된 대안에 매핑합니다. 종속성이 대안과 일치하면 빌드가 실패합니다.```
ERROR moment [canonical]
'moment' is not the approved package for this purpose.
Your team uses 'dayjs'.
Fix: Replace 'moment' with 'dayjs' in your manifest file.
공격: 공격자가 패키지 유지보수자의 계정을 탈취하거나(또는 유지보수자가 악의적인 행동을 취하여) 악성 패치 버전을 게시합니다. 이는 정상 업데이트처럼 보입니다. CI가 즉시 설치하면 아무도 알아채기 전에 침해당합니다.
sloppy-joe가 차단하는 방법: 버전 연령 게이트는 min_version_age_hours(기본값: 72시간) 이전에 게시된 버전의 모든 종속성을 차단합니다. 이를 통해 커뮤니티, Socket.dev 및 기타 스캐너가 악성 버전을 플래그할 시간을 확보합니다.```
ERROR react [metadata/version-age]
Version '^19.0.0' of 'react' was published 6 hours ago (minimum: 72 hours).
New versions need time for the community and security scanners to review them.
Fix: Wait until the version is at least 72 hours old, or pin to an older version.
### 15. 새로 생성된 패키지
**공격:** 어제 생성되었고 다운로드 수가 3회인 패키지로, 인기 패키지와 유사한 이름을 가지고 있습니다. 오타 스쿼트나 향후 공격을 위한 자리채움(placeholder)일 가능성이 높습니다.
**sloppy-joe의 차단 방법:** 30일 미만 전에 생성된 모든 패키지를 플래그합니다.```
ERROR sketchy-lib [metadata/new-package]
'sketchy-lib' was first published 2 days ago.
New packages are higher risk.
Fix: Verify 'sketchy-lib' at its registry page and source repository.
공격 방식: 다운로드 수가 12개인 패키지가 requests와 한 글자 차이인 경우. 거의 확실히 타이포스쿼팅입니다.
sloppy-joe가 차단하는 방법: 레지스트리에서 다운로드 데이터를 제공하는 경우(현재 npm, crates.io, RubyGems) 다운로드 수가 100개 미만인 패키지를 플래그합니다.``` ERROR requsets [metadata/low-downloads] 'requsets' has only 12 downloads. Fix: Verify 'requsets' is the package you intend to use.
---
## 지원되는 생태계
| 생태계 | 매니페스트 | Lockfile 정책 | 존재 여부 | 메타데이터 | 연령 게이트 |
|-----------|----------|-----------------|:---------:|:--------:|:--------:|
| npm | package.json | `package-lock.json` 또는 `npm-shrinkwrap.json` 필수 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| PyPI | `pyproject.toml`, `requirements*.txt`, `Pipfile`, `setup.cfg`, `setup.py` | Poetry는 `poetry.lock`으로 신뢰되고, uv는 `uv.lock`으로 신뢰되며, 완전히 해시 잠긴 pip-tools는 커밋된 requirements 그래프가 `--index-url`과 정확히 허용된 `--extra-index-url` 값을 바인딩할 때만 신뢰되며, 리포지토리에서 볼 수 있는 Poetry/uv 사용자 정의 인덱스는 정확한 `trusted_indexes.pypi` 허용 목록에 의해서만 신뢰될 수 있습니다. 레거시 매니페스트는 `python_enforcement`가 `poetry_only`인 경우를 제외하고 매 실행마다 경고합니다. | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Cargo | Cargo.toml | `Cargo.lock` 필수 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Go | go.mod | `go.sum`은 외부 종속성에 필수; stdlib 전용 또는 전체 로컬 `replace`에는 필요하지 않음 | :white_check_mark: | :x: | :x: |
| Ruby | Gemfile | `Gemfile.lock` 필수 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| PHP | composer.json | `composer.lock` 필수 | :white_check_mark: | :x: | :x: |
| JVM (Gradle) | build.gradle / build.gradle.kts | `gradle.lockfile` 필수 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| JVM (Maven) | pom.xml | 경고 전용: 엄격한 lockfile 적용 없음 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| .NET | *.csproj | `packages.lock.json` 필수 | :white_check_mark: | :x: | :x: |
모든 생태계는 존재 여부, 유사성, 표준 검사를 받습니다. 메타데이터와 연령 게이트는 레지스트리 API가 노출하는 항목에 따라 달라집니다. Lockfile 지원을 통해 생태계가 신뢰할 수 있는 프로젝트 로컬 lockfile 모델을 제공하는 경우 전이적 종속성 스캐닝과 정확한 버전 해결이 가능합니다.
## 빠른 시작```bash
# Install
cargo install sloppy-joe
# Check current project (auto-detects ecosystem)
sloppy-joe check
# Check with canonical enforcement and age gate
sloppy-joe check --config /etc/sloppy-joe/config.json
# Output as JSON for CI
sloppy-joe check --json
| 코드 | 의미 |
|---|---|
0 | 모든 검사 통과 |
1 | 문제 발견 |
2 | 런타임 오류 |
{ "canonical": { "npm": { "lodash": ["underscore", "ramda", "lazy.js"], "dayjs": ["moment", "luxon"], "axios": ["request", "got", "node-fetch", "superagent"] }, "pypi": { "httpx": ["urllib3", "requests"], "ruff": ["flake8", "pylint"] } }, "internal": { "go": ["github.com/yourorg/"], "npm": ["@yourorg/"] }, "allowed": { "npm": ["some-vetted-external-pkg"] }, "similarity_exceptions": { "cargo": [ { "package": "serde_json", "candidate": "serde", "generator": "segment-overlap" } ] }, "metadata_exceptions": { "cargo": [ { "package": "colored", "check": "metadata/maintainer-change", "version": "2.2.0", "previous_publisher": "kurtlawrence", "current_publisher": "hwittenborn" } ] }, "min_version_age_hours": 72, "allow_legacy_npm_v1_lockfile": false, "python_enforcement": "prefer_poetry" }
**`canonical`** — 키는 승인된 패키지, 값은 거부된 대안입니다.
**`internal`** — 조직의 패키지입니다. 모든 검사를 건너뜁니다. 이들은 지속적으로 변경됩니다.
**`allowed`** — 검증된 외부 패키지입니다. 존재 여부 및 유사성 검사를 건너뛰지만, 여전히 버전 기간 게이트의 적용을 받습니다.
**`similarity_exceptions`** — 검토된 유사성 오탐지에 대한 정확한 패키지/후보/생성기 억제입니다. 특정 유사성 에지가 잘못되었지만 패키지에 대한 정상 검사를 원할 때 사용합니다.
**`metadata_exceptions`** — 검토된 메타데이터 억제입니다. 현재는 `metadata/maintainer-change`만 지원하며, 정확한 패키지/버전/이전 게시자/현재 게시자 일치가 필요합니다.
유지자 변경 차단을 검토해야 할 때 `sloppy-joe check --review-exceptions`를 사용하세요. 스캔은 여전히 정상적으로 차단되지만, 사람이 읽을 수 있는 출력에는 소유자, 저장소 URL, 복사하여 붙여넣을 수 있는 `metadata_exceptions` 스니펫이 포함된 `REVIEW EXCEPTIONS` 섹션이 추가됩니다. `--json`은 동일한 데이터를 최상위 `review_candidates` 필드에 포함합니다.
**`min_version_age_hours`** — 이 시간보다 전에 게시된 버전을 차단합니다. 기본값: 72(3일). 비활성화하려면 0으로 설정하세요. 내부 패키지는 면제됩니다.
**`allow_legacy_npm_v1_lockfile`** — 축소된 신뢰 모드에서 npm v5/v6의 `lockfileVersion: 1` npm 잠금 파일을 허용합니다. 기본값: `false`. 레거시 npm에 의도적으로 고정되어 있고 큰 경고와 축소된 신뢰할 수 있는 npm 전이 적용 범위를 수용하지 않는 한 이 옵션을 끄십시오.
**`python_enforcement`** — Python 신뢰 정책을 제어합니다. `prefer_poetry`(기본값)는 Poetry 프로젝트와 uv 프로젝트를 신뢰하며, 커밋된 요구사항 그래프가 `--index-url` 및 모든 비PyPI `--extra-index-url` 값을 정확히 바인딩하는 경우에만 완전 해시 잠금된 pip-tools 요구사항을 신뢰하고, 그렇지 않으면 pip-tools를 축소된 신뢰로 강등합니다. 해시되지 않은 `requirements*.txt`, `Pipfile`, `setup.cfg`, `setup.py`, Poetry/uv가 아닌 `pyproject.toml`과 같은 레거시 매니페스트는 실행할 때마다 경고합니다. `poetry_only`는 이러한 비Poetry Python 워크플로를 차단하고 Poetry를 요구합니다.
### 구성 보안
구성은 **프로젝트 디렉토리에서 절대 읽지 않습니다**. 셸 접근 권한이 있는 AI 에이전트가 저장소 내 구성을 다시 작성하여 원하는 모든 것을 허용 목록에 추가할 수 있습니다.
구성 해결:
1. `--config /path/to/config.json` — 로컬 파일(CLI 플래그, 최고 우선순위)
2. `--config https://example.com/config.json` — URL에서 가져오기
3. `SLOPPY_JOE_CONFIG=...` — 환경 변수(파일 경로 또는 URL)
4. 구성 없음 = 존재 여부 + 유사성 + 메타데이터 검사만 수행
잘못된 구성은 실행 가능한 오류 메시지와 함께 **하드 실패**합니다 — 손상된 구성이 조용히 보호 없음으로 대체되지 않습니다.
전체 형식 참조, CI 통합 패턴 및 예제는 [CONFIG.md](https://github.com/brennhill/sloppy-joe/blob/main/CONFIG.md)를 참조하세요.
부트스트랩 구성:```bash
sloppy-joe init --greenfield --ecosystem npm
sloppy-joe init --from-current
sloppy-joe init --from-current --register
sloppy-joe init --register
CI 파이프라인에 sloppy-joe를 추가하는 가장 빠른 방법 — GitHub Releases에서 사전 빌드된 바이너리를 다운로드합니다 (Rust 툴체인 불필요):```yaml
name: Dependency Check on: [push, pull_request]
jobs: sloppy-joe: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: brennhill/[email protected] with: config: https://raw.githubusercontent.com/yourorg/configs/main/sloppy-joe.json
#### 작업 입력
| 입력 | 설명 | 기본값 |
|-------|-------------|---------|
| `config` | 설정 파일 경로 또는 HTTPS URL | *(없음)* |
| `dir` | 스캔할 프로젝트 디렉터리 | `.` |
| `type` | 생태계 (`npm`, `pypi`, `cargo`, `go`, `ruby`, `php`, `jvm`, `dotnet`) | 자동 감지 |
| `deep` | 전이 종속성 유사성 검사 활성화 | `false` |
| `paranoid` | 비트플립 변형 활성화 | `false` |
| `args` | 추가 CLI 인수 | *(없음)* |
| `version` | 설치할 sloppy-joe 버전 | `latest` |
#### 예제```yaml
# Minimal — CI-oriented scan, auto-detect ecosystem, no config
- uses: brennhill/[email protected]
# With org config from a URL
- uses: brennhill/[email protected]
with:
config: https://raw.githubusercontent.com/yourorg/configs/main/sloppy-joe.json
# Deep scan with paranoid mode
- uses: brennhill/[email protected]
with:
config: ${{ secrets.SLOPPY_JOE_CONFIG }}
deep: true
paranoid: true
# Scan a subdirectory, pin to a specific version
- uses: brennhill/[email protected]
with:
dir: ./packages/api
version: '1.1.0'
dependency-guard: script: - cargo install sloppy-joe - sloppy-joe check --ci --config $SLOPPY_JOE_CONFIG
### pre-commit
sloppy-joe는 [pre-commit](https://pre-commit.com) 프레임워크와 함께 작동합니다.
`.pre-commit-config.yaml`에 추가하세요:```yaml
# .pre-commit-config.yaml
repos:
- repo: https://github.com/brennhill/sloppy-joe
rev: v1.1.0
hooks:
- id: sloppy-joe
이 훅은 모든 커밋(및 선택적으로 푸시)에서 sloppy-joe check를 실행합니다.
매니페스트 파일에서 생태계를 자동으로 감지합니다. 추가 인수를 전달
args를 통해:```yaml
- id: sloppy-joe
args: [--config, "https://example.com/config.json"]
또는 프레임워크 없이 간단한 셸 훅을 사용하세요:```bash
#!/bin/sh
sloppy-joe check || exit 1
sloppy-joe는 유사성 탐지를 위해 레지스트리 기반 생성 접근 방식을 사용합니다. 모든 종속성을 정적 코퍼스와 편집 거리로 비교하는 대신(이는 오탐을 발생시킵니다), 각 종속성 이름의 특정 변형을 생성하고, 해당 변형이 존재하는지 레지스트리에 질의한 후 정확히 일치하는 항목을 플래그합니다.``` Pipeline (in order):
유사성 검사는 4단계로 실행됩니다.
- **0단계: 범위 스쿼팅** — 로컬 검사, 네트워크 없음. Levenshtein 거리를 통해 알려진 정상 범위/네임스페이스와 비교합니다.
- **1단계: 매니페스트 내부** — 로컬 검사. 동일한 매니페스트의 두 의존성이 서로 변형(mutation)인 경우 플래그를 지정합니다.
- **2단계: 레지스트리 쿼리** — 변형을 생성하고, 레지스트리에 일괄 쿼리하여 존재 여부를 확인한 후 결과를 캐싱합니다(7일 TTL).
- **3단계: 메타데이터 보강** — 일치 항목에 대한 다운로드 수와 게시 날짜를 가져와 보고서에 증거를 추가합니다.
각 변형 생성기는 출력에 태그를 지정하므로, 보고된 검사 유형(예: `similarity/homoglyph`)은 결정적입니다. 여러 생성기가 동일한 후보를 생성하는 경우 가장 심각도가 높은 생성기가 우선합니다.
## CI 신뢰성
sloppy-joe는 불안정한 실패가 허용되지 않는 CI 파이프라인을 위해 설계되었습니다.
**백오프(backoff)를 통한 재시도.** 모든 레지스트리 HTTP 호출은 일시적 오류(5xx, 타임아웃, 연결 오류) 발생 시 지수 백오프(200ms, 400ms, 800ms)로 3회 재시도합니다. 단 한 번의 네트워크 오류로 빌드가 실패하지 않습니다.
**쿼리 오류 시 장애-폐쇄(fail-closed).** 레지스트리 또는 OSV 쿼리가 실패하면, sloppy-joe는 검사를 조용히 건너뛰는 대신 차단 오류 `registry-unreachable`을 발생시킵니다. 더 이상 검사가 차단되기 전에 생태계별 임계값이나 샘플 크기 제한에 의존하지 않습니다.
**유사성 캐시.** 변형 존재 결과는 7일 동안 캐시됩니다. 첫 번째 검사 이후 대부분의 쿼리는 네트워크 호출 없이 캐시에서 처리됩니다. 새 의존성만 레지스트리 쿼리를 트리거합니다.
**잠금 파일 인식 해결.** 지원되는 잠금 파일이 있고 신뢰할 수 있는 경우(`package-lock.json`, `npm-shrinkwrap.json`, `Cargo.lock`, `Gemfile.lock`, Poetry 프로젝트의 `poetry.lock`, uv 프로젝트의 `uv.lock`, `composer.lock`, `gradle.lockfile`, `packages.lock.json`), sloppy-joe는 범위에서 추측하는 대신 잠금 파일에서 정확한 버전을 확인합니다. 완전 해시 잠금된 `requirements*.txt`도 정확한 고정 버전을 제공할 수 있으며, 커밋된 요구 사항 그래프가 자체 `--index-url`과 정확히 허용된 `--extra-index-url` 값을 바인딩할 때 완전히 신뢰됩니다.
## 테스트
테스트 스위트는 유사성 검사, 메타데이터 신호, OSV 동작, 구성 파싱 및 검증, 잠금 파일 해결, 매니페스트 및 잠금 파일 사전 점검 정책, 보고서 형식화, HTTP 재시도 로직을 다룹니다.```bash
cargo test
| 기능 | sloppy-joe | Socket.dev | cargo-deny | pip-audit | npm audit |
|---|---|---|---|---|---|
| 환각(hallucinated) 패키지 탐지 | ✅ | ❌ | ❌ | ❌ | ❌ |
| 오타 스쿼팅 탐지 | ✅ 11개 생성기 | 부분적 | ❌ | ❌ | ❌ |
| 표준 이름 강제 적용 | ✅ | ❌ | ❌ | ❌ | ❌ |
| 알려진 취약점 스캔 | ✅ OSV 통해 | ✅ | ✅ | ✅ | ✅ |
| 설치 스크립트 분석 | 기본 (플래그 + 저장소 없음) | ✅ 심층 분석 | ❌ | ❌ | ❌ |
| 라이선스 준수 | 범위 외: 준수, 보안 아님 | ✅ | ✅ 훌륭함 | 범위 외: 준수, 보안 아님 | 범위 외: 준수, 보안 아님 |
| 다중 생태계 | 8개 생태계 | npm, PyPI, Go, Ruby, Java, .NET | Rust 전용 | Python 전용 | npm 전용 |
| AI 에이전트 안전성 (저장소 외부 설정) | ✅ | ❌ | ❌ | ❌ | ❌ |
| 오프라인/CI 친화적 | ✅ 어디서든 실행 | Socket 플랫폼 필요 | ✅ | ✅ | ✅ |
| 무료 / 오픈 소스 | Apache 2.0 | 무료 티어 + 유료 | Apache 2.0 | Apache 2.0 | 내장 |
타 도구가 더 강한 부분: Socket.dev는 sloppy-joe의 플래그 기반 접근 방식을 훨씬 뛰어넘는 행동 탐지를 통한 심층 설치 스크립트 분석을 수행합니다. cargo-deny는 최고 수준의 라이선스 준수 확인 기능을 제공하지만, 이는 의도적으로 sloppy-joe의 범위 밖입니다. 라이선스 정책은 종속성 보안 제어가 아니라 준수 문제이기 때문입니다. npm audit과 pip-audit은 단일 생태계 취약점 스캔을 위한 설치 없는(zero-install) 옵션입니다.
sloppy-joe의 차별점: 패키지가 실제로 레지스트리에 존재하는지 확인(AI 환각 탐지), 거의 오탐 없이 11개의 오타 스쿼팅 생성기 실행, 표준 패키지 선택 강제, 그리고 AI 에이전트가 자체 검사를 약화시킬 수 없도록 설정을 저장소 외부에 유지하는 유일한 도구입니다.
Apache 2.0
| ❌ |
| ❌ |
| npm | ✅ | ✅ | ✅ | ✅ | ❌ |
| PyPI | ✅ | ✅ | ✅ | ✅ | ✅ |
| Cargo | ✅ | ✅ | ❌ | ✅ | ❌ |
| Go | ✅ | ✅ | ✅ | ❌ | ❌ |
| Ruby | ✅ | ✅ | ✅ | ❌ | ❌ |
| PHP | ✅ | 🔶 | ❌ | ❌ | ❌ |
| JVM (Gradle/Maven) | ✅ | ✅ | ❌ | ❌ | ❌ |
| .NET (NuGet) | ✅ | ✅ | ❌ | ❌ | ❌ |
| 단일 바이너리 | ✅ | ❌ | ❌ | ❌ | ❌ |
| 오픈소스 | Apache 2.0 | 상용 | Apache 2.0 | MIT | OSS |
| 언어 | Rust | SaaS | Python | Python | Python |