Compare commits

...

702 Commits

Author SHA1 Message Date
Abhisek Datta
6acf08aec0
feat: Exclude Fork and Archive during GitHub Org Scan (#650)
* Only scan private repos under org based scan

* Only scan private repos under org based scan

* fix: Style and formatting issues

* Update scan.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

* fix: Add tests for github org reader filters

---------

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: infosecwonderland <monika.talekar@ascenda.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-09 16:44:49 +00:00
Sahil Bansal
33c4ca5059
Fix CycloneDX SBOM validation (#647)
* fix component duplication & license format

* fix tests

* use bool type for consistency
2025-12-04 09:34:59 +05:30
Sahil Bansal
a430641960
Add bun.lock parser support (#636)
* add bun.lock file parser support

* fix linter and update ListParsers test case

* fix linter
2025-12-01 12:56:48 +05:30
Kunal Singh
76788aa1cb
fix: json matching for mcp publish verify (#645) 2025-12-01 10:46:35 +05:30
Kunal Singh
dbc2eadac3
fix: verify mcp publish job failing (#642)
* fix: verify mcp publish failing

* added comments

* fix typo
2025-11-27 11:21:38 +00:00
Kunal Singh
ecf8c93f3f
Merge pull request #644 from safedep/chore/precommit-fmt-code
chore: Add golangci fmt for pre-commit
2025-11-27 15:11:34 +05:30
abhisek
070c5f5a24
chore: Add golangci fmt for pre-commit 2025-11-27 15:00:10 +05:30
Oleksandr Redko
4e39cebe61
chore: add formatters to golangci-lint config (#643)
Signed-off-by: Oleksandr Redko <oleksandr.red+github@gmail.com>
2025-11-27 14:58:24 +05:30
Kunal Singh
767e7cb16e
fix: OCI packages must not have 'version' field (#641) 2025-11-26 17:28:12 +05:30
Kunal Singh
4da939276e
fix: description validation error, mcp publishing (#640) 2025-11-26 16:32:38 +05:30
Kunal Singh
dc5846fb93
Merge pull request #639 from safedep/fix-mcp-ci-wd
fix: wrong working directory construction in mcp publishing CI
2025-11-26 14:44:35 +05:30
Kunal Singh
18a996dd53 relative path 2025-11-26 14:38:06 +05:30
Kunal Singh
333729a032 fix: path 2025-11-26 14:36:09 +05:30
Kunal Singh
0a67953b3f checkout before anyting 2025-11-26 14:33:02 +05:30
Kunal Singh
3316c81f35 fix: wrong working directory construction 2025-11-26 14:27:41 +05:30
Kunal Singh
51e185a09f
Merge pull request #638 from safedep/fix-mcp-ci
fix: working directory to .mcp-publisher
2025-11-26 14:08:34 +05:30
Kunal Singh
4599bacf17 fix: working directory to .mcp-publisher 2025-11-26 14:01:46 +05:30
Kunal Singh
e971466097
feat: vet-mcp registry publishing (#635)
* feat: ver-mcp registry publishing

* Update .github/workflows/publish-mcp.yml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>

* feat: verify job for mcp server publish

* feat: refactor server.json location

* fix: cd to .mcp-publisher dir

* update logo urls

* refactor: publish to mcp when container is build

---------

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 13:20:34 +05:30
Kunal Singh
65c44c97c1
Merge pull request #634 from safedep/vet-mcp-cursor-download-link-fix
Update cursor installation link for MCP Server
2025-11-24 13:25:39 +05:30
Kunal Singh
1c824c5515
Update cursor installation link for MCP Server
Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
2025-11-24 13:04:00 +05:30
Abhisek Datta
e1908e783b
chore: Update SafeDep Cloud URLs (#632)
* fix: SafeDep cloud URLs

* fix: Markdown formatting issue
2025-11-10 06:45:13 +00:00
Kunal Singh
6f5d0cea33
fix: github action versions not pinned and top level permissions missing (#631) 2025-10-25 16:45:58 +05:30
Sahil Bansal
dc3bc11a51
add git in vet image (#629) 2025-10-17 14:35:23 +05:30
Sahil Bansal
9aebb9d68e
fix: Handle Multiple License Type in npm Graph Parser (#625)
* do not fail on non string values for npm graph license field

* add non-string license type package for testing

* fix: Handle multiple cases for npm package-json license

---------

Co-authored-by: abhisek <abhisek.datta@gmail.com>
2025-10-15 09:30:21 +05:30
Abhisek Datta
801e81ac0d
fix: Use standard colors for inspect malware command (#624) 2025-10-13 04:20:06 +00:00
Kunal Singh
aa06dc6d93
feat: using branch color in manual (#623) 2025-10-13 09:06:01 +05:30
Kunal Singh
8e02db1d09
Merge pull request #622 from safedep/refactor/remove-local-manaul-gen
refactor: remove locally generated manual flow
2025-10-11 10:15:41 +05:30
Kunal Singh
6ad8539938 generate vet cli manual in ./docs/manual dir 2025-10-10 22:28:48 +05:30
Kunal Singh
6ade12a5f6 refactor: remove locally generated manual flow 2025-10-10 22:19:26 +05:30
Kunal Singh
521a5a5756
Merge pull request #621 from safedep/fix/gh-pages-ci
fix: github pages CI build jekyll and deploy to new service
2025-10-10 21:53:48 +05:30
Kunal Singh
54bade56e9 fix: build only after safe generation 2025-10-10 21:45:49 +05:30
Kunal Singh
1aecf640aa fix: typo 2025-10-10 21:43:13 +05:30
Kunal Singh
b1738ac35a fix: github pages CI build jekyll and deploy to new service 2025-10-10 21:40:36 +05:30
Kunal Singh
5663b59636
Merge pull request #620 from safedep/fix/gh-pages-ci-ignore-pattern-dir
fix: added ignore pattern for dynamic lines depending on current work…
2025-10-10 21:18:49 +05:30
Kunal Singh
dc14669605 fix: added ignore pattern for dynamic lines depending on current working directory 2025-10-10 21:08:34 +05:30
Kunal Singh
a8cd3a5b08
feat: github pages cli reference manual deploy (#619)
* feat: github pages cli reference manual deploy

* fix: typos

* feat: simplified logic, use gen folder

* fix: comments, typos, grammar

* update: manual home page

* fix: using generated files in ./docs/manual and not in subdir

* update: manual index (home) with vet.html link - more explicit

* refactor: copy changes
2025-10-10 18:56:31 +05:30
Kunal Singh
2d74224fd3
feat: added internal doc generate command (#618)
* feat: added internal doc generate command

* make generate and go mod tidy
2025-10-09 15:51:15 +05:30
Kunal Singh
b4aaf026c9
fix: recommended way os using golangci lint (#615)
* fix: recommended way os using golangci lint

* make generate

---------

Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2025-10-06 15:31:02 +00:00
Kunal Singh
e48452dd05
feat: show manifest relative path in summary report (#613)
* feat: show manifest relative path in summary report

* fix: typos

* fix: using standard color profile

* fix: log message

---------

Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2025-10-06 20:53:04 +05:30
Abhisek Datta
9f55120d2d
fix: Console Color Profiles (#610)
* fix: Color profile for console

* fix: Console color profile for dark mode

* fix: Simplify color profile identification using charm

* docs: Add trade-off comments
2025-10-06 10:22:48 +05:30
Kunal Singh
f88f76a1c5
Merge pull request #611 from safedep/container-e2e
feat: added vet scan container e2e
2025-10-04 13:21:29 +05:30
Kunal Singh
08e8915242 feat: added vet scan container e2e 2025-10-04 13:13:31 +05:30
Copilot
d5a64a4f61
Remove Markdown Builder in Favor of DRY Markdown Builder (#608)
* Initial plan

* Replace local markdown package with dry library version

- Updated dry dependency to v0.0.0-20250916040320-209e39edc57f (latest)
- Replaced imports from pkg/reporter/markdown to dry/reporting/markdown
- Removed local pkg/reporter/markdown directory
- All tests passing, build successful

Co-authored-by: abhisek <31844+abhisek@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: abhisek <31844+abhisek@users.noreply.github.com>
Co-authored-by: Kunal Singh <kunalsin9h@gmail.com>
2025-10-02 23:09:48 +05:30
Copilot
8671c25fb5
Fix #606: Refactor NewEvaluator to use Option pattern and add defensive nil checks (#607)
* Initial plan

* Refactor NewEvaluator to use Option pattern and add defensive nil checks

- Implemented Option function type for configuring evaluator
- Added WithIgnoreError option function
- Updated NewEvaluator to accept only name parameter and variadic options
- Updated all usages in pkg/analyzer/filterv2/ and pkg/analyzer/ directories
- Added defensive nil checks for enum constants in EvaluatePackage
- Added comprehensive tests for Option pattern
- All tests pass successfully

Co-authored-by: abhisek <31844+abhisek@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: abhisek <31844+abhisek@users.noreply.github.com>
2025-10-02 12:28:25 +05:30
Abhisek Datta
3eb501c72b
feat: Add Support for Filter v2 - Spec Based Policy Engine (#560)
* feat: Add support for filter v2

* Add filter v2 support

* Add test for filter v2 evaluator

* fix: CEL v2 query engine

* chore: Add sample policy-v2

* chore: Remove deprecated API use

* fix: Remove deprecated API

* fix: Misc linter fixes

* fix: Linter fixes

* fix: Policy v2 sample

* refactor: Extract filter match rendering into common concern

* test: Update test case

* fix: JSON round trip problem by using PB messages for filter eval

* Improve DX by declaring enum constants in CEL env

* fix: Code generator for enum type names

* docs: Add policy dev enumgen docs

* chore: Add example policy

* test: Add test for CEL filter suite v2 analyser

* Code review fixes

* fix: Code review comments

* chore: Add CI check to ensure generated code is updated

* fix: Add nil guards during init
2025-10-02 09:01:50 +05:30
Kunal Singh
498f809e94
Merge pull request #605 from safedep/test/add-test-cases-for-relative-exclusion-matcher
test: Add test case for relative exclusion matcher
2025-10-01 17:10:04 +05:30
abhisek
c2cb4375f8
test: Add test case for relative exclusion matcher 2025-10-01 13:51:58 +05:30
Sahil Bansal
7f8b335393
update docs (#604) 2025-09-25 15:48:56 +05:30
Sahil Bansal
969404956c
skips analytics init for root or help cmd/flags (#603)
* skips analytics init for root or help cmd/flags

* fix analytics init only on non-help cmds
2025-09-24 19:23:54 +05:30
Sahil Bansal
e9d3da03db
Fix Attest build provenance failing (#602)
* upload dist folder to make it accessible in provenance job

* pin commit sha's for uploading & downloading artifacts
2025-09-22 15:29:21 +05:30
Abhisek Datta
ec792952f8
fix/dir reader log path failure (#599)
* fix: Avoid hard failure in dir scanner on path errors

* fix: Ensure dir reader fails if root path doesn't exist
2025-09-21 15:59:45 +00:00
Copilot
d94a05844e
Add custom reference URL and version format control for OSV malware reports (#598)
* Initial plan

* Implement OSV report improvements with custom reference URLs and version format control

- Add --report-reference-url flag for custom reference URLs instead of default platform.safedep.io
- Add --range flag to control version representation:
  - With --range: use range-based versioning (SEMVER/ECOSYSTEM)
  - Without --range: use explicit versions array (new default)
- Update OSV report generation to support both modes
- Add comprehensive tests for new functionality
- Backward compatibility maintained for existing functionality

Co-authored-by: KunalSin9h <82411321+KunalSin9h@users.noreply.github.com>

* fix: custom reference url and added docs

* fix: docs table layout

* fix: test

* refactor: remove unnecessary else statement

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: KunalSin9h <82411321+KunalSin9h@users.noreply.github.com>
Co-authored-by: Kunal Singh <kunalsin9h@gmail.com>
2025-09-17 16:09:32 +05:30
Sahil Bansal
c449d94dad
pin building version to sha (#595) 2025-09-13 18:53:52 +05:30
Arunanshu Biswas
c0144e60e3
feat: update go version to latest (#589)
* feat: update go version to latest

* update workflow

* use bookworm

* revert fixture

* fix dockerfile run

* use more modern debian image

---------

Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2025-09-13 10:25:25 +05:30
Kunal Singh
d3c87b6e06
fix: scalibr issue when runningSystem is true (#591) 2025-09-08 16:56:56 +05:30
Arunanshu Biswas
0ae3560ba1
fix: mcp SSE hardening (#587) 2025-09-04 06:37:59 +05:30
Kunal Singh
e4e9dc2590
Merge pull request #586 from safedep/fix/npm-publish-use-head-branch-tag
use head_branch for extracting tag
2025-08-27 18:21:55 +05:30
Sahilb315
f2a4722d04 use head_branch for extracting tag 2025-08-27 18:03:04 +05:30
Sahil Bansal
850f2c1dc9
fix syntax issue (#585) 2025-08-27 16:06:29 +05:30
Sahil Bansal
6dcbd15923
fix npm publish workflow to execute after goreleaser completion (#581)
Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2025-08-27 08:06:00 +00:00
Kyle Kelly
82505f2460
Refactor goreleaser workflow to use attest-build-provenance (#584) 2025-08-27 07:16:50 +05:30
Kunal Singh
1afeb397f7
fix: extra space in ascii art (#582) 2025-08-26 09:10:45 +05:30
Sahil Bansal
5844d4ffd1
add support for generating sbom for homebrew installed packages (#571)
* add support for generating sbom for homebrew installed packages

* add brew test cases

* Update scan.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>

* minor error improvements

* add brew ecosystem

* maintain consistency in ecosystem name

* rename `brew` flag to `homebrew`

---------

Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-25 12:00:02 +05:30
Kunal Singh
651b09b085
Merge pull request #579 from safedep/readme-gitlab-fix
Change vet CI Component path in README
2025-08-23 17:45:56 +05:30
Kunal Singh
118210c7c7
Merge branch 'main' into readme-gitlab-fix 2025-08-23 17:40:28 +05:30
Copilot
269b843bb7
Improve MCP vulnerability API performance by using dedicated endpoint (#574)
* Initial plan

* Update dependencies and plan Get Vulnerabilities Tool implementation

Co-authored-by: arunanshub <48434243+arunanshub@users.noreply.github.com>

* Add GetPackageVersionVulnerabilitiesOnly driver method and comprehensive tests

Co-authored-by: arunanshub <48434243+arunanshub@users.noreply.github.com>

* Complete implementation by registering new vulnerability tool in MCP server

Co-authored-by: arunanshub <48434243+arunanshub@users.noreply.github.com>

* Address feedback: Remove separate vulnerability tool and use dedicated API in existing method

Co-authored-by: abhisek <31844+abhisek@users.noreply.github.com>

* Fix scanner mock interface after protobuf dependency update

Co-authored-by: arunanshub <48434243+arunanshub@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: arunanshub <48434243+arunanshub@users.noreply.github.com>
Co-authored-by: abhisek <31844+abhisek@users.noreply.github.com>
Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2025-08-23 12:08:59 +00:00
Kunal Singh
68609f7397
Change vet CI Component path in README
Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
2025-08-23 17:37:01 +05:30
Kunal Singh
d0d21e6710
feat: updated vet in action demo gif (#578)
* feat: updated vet in action demo gif

* fix: replaced old gif

---------

Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2025-08-23 12:03:09 +00:00
Kunal Singh
319cfdac5e
Added screenshot image to top in readme (#577)
* Added screenshort image to top in readme

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>

* added local file

---------

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
2025-08-23 17:27:58 +05:30
Kunal Singh
458391e6f0
fix: progress bar overlaping other previous tables etc (#576) 2025-08-22 17:35:08 +05:30
Kunal Singh
c8ad28bf1a
Merge pull request #572 from safedep/new-vet-banner
feat: new vet ascii banner
2025-08-21 22:10:44 +05:30
Kunal Singh
3791b9555c fix: check for commit lenght 2025-08-21 21:53:19 +05:30
Kunal Singh
30a7b484d1 feat: new vet ascii banner 2025-08-21 21:51:58 +05:30
Copilot
41684afa80
Fix OSV schema for PyPI ecosystem: use proper case "PyPI" and ECOSYSTEM range type (#570)
* Initial plan

* Fix OSV schema for PyPI ecosystem - use proper case and ECOSYSTEM range type

Co-authored-by: KunalSin9h <82411321+KunalSin9h@users.noreply.github.com>

* Add clarifying comments and rename ecosystem mapping variables for better intention revealing

Co-authored-by: abhisek <31844+abhisek@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: KunalSin9h <82411321+KunalSin9h@users.noreply.github.com>
Co-authored-by: abhisek <31844+abhisek@users.noreply.github.com>
2025-08-21 19:08:38 +05:30
Sahil Bansal
2d06114eb7
add cloud session refresh using refresh token functionality (#565)
* add cloud session refresh using refresh token functionality

* add ui msg & update globalConfig to fallback to default when nil

* rm unnecessary comment

* refactor access token checks and error handling for cloud session refresh

* print error to user for automatic re-login

---------

Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2025-08-19 06:26:24 +00:00
Sahil Bansal
52aa033fe4
add IsSuspicious value for reporting suspicious packages too (#567) 2025-08-18 17:56:24 +05:30
Kunal Singh
d8b83e2bc2
Merge pull request #566 from safedep/chore/misc-cleanup-20250815
chore: Misc cleanup and test improvements
2025-08-18 09:52:04 +05:30
Kunal Singh
b9ebcc71da
Merge branch 'main' into chore/misc-cleanup-20250815 2025-08-18 09:33:52 +05:30
Sahil Bansal
0f4c01b83a
add html reporter & create template for report (#559)
* add html reporter & create template for report

* updated table colors

* chore: rm unused code block

* add policy violations

* chore: rm extra var

* Update pkg/reporter/templates/report.templ

Co-authored-by: Kunal Singh <kunalsin9h@gmail.com>
Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>

* chore: rm extra file

* chore: rm unsued css property

* add html reporter & create template for report

* updated table colors

* chore: rm unused code block

* add policy violations

* chore: rm extra var

* Update pkg/reporter/templates/report.templ

Co-authored-by: Kunal Singh <kunalsin9h@gmail.com>
Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>

* chore: rm extra file

* chore: rm unsued css property

* return error when failing to create html reporter

---------

Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>
Co-authored-by: Kunal Singh <kunalsin9h@gmail.com>
2025-08-18 09:33:18 +05:30
abhisek
7cb923b2fd
fix: safely handle global config in test runner 2025-08-15 21:28:23 +05:30
abhisek
e32784a09e
fix: Test to use t.Setenv instead of os 2025-08-15 20:36:20 +05:30
abhisek
db6832e782
chore: Misc cleanup and test improvements 2025-08-15 20:26:55 +05:30
Kunal Singh
4b80c4a624
Fix: truffle hog, invalid commit hash. (#564)
Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
2025-08-13 08:49:43 +00:00
Sahil Bansal
12785f9c05
add support for publishing vet to npm (#563)
* add support for publishing vet to npm

* Update .github/workflows/publish-npm.yml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>

* update npm package readme

---------

Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-12 22:02:33 +05:30
Sahil Bansal
7d4569fb3d
use tag version instead of commit hash (#557) 2025-08-11 12:14:32 +00:00
Teja Kummarikuntla
47939fafaf
fix(report): Add commas between tags in generated markdown (#553)
* Seperate Tags with Comma in Report

* Delete pkg/.DS_Store

Signed-off-by: Teja Kummarikuntla <34749692+tejakummarikuntla@users.noreply.github.com>

* Update pkg/reporter/markdown.go

Co-authored-by: Kunal Singh <kunalsin9h@gmail.com>
Signed-off-by: Teja Kummarikuntla <34749692+tejakummarikuntla@users.noreply.github.com>

---------

Signed-off-by: Teja Kummarikuntla <34749692+tejakummarikuntla@users.noreply.github.com>
Co-authored-by: Kunal Singh <kunalsin9h@gmail.com>
2025-07-31 21:23:10 +05:30
Sahil Bansal
e68ead129b
add support for extensions purl (#551)
* add support for extensions purl

* Update pkg/common/purl/purl_test.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>

---------

Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-30 08:15:19 +05:30
Rohan Mishra
742365bc18
fix(tools): prevent nil pointer panic in GetPackageLicenseInfo handler (#548)
Signed-off-by: Rohan <315scisyb2020rohanmishra@gmail.com>
2025-07-27 18:51:59 +05:30
Kunal Singh
dee54e5184
Merge pull request #546 from safedep/deepwiki
added ask deepwiki badge.
2025-07-24 10:09:58 +05:30
Kunal Singh
1e84769891
added ask deepwiki badge.
Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
2025-07-23 21:42:10 +05:30
Kunal Singh
16a67216b4
Merge pull request #484 from safedep/fix/policy-violation-suspicious-#483
Fix/policy violation on  suspicious packages without paranoid mode #483
2025-07-22 10:27:18 +05:30
Abhisek Datta
0b4e76d858
Merge branch 'main' into fix/policy-violation-suspicious-#483 2025-07-22 10:11:36 +05:30
Abhisek Datta
ccd2c48e0c
fix: Misc cleanup of exclusion matcher initialization (#545) 2025-07-22 08:55:34 +05:30
Sahil Bansal
150cad94a6
Support exclusion patterns for lockfiles flag (#543)
* introduce config for lockfile reader

* add exclusion support

* add test cases for exclusion patterns

* refactor: introduce common exclusion matcher and update lockfile reader to use it

* chore: rm print statements

* refactor: use better naming for tests

* use doublestar lib for supporting dir reader exclusion patterns

* fix: path handling in exclusion matcher to support relative & absolute paths
2025-07-22 08:37:41 +05:30
Kunal Singh
c488d980cc fix: fail fast only on malware 2025-07-21 13:45:22 +05:30
Kunal Singh
3d8b7c5b63 feat: warning in markdown summary report for suspicious packages 2025-07-21 13:16:19 +05:30
Abhisek Datta
b4976630da
Merge branch 'main' into fix/policy-violation-suspicious-#483 2025-07-21 09:14:36 +05:30
Sahil Bansal
3d6d8ed036
Add github actions sync resolver (#539)
* feat: add GHA env resolver

* refactor: expose sync reporter resolver constructors

* fix: use os.LookupEnv for better GHA detection

* fix typo

* use environment sync resolver

* test: add test cases & fix naming

* Update pkg/reporter/sync_test.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>

* modify sync resolver tests

* fix tests failing in ci/cd

---------

Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-21 09:11:24 +05:30
Sahil Bansal
075627f53f
Add test cases for editor based extensions scanning (#542)
* fail fast if distribution is not among our supported editors

* tests: add test cases for each supported editor

* rm unused extensions
2025-07-19 00:17:47 +05:30
Abhisek Datta
1e2b75fa9c
Merge branch 'main' into fix/policy-violation-suspicious-#483 2025-07-17 09:29:18 +05:30
Sahil Bansal
06988f9b33
OpenVSX extensions scanning support (#536)
* feat(readers): Add OpenVSX ecosystem support

* refactor: use better naming conventions

* refactor: improve extensions reader with structured config
2025-07-15 18:40:02 +05:30
Abhisek Datta
c3d96dbef5
fix: Improve Agentic Query Prompt and Tools (#538) 2025-07-14 22:46:37 +05:30
Abhisek Datta
5f4cccbc85
feat: Add Support for Agentic Query and Analysis (#535)
* Add initial UI for agent mode

* fix: Cleanup and define agent contract

* Add react agent

* Add interactions memory

* Add support for stdio based MCP integration

* Add basic sqlite3 report generator

* fix: Persist vulnerabilities with package relation

* fix: Persist license information

* refactor: Agents into its own command package

* feat: Add support for tool calling introspection

* refactor: UI to hide implementation detail

* sqlite3 reporter persist dependency graph

* fix: Support multiple LLM provider for agent

* docs: Update agents doc

* docs: Remove deprecated query docs

* fix: UI tests

* fix: Linter issue

* Add support for prompt mode

* Improve UI with animation

* Fix UI tests after update

* Add OpenSSF scorecard persistence

* Add slsa provenances in sqlite3 reporter

* Add test cases for sqlite3 reporter

* Fix agent doc

* fix: Sqlite3 reporter use safe accessors

* feat: Add support for fast model

* feat: Simplify and streamline agent UI for better user experience

- Remove decorative borders and excessive styling to maximize output area
- Implement clean minimal design similar to modern TUI interfaces
- Add bordered input area for clear visual separation
- Move thinking indicator above input area for better visibility
- Enhance input field reset logic for proper line alignment
- Remove verbose help text and status messages
- Optimize layout calculations for full width utilization
- Add smooth animations for agent thinking state with spinner
- Clean up code structure and remove unused progress bar functionality

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Improve agent status line

* test: Update UI tests

* fix: Use terminal safe rendering

* fix: Fix nil deref without storing empty strings in DB

* fix: Support overwriting sqlite3 database

* fix: Data model to use m2m between manifest and package

* style: Fix linter issue with unused variables

* Misc fixes

* Add test for agent memory

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-11 18:37:44 +05:30
Copilot
cd7caffb4a
Add HTTP HEAD request support to SSE MCP server (#533)
* Initial plan

* Add HTTP HEAD request support to SSE MCP server

- Created sseHandlerWithHeadSupport wrapper to handle HEAD requests to /sse endpoint
- HEAD requests return same headers as GET (text/event-stream, no-cache, etc.) without body
- Modified NewMcpServerWithSseTransport to use the wrapper
- Added comprehensive unit and integration tests
- Updated documentation to mention HEAD support for SSE endpoint
- Enables tools like Langchain to probe endpoint for health/capability checks

Co-authored-by: abhisek <31844+abhisek@users.noreply.github.com>

* Add HTTP HEAD request support to SSE MCP server

Co-authored-by: abhisek <31844+abhisek@users.noreply.github.com>

* Fix linter issues: remove trailing whitespace and handle w.Write error

Co-authored-by: abhisek <31844+abhisek@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: abhisek <31844+abhisek@users.noreply.github.com>
2025-07-05 13:41:37 +00:00
Copilot
548ede77b8
Fix OSV report generation fallback value for 'introduced' version from "0.0.0" to "0" (#532)
* Initial plan

* Fix OSV introduced version fallback from 0.0.0 to 0 per OSV schema

Co-authored-by: abhisek <31844+abhisek@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: abhisek <31844+abhisek@users.noreply.github.com>
2025-07-04 11:47:10 +05:30
Kunal Singh
3fa7307d93
Merge pull request #529 from safedep/chore/sync-reporter-linter-fixes-cleanup
chore: Sync reporter linter fixes
2025-06-30 21:55:53 +05:30
abhisek
5cc80f9f88
chore: Sync reporter linter fixes 2025-06-30 21:46:50 +05:30
Omkar Phansopkar
387f6aeb72
Updated instructions for mcp server setup (#527)
* Updated instructions for mcp server setup

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Updated vscode usage image

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Spell vscode full form

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Added sse instructions

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

---------

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-06-28 18:13:48 +05:30
Omkar Phansopkar
200257bab3
Merge pull request #517 from safedep/chore/updateDeps
Updated deps and minor refactoring
2025-06-24 15:21:47 +05:30
Omkar Phansopkar
a87e6ab466
Updated deps
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-06-24 14:46:14 +05:30
Omkar Phansopkar
a0f6467e85
Merge branch 'main' into chore/updateDeps 2025-06-24 14:43:32 +05:30
Abhisek Datta
78e2bad49b
feat: Malicious Packages (OSV) Reporter for Inspect Command (#518)
* Add osv reporter

* fix: Pass config to openssf report generator

* fix: file name and check if already osv record exists' (#519)

---------

Co-authored-by: Kunal Singh <kunalsin9h@gmail.com>
2025-06-24 09:12:34 +00:00
Omkar Phansopkar
4f989c59f6
Fix e2e: scenario-11-code-csvreport.sh (#522)
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-06-24 14:39:02 +05:30
Omkar Phansopkar
932269d6bb
Updated contributing.md
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-06-19 20:22:04 +05:30
Omkar Phansopkar
7a2a365136
Updated testcase
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-06-14 15:44:29 +05:30
Omkar Phansopkar
459a246488
Updated docker go version
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-06-14 15:26:53 +05:30
Omkar Phansopkar
966971b941
Updated go version in CI workflows
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-06-14 15:16:33 +05:30
Omkar Phansopkar
f9d17487ad
Updated deps and minor refactoring
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-06-14 15:09:11 +05:30
Kunal Singh
8b71c540e6
feat: added cargo.lock scalibr parser (#512)
* feat: added cargo.lock scalibr parser

* fix: invalid manifest ref in cargo

* fix: list parser test - increment to 22

* fix: added cargo in supported ecosystems
2025-06-06 16:43:40 +05:30
Abhisek Datta
cccf646856
chore: Add multi-arch build for docker container (#510)
* chore: Add multi-arch build for docker container

* fix: Multi-platform build verification

* fix: Multi-platform build verification

* fix: MCP server docs

* chore: Add to cursor button for vet MCP Server
2025-06-06 12:59:05 +05:30
Kunal Singh
124199b331
docs(readme): added pkg.go.dev badge. (#509)
Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
2025-06-05 08:51:32 +00:00
Abhisek Datta
5a5a9518c6
feat: Add Support for vet MCP Server (#502)
* fix: MCP server with update mcp-go

* docs: Update MCP usage docs

* docs: Update MCP usage docs

* chore: Update DRY for Go adapter

* test: Add mcp driver test cases

* test: Simplify test cases

* docs: Update README

* docs: Update README

* test: Add test case for mcp tool

* test: Refactor for common concerns

* test: Add tool tests

* docs: Update MCP server docs

---------

Co-authored-by: Kunal Singh <kunalsin9h@gmail.com>
2025-06-05 11:00:43 +05:30
Sahil Bansal
3d94f0f710
Fixes duplicate & incorrect version in requirements.txt & empty Upgrade To Version Suggestion (#401)
* fixes #344

* fixed duplicate & incorrect version for requirements.txt

* fix return err if lfParser.Parse fails

* fix: Update edge cases and add test

---------

Co-authored-by: abhisek <abhisek.datta@gmail.com>
2025-06-04 09:44:08 +05:30
Kunal Singh
4f43177976
fix: pomxml parser not working when renamed files (#505)
* fix: pomxml parser not working when renamed files

* fix: pom.xml parsing with differnet filename

* removed test file'

* remvoed unused code
2025-06-03 13:49:32 +05:30
Kunal Singh
0a2d642ea8
Docs: added instructions for container image scanning & and fix misc (#501)
* fix: scan flgas, report-cdx and report-graph

* added more instructions for container image scanning
2025-05-30 17:40:47 +05:30
Kunal Singh
49cc6ca395
Support for local docker and tar images in container scanning. (#497)
* refactor: convert current image resolution into workflow pattern

* feat: image from local tarball folder

* feat: image from local docker catalog

* refacot: decompose docker image catalog resolver into multiple functions

* refactor: using utilit function for logging and return error

* feat: remove tem tar dir after image obj is created

* refactor: handle empty nil error in log and error funciton

* removed local image not supported test

* fix: error fmt.Errorf with non-constant values

* go mod tidy

* fix: linter unreachable  code

* refactor: removed unwanted parameter

* test: added scenerio for different image scan operations

* fix: added scenario into all.sh

* fix: missed .sh extension for one entry

* fix: test bad file path for local tar scan

* remvoed logger and error combined function

* refactor: using context form top of tree

* feat: using custom error for image resolution unsupported

* refactor: returning unsupported workflow error for each docker api fail

* feat: added no-remote flag for disable remote fetch

* feat: --image-no-remote

* fix: test, creating temp files witn \/ causing issue

* chore: Misc cleanup for Container Image Resolver (#499)

* chore: Misc cleanup

* fix: Bug with docker image resolver

* fix: Error msg

* chore: Improve debug logging for docker enumeration

* chore: Improve debug logging for docker enumeration

---------

Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2025-05-29 22:36:49 +05:30
Abhisek Datta
a2c003f634
docs: Update README (#496) 2025-05-29 00:03:48 +05:30
Abhisek Datta
72e08bdd8a
refactor: Sync reporter to allow env resolver adapter (#495)
* refactor: Sync reporter to allow env resolver adapter

* fix: Set optional params only when not empty

* fix: linter warning
2025-05-27 22:00:50 +05:30
Kunal Singh
1f8a5750d2
feat: container scanning (#489)
* feat: container scanning

* fix: tests

* refactor: added commens and set running-system for remote image to false

* using standalone extractors with all scope

* feat: handle manifects

* using set for duplicate purl

* feat: added initial tests

* refactor: creating manifest from local and modified tests

* refactor: decouple parser and reader for container scanning

* refactor: seperated image and reader config

* refactor: using applicatoin name to purl

* refactor: removed technical osv-scalibr names from error message

* refactor: misc

* added analytics

* moved container image reader test to e2e test

* refactor: getting image in consutrctor

* refactor: composit grouping of ecosystem and file

* feat: cache to reudce duplicate packages

* refactor: removed json-dump-dir output dir

* feat: added ui for better UX

* test: skip when not e2e

* refactor: made scalibr image object reference private

* test: application name testing'

* test, different image with invalid cases

* feat: clean up image after use

* refactor: fetching image in enum manifests

* fix: tests, handing error from enum manifests

* tests: added test for local image not supported

* refactor: removed ui reference in container reader
2025-05-27 20:45:37 +05:30
Kunal Singh
826b8eafdf
Fix/ Bug in Resolving Package Version in Maven POM with Dependency Management (#487)
* feat: pom.xml dependency resolver

* feat: manifest from packages

* feat: disable osv-scalibr's native loggin

* fix: parser test, update total parsers count'

* refactor: using manifest's NewPackageManifestFromLocal and AddPackage methods

* fix: tests, package enumertaion with new pom.xml parser

* fix: docker image with go 1.24.2

* feat(test): pom parser

* refactor: extracted commaon osv-scalibr's function in scalibr.go

* refactor: setting scalibr logger in init function
2025-05-20 18:10:49 +05:30
Omkar Phansopkar
5c7ab43567
Merge pull request #482 from safedep/fix/481-sarif-report-builder
fix: SARIF report builder to handle vuln and malicious code rule index
2025-05-14 12:32:43 +05:30
Kunal Singh
a04cf78657 refactor: simplified 2025-05-09 16:19:41 +05:30
Kunal Singh
9e9abdd162 fix: tests, common pkgManifest causing issues due to multiple packages 2025-05-09 15:58:27 +05:30
Kunal Singh
5840ebd227 test: check if policy is violated 2025-05-09 11:47:37 +05:30
Kunal Singh
f2a2eb0548 fix: unwanted policy violation emmition for suspicious packages 2025-05-08 20:45:55 +05:30
abhisek
ac1c83393f
fix: SARIF report builder to handle vuln and malicious code rule index 2025-05-05 17:55:23 +05:30
Abhisek Datta
a77be8f4c4
feat: Add manifest source in csv report (#480) 2025-04-28 23:11:55 +05:30
infosecwonderland
eebae09e82
feat: add excludeRepos support to GithubOrgReader (#476)
* feat: add excludeRepos support to GithubOrgReader

* feat: add excludeRepos support to GithubOrgReader

* Syntax changes

* fix: excluded repo variable declartion

* fix: scan command to include excluded repo config

---------

Co-authored-by: abhisek <abhisek.datta@gmail.com>
2025-04-28 15:03:54 +05:30
Omkar Phansopkar
e90756d5a3
CDX reporting in readme (#479)
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-04-28 12:00:47 +05:30
Abhisek Datta
c795255e35
docs: Update vet logo (#475)
* docs: Update vet logo

* fix: logo size

* docs: Improve README by using collapsible sections

* docs: Add interactive tutorial

* docs: Update vet logo

* docs: Update README

* docs: Update README

* docs: Update vet logo

* docs: Update README

* fix: Revert star history URL

* fix: README image
2025-04-24 23:56:02 +05:30
Abhisek Datta
36d5c021f3
fix: Handle github reader reading directory (#477) 2025-04-24 22:24:24 +05:30
Abhisek Datta
3490812ed1
chore: Add anonymous telemetry collector (#468)
* chore: Add anonymous telemetry collector

* fix: Posthog property handling
2025-04-22 15:53:32 +05:30
Omkar Phansopkar
5b766bb27b
Merge pull request #465 from safedep/feat/add-query-malysis-enricher
feat/add query malysis enricher
2025-04-21 11:16:40 +05:30
abhisek
0bdbf2da8a
test: Add test case for malware query enricher 2025-04-20 13:30:56 +05:30
abhisek
00eb5c8ec7
feat: Update markdown summary reporter for malware query 2025-04-20 10:45:59 +05:30
abhisek
0a3fc8d428
chore: Update dependencies 2025-04-19 22:15:51 +05:30
abhisek
3d7ea62f61
feat: Add malware query enricher 2025-04-19 22:11:17 +05:30
abhisek
88f5178a05
feat: Add community service API endpoint 2025-04-19 20:49:52 +05:30
Kunal Singh
3d9639d0ef
feat: github repos version resolution (#458)
* feat: github repos version resolution

* fix: malware analysis e2e testing

* fix: e2e testing for malware analysis using vet scan

* revert: e2e testing

* fix(test): remove bad e2e tests commands, scan without version has not effect

* fix: test failing 🔥, for scoped packages

* feat(e2e): added inspect malware command

* fix: use of internal code to library

* refactor: Maintain separation of concerns and loose coupling

* fix: PURL reader test

---------

Co-authored-by: abhisek <abhisek.datta@gmail.com>
2025-04-12 08:05:01 +05:30
Nilanjan De
03e1a10c1d
chore: correct typos (#460) 2025-04-11 21:19:02 +00:00
Abhisek Datta
7f88f83a8c
ci: Fix codecov upload step (#457) 2025-04-10 16:23:19 +05:30
Kunal Singh
0d1ba75d4c
feat(inspect): resolve version of npm, pypi, rubygem packages' (#451)
* feat(inspect): resolve version of npm, pypi, rubygem packages'

* chore: go mod tidy

* feat: added version resolve to purl reader and added tests

* feat(inspect): resolve version of npm, pypi, rubygem packages'

* fix: updated safedep/dry version

* fix: removed unused block of code, unremoved after refactoring

* refactor: remove custom registry adapter function with predefined

* fix: error handing

* refactor: print statement for better aligning with current code

* refactor: added helpful error message

* Update cmd/inspect/malware.go

Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>

* Update pkg/common/packageregistry.go

Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>

* feat(e2e test): malware analysis without version

---------

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2025-04-09 14:39:30 +05:30
Abhisek Datta
2e0c4a5d3d
ci: Add codecov reporting (#454) 2025-04-09 11:46:22 +05:30
Omkar Phansopkar
923fc4744c
Implemented CycloneDX reporter with metadata, packages & vulnerabilities (#434)
* Implemented CycloneDX reporter with metadata, packages & vulnerabilities

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Refactor to using PtrTo instead of dereferencing

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Minor lint fixes

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Implemented CycloneDX features - Licenses, Vulnerability & annotations

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Support malware in cyclonedx bom

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Script for SPDX licenses, prevent duplicate vulnerabilities

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Fix comment typo

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Test cases for reader application names

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Replaced StringPtr with PtrTo

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Tests for cyclonedx reporter and cvss score calculation

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

---------

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-08 19:54:50 +05:30
Omkar Phansopkar
859ce29ab0
Fixed incorrect dependencies provided by Package.GetDependencies (#450)
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-04-07 13:44:08 +05:30
Kunal Singh
0ad855f2a8
Feat/gitlab policy violations (#445)
* feat: added policy violation support in gitlab reporting

* feat(gitab): policy violations entries in report

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>

* fix(gitlab): identifier id

* fix: using info severity level for policy violations

---------

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
2025-04-07 12:00:40 +05:30
Arunanshu Biswas
f6797d0d6f
feat(reporter): sync malware analysis report (#444)
* build(deps): update dependencies

* feat(reporter): sync malware analysis report

* refactor: fix verification record

* test(reporter/sync): add basic tests for sync
2025-04-03 20:23:30 +05:30
Kunal Singh
6cf88c2e86
Merge pull request #441 from safedep/feat/#430-sarif-vuln-malware
Added vulnerabilities & malware in SARIF reports
2025-04-02 10:50:58 +05:30
Omkar Phansopkar
aaa1794e89
Removed unnecessary debug log
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-04-02 10:38:23 +05:30
Omkar Phansopkar
0bfd37027f
Fix incorrect summary for Malware analyzer event filters
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-04-02 10:32:40 +05:30
Omkar Phansopkar
d7f7a6c72e
Use common ToolMetadata for all reporters and default-enable vuln & malware in SARIF
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-04-01 21:10:45 +05:30
Omkar Phansopkar
976c5317ac
Fix uninitialised vulncache
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-04-01 19:58:59 +05:30
Omkar Phansopkar
86382bbc70
Refactor tool meta data config & using separate vulncache
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-04-01 19:52:08 +05:30
Omkar Phansopkar
d0111cec20 Added vulnerabilities & malware in SARIF reports
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-03-31 20:59:29 +05:30
Abhisek Datta
95c2970e6d
fix: Do not delete old container images (#440) 2025-03-31 11:31:39 +00:00
Kunal Singh
bf2316843b
feat: Improve Description and Solution for Vulnerabilities (#439)
* feat: better descritpion and sulution for vulns

* fix: missing check for nil
2025-03-31 15:19:02 +05:30
Kunal Singh
02fa8de1b4
docs(readme): updated gitlab ci component link (#436) 2025-03-30 18:23:01 +05:30
Omkar Phansopkar
828467309c
Fixed plaintext in sarif messages (#435)
* Fixed plaintext in sarif messages

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Fixed extra group items in regex replace

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Updated testcase with misc symbols

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

---------

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-03-29 19:27:17 +05:30
Kunal Singh
c156f54274
Feat/gitlab report (#419)
* feat: new gitlab report

* fix: gitlab-report version missing

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>

* fix: isseues with schema

* added comments

* fix: removed unknown identifiers

* fix: direct dependency suing depth == 0

* oops: pushed output file :)

* fix: identifiers was taken only one eacy type

* fix: nested struct on GitLabReport

* added relavent docs reference

* fix: hardcoded vet version, with correct version from version.go

* added malwares

* refactor: minor

* fix: summary and name

* added tests

* refactor: seperate function for severity

* refactor: using enums for severity

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>

* feat: using all gitlab identifiers

* refactor: removed unwanted package struct exports

* refactor: IsDirect function to check if package is direct dependency

* removed TODO comments

* refactor: using non-standard id for malware analysis

* refactor: removed hardcoded valuesin gitlab.go

* refactor: gitlab consts

* feat: add gitlab scan fail status const

* refactor: extracted identifiers urls

* fix: github adivisory link

* refactor: solid principle for identifiers url, and scoped method for gitlab reporter

---------

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
2025-03-26 20:26:05 +05:30
Abhisek Datta
f6258fdc86
feat: Add support for malysis min confidence config (#429)
* feat: Add support for malysis min confidence config

* fix: Test case to use factory function
2025-03-26 14:07:40 +05:30
Kunal Singh
635baeb86e
feat: test container working: scan with cloud (#424)
* feat: test container working: scan with cloud

* removed testing policy.yml file

* feat: container test using auth verify

* fix: ci fail due to missing container tag

* fix: buildx not storing images locally

* feat: envs into image

* let see :)

* using secrets directly into docker command

* why do i need to mount?

* fix: test run only on on pr to main repo
2025-03-26 12:43:59 +05:30
Abhisek Datta
6eec7e1740
fix: Dockerfile install ca-certificates (#422) 2025-03-25 09:59:34 +05:30
Kunal Singh
18b9ffee84
fix: container permission issue when creating report files (#417) 2025-03-24 14:21:28 +05:30
Kunal Singh
ed80658f46
Merge pull request #413 from safedep/fix/vet-version-error-code
fix: error code in vet version command
2025-03-23 09:07:31 +05:30
Kunal Singh
21a41aa3e8
Merge branch 'main' into fix/vet-version-error-code 2025-03-23 09:04:59 +05:30
Abhisek Datta
05e6fbebfd
ci/harden github actions (#412)
* ci: Pin github actions to its commit SHA

* ci: Pin github actions to its commit SHA

* chore: Pin docker base images

* fix: Typo in vet-ci
2025-03-23 01:46:25 +05:30
Kunal Singh
c8d4cd38c9 fix: error code in vet version command 2025-03-22 16:46:21 +05:30
Abhisek Datta
35b0021569
Update container.yml (#410)
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
2025-03-21 16:22:43 +05:30
Abhisek Datta
4be215d914
ci: tag container image release (#407)
Co-authored-by: Kunal Singh <kunalsin9h@gmail.com>
2025-03-21 16:13:51 +05:30
Kunal Singh
c1d9050d26
Merge pull request #409 from safedep/fix/nonroot-user-in-debian
fix: use of default non-root user outside distroless images
2025-03-21 16:08:50 +05:30
Kunal Singh
14ca44453f fix: use of default non-root user outside distroless images
Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
2025-03-21 16:04:40 +05:30
Kunal Singh
2c682bdf66
fix: missing sheel for using vet's image as environment (#408)
Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
2025-03-21 15:29:05 +05:30
Omkar Phansopkar
855f0afb21
Updated code with fix need for go runtime (#406)
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-03-21 11:51:02 +05:30
Abhisek Datta
49b2e0f3df
feat: Add Support for GitHub Action or Repository Scanning (#405)
* feat: Add support for github actions scanning

* fix: enrich malware test cases

* fix: fail fast for malware inspect if auth not available

* fix: bug with package version
2025-03-21 04:23:47 +05:30
Sahil Bansal
5387a395a3
fix uv.lock Parser Graph Root Handling (#397)
* fix uv.lock Parser Graph Root Handling

* fixed test case for pathToRoot

* added support for dev dependencies & extra support fot scanning root pkg

---------

Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2025-03-20 10:31:55 +05:30
Abhisek Datta
ec141c3693
Update README.md (#402)
* Update README.md

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

* Update README.md

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

* Update README.md

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

---------

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
2025-03-19 14:28:49 +05:30
Kunal Singh
e0bb4a7836
feat: progress bar in cloud report syncing (#400)
* feat: progress bar in cloud report syncing

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>

* fix: missing nil check guard in syncReportTracker closure

* Update pkg/reporter/sync.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>

* fix: race condition of pre-closure of progress bar before finish

* fix: race condition

* fix: Delay marking trackers as done till stop event

---------

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: abhisek <abhisek.datta@gmail.com>
2025-03-19 09:36:27 +05:30
Kunal Singh
27548de0c8
fix: Handling Package Dependency Callback for Insights v2 Package Met… (#393)
* fix: Handling Package Dependency Callback for Insights v2 Package Meta Enricher

* fix: unwanted newline character and ecosystem handing
2025-03-17 21:15:06 +05:30
Omkar Phansopkar
666011a975
Implemented Defectdojo reporter (#388)
* Implemented Defectdojo reporter

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Added defect-dojo reporter to query command

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Accept defectdojo host URL as arg instead of environment variable

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

---------

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2025-03-17 11:55:22 +05:30
Sahil Bansal
5b4ae39c6a
Feat/#374 support for scanning uv.lock files (#389)
* implemented custom parser for uv.lock files

* ecosystem fixes

* Added tests for uv lockfile parsing

* Incremented list parser count
2025-03-17 08:49:10 +05:30
Kunal Singh
9bd1fb019b
fix: missing version on go install builds (#387)
* fix: missing version information while building using go install

* refactor: missing version on go install builds

* refactor: go version set by both ldflags and go install(runtime)

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>

---------

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
2025-03-13 20:54:10 +05:30
Abhisek Datta
1e631efb06
chore: Remove linter-install from dev-setup (#386) 2025-03-12 16:15:31 +05:30
Kunal Singh
5f960dbf6c
Migrated go version to 1.24 (#381)
* Migrated project to go1.24

* using oapi-codegen from go tools

* tools section to the top in go.mod

* using 1.10.1 oapi-codegen version

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>

* using actions commit hash inplace of version

* revert back to go install for tool oapi-codegen-install

* refactor(devtools): removed oapi-codegen from Makefile

* fix(vet): update gomarkdown package

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>

* fix(vet): updated gin-contrib/sse package

---------

Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
2025-03-12 16:01:24 +05:30
Abhisek Datta
c9bb677999
ci: vet enable comments proxy (#382) 2025-03-10 17:20:37 +05:30
Kunal Singh
fdb3bdd0f2
Add ASDF plugin installation steps to CONTRIBUTING.md (#380)
Signed-off-by: Kunal Singh <kunalsin9h@gmail.com>
2025-03-10 08:16:36 +00:00
Omkar Phansopkar
ec02875f75
Updated code analysis framework (#370)
* Updated code analysis framework

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Updated code with latest packagehint resolution

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

---------

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-03-07 09:52:56 +05:30
Abhisek Datta
2f4e02e883
refactor: Remove deprecated docs (#371) 2025-03-04 19:47:28 +05:30
Sachin Maurya
71fce1b1b2
fix: remove replace directive from go.mod file (#369)
Signed-off-by: slayer321 <sachin.maurya7666@gmail.com>
Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2025-03-04 13:44:22 +05:30
Abhisek Datta
1864e687f8
feat/vscode malysis support (#368)
* chore: Update API sdk to add support for vsx scanning

* chore: Add inspect cmd flag to skip waiting for result

* feat: Add support for VS Code extension reader

* fix: Remove unnecessary vsx distribution path
2025-03-04 13:43:09 +05:30
Abhisek Datta
d599ac8407
feat: Add vet cloud quickstart command (#361)
* feat: Add vet cloud quickstart command

* fix: Quick start tenant setup

* fix: Display msg for API key creation
2025-02-27 04:24:49 +05:30
Abhisek Datta
1db3d0d2cf
fix: Update SLSA provenance generator to handle GitHub deprecated action (#360) 2025-02-25 13:40:21 +05:30
Abhisek Datta
359712bee0
fix: Handle known false positives with npm LFP (#359) 2025-02-25 11:25:58 +05:30
Abhisek Datta
c313485e2f
feat: Allow summary report to filter by usage evidence (#354)
* feat: Allow summary report to filter by usage evidence

* fix: Filter by dependency usage evidence
2025-02-19 23:17:35 +05:30
Abhisek Datta
99612906d8
fix: Summary report tag refactoring (#349)
* fix: Summary report tag refactoring

* chore: Dependency upgrades
2025-02-17 19:13:48 +05:30
Omkar Phansopkar
a47764af5f
Ensure code DB path exists & exclude patterns option (#345)
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-02-07 21:51:42 +05:30
Omkar Phansopkar
1e9fae0330
Added usage evidence in summary and CSV report (#341)
* Added used in code tag for usage evidences in summary report

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Added usage evidence in csv report

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* temporary e2e test for csv report

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Fixed E2E test for csv report comparison

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

---------

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-02-06 06:17:18 +05:30
Omkar Phansopkar
89a6233e76
Integrated depsusage data prepared by code analysis and report unused deps in summary (#336)
* Integrated depsusage data and report with summary

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Verified enricher contracts

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Updated scan command flags & summary reporter

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

---------

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
2025-02-04 13:23:47 +05:30
Abhisek Datta
bc7773d90a
fix: Ensure version is available for sync reporter (#335)
* fix: Ensure version is available for sync reporter

* fix: Fail fast using cobra prerun hook
2025-02-04 09:18:55 +05:30
Omkar Phansopkar
08b5f612ac
Implemented code scan command for building sqlite storage with code analysis data (#326)
* Implemented code scan command for building sqlite storage with code analysis data

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Added E2E test for code scan command

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* refactor: Migrate pkg/command to internal/command since we use pkg as a independent concern

---------

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
Co-authored-by: abhisek <abhisek.datta@gmail.com>
2025-02-03 11:23:51 +05:30
Manav sethi
9ffed8f039
Update scorecard.yml (#330)
* Update scorecard.yml

Signed-off-by: Manav sethi <mail@manav.co.in>

* Update scorecard.yml

Signed-off-by: Manav sethi <mail@manav.co.in>

* Update scorecard.yml

Signed-off-by: Manav sethi <mail@manav.co.in>

* Update scorecard.yml

Signed-off-by: Manav sethi <mail@manav.co.in>

* Revert uneeded change

Signed-off-by: Manav sethi <mail@manav.co.in>

* Added commit sha instead of the version

Signed-off-by: Manav sethi <mail@manav.co.in>

---------

Signed-off-by: Manav sethi <mail@manav.co.in>
2025-01-31 16:28:43 +05:30
Abhisek Datta
e34ef28d03
chore: Dependency upgrades (#324) 2025-01-31 12:31:28 +05:30
Omkar Phansopkar
45915b806a
Merge pull request #321 from safedep/feat/revamp-code-analysis-system
feat/revamp code analysis system
2025-01-29 15:32:38 +05:30
abhisek
d6d3ea8896
chore: Add sqlite3 dependency 2025-01-29 14:08:41 +05:30
abhisek
ccc7712e61
feat: Add contract for code scanner and repository 2025-01-29 13:54:40 +05:30
abhisek
6d9af94dc0
feat: Add ent sqlite3 based storage driver 2025-01-29 13:21:12 +05:30
abhisek
e98690ca42
feat: Add ent as the ORM for storage 2025-01-29 13:03:42 +05:30
abhisek
0701766574
feat: Deprecate legacy code analysis command 2025-01-29 12:42:14 +05:30
Abhisek Datta
2d9cacfe59
docs: Add scarf.sh for analytics (#320) 2025-01-28 18:40:24 +05:30
Abhisek Datta
2354b4af31
fix: Improve markdown summary report to make malware analysis section collapsable (#313) 2025-01-22 11:20:51 +05:30
Abhisek Datta
2c92368985
feat: Add Malysis Integration at Scan Phase (#309)
* feat: Integrate malysis based malware analysis data enricher

* feat: Extend package model to store malware analysis data

* fix: Wait for package enrichers with timeout

* feat: Add malware analyzer plugin

* feat: Add support for malware analyser for classification

* feat: Add support for backoff retry

* fix: Bug with backoff/retry loop

* feat: Update markdown summary reporter for malware analysis stats

* docs: Add docs for malysis enricher config

* docs: Update README.md for malware analysis reference

* feat: Add cli flag to configure trust on tool result

* feat: Add cli flag to customize malware analysis wait time

* test: Add E2E test for malware analysis integration

* fix: Path of malware analysis e2e test

* fix: Show malysis metrics in markdown summary report

* fix: Malware analyser raise filter event for rest of the engine to know about malware
2025-01-22 09:50:59 +05:30
Manav sethi
01396c1243
Minor bugfix to handle cases where the package name doesn't resolve properly (#305)
* Minor bugfix to handle cases where the package name doesn't resolve to a proper URL

* Remove accidental changes

* PR review fixes

* Pass the trustedURLs directly instead of ignoring the npm configured URL

* Updated the test cases
2025-01-14 15:23:12 +05:30
Harisudarsan
6b050feee4
Add support for spdx expression (#304)
Signed-off-by: harisudarsan1 <sudarshanhari561@gmail.com>

error handling and add examples for license filtering

Handled the excluded errors in filter evaluator
Examples are updated for the SPDX license filtering

Signed-off-by: harisudarsan1 <sudarshanhari561@gmail.com>

test: Add test case for spdx license evaluator

chore: Update go.mod

chore: Update vulnerable dependencies
2025-01-06 08:09:35 +05:30
Abhisek Datta
3fab4697a7
chore: Render malysis report URL in console (#302) 2025-01-02 18:50:37 +05:30
Abhisek Datta
141e984067
feat: Add SLSA tag in summary report when available (#301) 2024-12-28 11:50:10 +05:30
Abhisek Datta
7daa0728ab
feat: Integrate with SafeDep Malware Analysis Service (#299)
* feat: Add support for malware analysis service integration

* feat: Update malware analysis command to poll for report

* fix: Spinner handling in malware analysis command

* fix: Malware analysis output table

* fix: Confidence string handling
2024-12-19 14:50:57 +05:30
Abhisek Datta
d98075e84d
fix: Handle package.json when only devDependencies present (#298) 2024-12-12 18:17:43 +05:30
Abhisek Datta
657502940f
fix: PURL handling for manifest (#297) 2024-12-06 11:24:33 +05:30
Abhisek Datta
5ef16388de
Merge pull request #296 from safedep/fix/rubygems-project-json-report
fix: RubyGems JSON report project URL handling
2024-12-05 17:55:27 +05:30
abhisek
31da7ee2b7
fix: RubyGems JSON report project URL handling 2024-12-05 17:19:16 +05:30
Abhisek Datta
7535652640
Merge pull request #295 from AmalChandru/feat/update-to-latest-column
feat: Rename column "Update To" to "Latest" in remediation advice table
2024-12-04 20:09:40 +05:30
AmalChandru
19aa34e5bb feat(reporter): rename column 'Update To' to 'Latest'
Renamed the 'Update To' column in the remediation advice table to 'Latest' for improved clarity and alignment with backend data
2024-12-03 22:36:45 +05:30
Abhisek Datta
1101fdab0f
Merge pull request #294 from safedep/feat/293-pkg-insight-json-report-spec
feat: Add project info in JSON report
2024-11-29 11:06:53 +05:30
abhisek
f02786f4d1
feat: Add project info in JSON report 2024-11-28 19:42:01 +05:30
Abhisek Datta
0186904385
Merge pull request #291 from AmalChandru/docs/update-tree-sitter-link-code-analysis
docs:  Update tree-sitter parser link for Code Analysis documentation
2024-11-26 12:46:06 +05:30
Amal Chandran M V
6e4786a5ad
docs: Update tree-sitter link for code analysis
Signed-off-by: Amal Chandran M V <amaltathva@gmail.com>
2024-11-26 11:28:16 +05:30
Abhisek Datta
041dd151ae
Merge pull request #289 from safedep/feat/package-json-parser
feat: Add support for package.json parsing with approximate semver resolution
2024-11-25 20:11:27 +05:30
abhisek
2333893d4e
feat: Add support for package.json parsing with approximate semver resolution 2024-11-25 18:54:46 +05:30
Abhisek Datta
4d5bbfff03
Merge pull request #288 from AmalChandru/docs/fix-broken-link-policy-as-code
docs: Update broken links for Policy as Code (PaC)
2024-11-25 15:06:41 +05:30
Amal Chandran M V
30f0de2c87
docs: Update broken link for policy as code
Signed-off-by: Amal Chandran M V <amaltathva@gmail.com>
2024-11-25 14:53:47 +05:30
Abhisek Datta
8a56f5b195
Merge pull request #285 from safedep/ci/fix-vet-windows-releaser
fix: Goreleaser workflow - use sudo for apt-get install
2024-11-23 17:09:28 +05:30
abhisek
7f23bad2d3
fix: Goreleaser workflow - use sudo for apt-get install 2024-11-23 17:08:34 +05:30
Abhisek Datta
af18e3ac8b
Merge pull request #284 from safedep/ci/fix-vet-windows-releaser
fix: Goreleaser workflow
2024-11-23 17:05:00 +05:30
abhisek
7c3500db41
fix: Goreleaser workflow 2024-11-23 17:04:10 +05:30
Abhisek Datta
1308f2610d
Merge pull request #283 from safedep/ci/vet-windows-releaser
ci: Add Windows build support
2024-11-23 16:51:07 +05:30
abhisek
094e557720
ci: Add Windows build support 2024-11-23 11:10:47 +05:30
Abhisek Datta
9fb9b3fca6
Merge pull request #282 from safedep/feat/add-pm-namespace-json-report
feat: Add source and namespace in manifest in JSON report
2024-11-18 21:15:07 +05:30
abhisek
1899b99502
feat: Add source and namespace in manifest in JSON report 2024-11-18 20:55:43 +05:30
Abhisek Datta
080964f1d1
Merge pull request #281 from safedep/feat/enhance-markdown-summary-reporter
feat: Enhance markdown summary reporter to use collapsable sections
2024-11-18 17:27:12 +05:30
abhisek
780375b489
feat: Enhance markdown summary reporter to use collapsable sections 2024-11-18 15:42:44 +05:30
Abhisek Datta
3c70b4a0c9
Merge pull request #280 from safedep/chore/dependency-upgrade-2024-11-18
chore: Dependency upgrades
2024-11-18 15:04:19 +05:30
abhisek
41a9b533de
chore: Dependency upgrades 2024-11-18 14:06:38 +05:30
Nikhil Mittal
4f18e7986a
Merge pull request #278 from safedep/dev-nikhil
Updated maintainers
2024-11-14 14:39:16 +05:30
c0d3g33k
358861d283 Updated maintainers 2024-11-14 08:17:45 +00:00
Abhisek Datta
1217a3717a
Merge pull request #274 from safedep/feat/insights-v2-enricher
feat: Support Package Meta Enricher using Insights V2 API
2024-11-14 12:21:20 +05:30
abhisek
6c0f4269f3
refactor: Model ecosystem mapper to maintain SSOT 2024-11-14 08:37:19 +05:30
abhisek
130ee7dff6
fix: Use terraform provider as the ecosystem for terraform lockfiles 2024-11-14 08:28:56 +05:30
abhisek
007adb4a5a
ci: Run insights v2 E2E only when PR is from same repository 2024-11-13 22:04:27 +05:30
abhisek
8f29d4aba0
fix: Show msg to differentiate between authenticated or non-auth scans 2024-11-13 16:20:34 +05:30
abhisek
652b465893
chore: Add error msg when Insights v2 is used without API key 2024-11-13 08:01:31 +05:30
abhisek
0d698b7b21
fix: GitHub E2E to use control tower ecosystem 2024-11-12 20:34:37 +05:30
abhisek
f163d4c5b7
fix: E2E with safedep tenant env variable 2024-11-12 20:29:48 +05:30
abhisek
42fce64f6e
fix: E2E with safedep API key env variable 2024-11-12 19:50:39 +05:30
abhisek
4e4302f530
fix: E2E with safedep API key 2024-11-12 19:11:54 +05:30
abhisek
350ab7831c
fix: Add GITHUB_TOKEN for e2e tests 2024-11-12 17:37:35 +05:30
abhisek
26cfebd6fa
fix: Remove GITHUB_TOKEN from e2e test run 2024-11-12 17:19:42 +05:30
abhisek
fcc4c4b2da
fix: GitHub reader E2E test to make it predictable 2024-11-12 17:16:31 +05:30
abhisek
d86abc1e86
test: Run E2E scenarios with Insights v2 2024-11-12 16:20:28 +05:30
abhisek
887f3d9a93
fix: E2E tests to use insights v2 flag from env 2024-11-12 16:16:30 +05:30
abhisek
b197943ada
fix: Handle current version for packages 2024-11-12 16:12:28 +05:30
abhisek
78590158c7
fix: Insights v2 vulnerabilities mapper 2024-11-12 10:29:13 +05:30
abhisek
d63fed2543
wip: Map insights v2 to v1 2024-11-12 10:14:21 +05:30
abhisek
2031fc6d17
fix: Insights v2 enricher to call backend API 2024-11-12 09:13:04 +05:30
abhisek
378b1ed89e
feat: Add insights v2 enricher 2024-11-12 08:12:27 +05:30
abhisek
ebf6516817
feat: Add insights v2 API url config and client 2024-11-12 07:59:27 +05:30
Abhisek Datta
a9eb625796
Merge pull request #270 from safedep/fix/fix-purl-handling-gha
fix: PURL handling for GitHub Actions
2024-11-03 19:30:53 +05:30
abhisek
d7356d8cf7
fix: PURL handling for GitHub Actions 2024-11-03 19:24:37 +05:30
Abhisek Datta
f5e7aa9457
Merge pull request #266 from safedep/feat/add-support-dev-mode
feat: Add support for API URL overrides
2024-10-25 09:19:54 +05:30
abhisek
d854a93631
fix: Type handling in cloud query response 2024-10-23 18:29:11 +05:30
abhisek
edb5c25b62
feat: Add support for API URL overrides 2024-10-23 09:29:01 +05:30
Abhisek Datta
9feafdbb88
Merge pull request #263 from safedep/feat/cloud-apikey-management
feat: Add Support for Cloud API Key Management
2024-10-21 22:36:04 +05:30
abhisek
3c4f4275ba
fix: Use idiomatic cmd arg name 2024-10-21 22:30:18 +05:30
abhisek
25281e511f
feat: Add support to delete API key
feat: Use tabler for rendering cloud query tables

style: Fix linter errors
2024-10-21 22:28:06 +05:30
abhisek
8a32af83dc
feat: Add api key list command 2024-10-21 21:37:57 +05:30
abhisek
760079337e
feat: Add table helper to ease table creation and persistence to file 2024-10-21 21:37:57 +05:30
abhisek
6c7b160e84
fix: UI rendering for cloud whoami command 2024-10-21 21:37:57 +05:30
Abhisek Datta
298ddbe199
Merge pull request #264 from safedep/feat/terraform-support
feat: Add automatic resolution of terraform lockfile name to custom parser type
2024-10-21 21:37:16 +05:30
abhisek
14309727fc
feat: Add automatic resolution of terraform lockfile name to custom parser type 2024-10-21 21:33:25 +05:30
Abhisek Datta
63de99fab8
Merge pull request #260 from insaaniManav/feat/terraform-support
Feat/terraform support
2024-10-21 21:33:05 +05:30
Manav Sethi
82c631c907 Mapped ecosystem to custom terraform type 2024-10-21 21:01:42 +05:30
Manav Sethi
e1f707141f Changed .terraform.lock.hcl to customParserTerraform 2024-10-21 20:52:09 +05:30
Manav Sethi
e196604c1f Added tests for terraform lockfile 2024-10-21 20:49:52 +05:30
Manav Sethi
5429f8f3ff Increased parser number in test from 17 to 18 2024-10-21 13:06:34 +05:30
Manav Sethi
f1c7c6123a No need to read the file pass it to the parser directly 2024-10-21 12:57:45 +05:30
Manav Sethi
0979eda194 PR review changes 2024-10-21 12:48:55 +05:30
Manav Sethi
d9a6fd9015 Added terraform parser support to generate a package manifest 2024-10-20 21:22:12 +05:30
Manav Sethi
73f6678f6b Added terraform parser support to generate a package manifest 2024-10-20 20:57:41 +05:30
Abhisek Datta
160c094ad3
Merge pull request #259 from safedep/ci/fix-goreleaser-action-trigger
fix: goreleaser action trigger
2024-10-19 13:17:39 +05:30
abhisek
a8cb51c0c5
fix: goreleaser action trigger 2024-10-19 13:16:46 +05:30
Abhisek Datta
cc45ff513a
Merge pull request #258 from safedep/fix/multi-dev-20241018
fix/multi dev 20241018
2024-10-19 13:11:18 +05:30
abhisek
ce7fff57cf
test: Add skip github dep graph API for e2e test 2024-10-19 10:55:43 +05:30
abhisek
d2290cdfe7
feat: Add support to skip using GitHub dependency graph API 2024-10-19 10:38:42 +05:30
abhisek
78a728b87a
fix: SPDX test cases after supporting GitHub actions 2024-10-18 23:53:08 +05:30
abhisek
24cb277b82
fix: GitHub reader test case 2024-10-18 23:50:16 +05:30
abhisek
d39b4c16ee
chore: Update Dockerfile with Go version 2024-10-18 23:42:12 +05:30
abhisek
d213b87171
fix: Enable support for packagist ecosystem 2024-10-18 22:46:18 +05:30
abhisek
b707398a10
chore: Update dependencies 2024-10-18 22:29:13 +05:30
abhisek
14bf541042
chore: Update Go to 1.23 2024-10-18 22:24:37 +05:30
abhisek
b87652bb27
fix: test case for github reader 2024-10-18 17:43:31 +05:30
abhisek
105217834d
chore: Add debug log for publish policy violation 2024-10-18 16:01:11 +05:30
abhisek
eeda65a8b7
fix: Bug in manifest path handling in sync reporter 2024-10-18 15:59:28 +05:30
Abhisek Datta
763772c98e
Merge pull request #255 from insaaniManav/chore/goreleaser-upgrade
Chore/goreleaser upgrade
2024-10-18 09:04:41 +05:30
Manav Sethi
44743a962a Updated commit hash and removed dry runs 2024-10-17 14:20:27 +05:30
Manav Sethi
ae8c2892d6 Updated goreleaser workflow with commit hash pin 2024-10-17 13:56:35 +05:30
Manav Sethi
ec4bd2c341 Updated goreleasr file to version v2 2024-10-17 13:25:13 +05:30
Manav Sethi
96360bad60 Made version v2 2024-10-17 12:42:46 +05:30
Manav Sethi
beb6e2d96a Pinned action version made it specific 2024-10-17 12:26:28 +05:30
Manav Sethi
9c32f4d8ca Pinned action version made it specific 2024-10-17 12:24:32 +05:30
Manav Sethi
be85c7ec93 Removed debug flags and only run on tags 2024-10-17 11:32:33 +05:30
Manav Sethi
67cdf8e536 Removed debug flags and only run on tags 2024-10-17 11:29:34 +05:30
Manav Sethi
b6b26b7811 Added snapshot to goreleaser 2024-10-17 11:08:33 +05:30
Manav Sethi
985c68731e Updated goreleaser to v2 2024-10-17 10:56:34 +05:30
Abhisek Datta
f237b88b46
Merge pull request #254 from safedep/fix/vet-ci-dependabot
fix: #253: Disable cloud mode for Dependabot or external PR
2024-10-16 13:59:35 +05:30
abhisek
dde52a9d01
fix: #253: Disable cloud mode for Dependabot or external PR 2024-10-16 13:54:41 +05:30
Abhisek Datta
6123c75f96
Merge pull request #249 from safedep/feat/add-gha-parser
feat: Add initial support for scanning GitHub Actions
2024-10-13 17:16:38 +05:30
abhisek
feb90a9289
feat: Add initial support for scanning GitHub Actions
feat: Add GHA ecosystem spec mapping
2024-10-13 16:52:26 +05:30
Abhisek Datta
708712abfc
Merge pull request #247 from safedep/feat/add-query-schema-view-command
feat: Add support for viewing Cloud Query service schema
2024-10-12 13:30:31 +05:30
abhisek
f1d6f51237
fix: Allow query limit to be configurable 2024-10-12 13:05:38 +05:30
abhisek
c01a24c203
feat: Add support for viewing Cloud Query service schema 2024-10-12 13:02:11 +05:30
Abhisek Datta
53e5f6d244
Merge pull request #246 from safedep/chore/dependency-upgrade-2024-10-11
chore: Dependency upgrades
2024-10-11 23:00:02 +05:30
abhisek
15063e5993
chore: Dependency upgrades 2024-10-11 22:41:23 +05:30
Abhisek Datta
7eae3203f3
Merge pull request #245 from safedep/ci/vet-enable-cloud-mode
ci: Enable SafeDep cloud for vet
2024-10-11 22:30:18 +05:30
abhisek
155343dc43
ci: Enable SafeDep cloud for vet 2024-10-11 22:26:10 +05:30
Abhisek Datta
aa501a76c0
Merge pull request #244 from safedep/feat/cloud-report-sync-v2
feat: Add SafeDep Cloud Integration as Optional Commands
2024-10-11 21:03:05 +05:30
abhisek
82217aeb07
docs: Update README for maintenance attribution 2024-10-11 20:59:21 +05:30
abhisek
4f1cb39e56
fix: Update README 2024-10-11 20:51:34 +05:30
abhisek
59967ca8cf
Update production identity service info 2024-10-11 19:48:28 +05:30
abhisek
0df9261598
fix: Verify auth before persisting 2024-10-11 17:37:20 +05:30
abhisek
bda53d0857
fix: Fix E2E test case 2024-10-11 17:29:12 +05:30
abhisek
6ec6cf2b03
fix: sarif test case 2024-10-11 17:27:21 +05:30
abhisek
18af8d54e1
fix: Package manifest namespace and path handling 2024-10-11 16:54:36 +05:30
abhisek
69e32d99cc
fix: Tenant domain handling in cloud commands 2024-10-11 10:07:01 +05:30
abhisek
ad6340e60d
refactor: API key configuration command 2024-10-10 14:52:48 +05:30
abhisek
06b080a81c
feat: Add API key management command 2024-10-10 08:23:00 +05:30
abhisek
d8d94b7c1a
refactor: Remove deprecated API generated code 2024-10-09 19:54:25 +05:30
abhisek
7cb7e7fc95
fix: Persist tenant domain post onboarding and login 2024-10-09 17:06:01 +05:30
abhisek
7686e85e52
feat: Add cloud register command 2024-10-09 16:42:05 +05:30
abhisek
eeaf4e1e29
feat: Add cloud whoami command 2024-10-09 16:27:39 +05:30
abhisek
a613190e64
feat: Add cloud ping command 2024-10-09 15:58:47 +05:30
abhisek
30ac9c043e
feat: Add support for cloud login 2024-10-08 14:03:17 +05:30
abhisek
d016c63174
refactor: grpc client to separate cloud and sync API 2024-10-07 23:04:18 +05:30
abhisek
c0e915cfaa
Remove auth trial command for cloud commands 2024-10-05 08:56:24 +05:30
abhisek
debe15e572
feat: Add cloud query command 2024-10-02 23:41:26 +05:30
abhisek
476cd4d29d
refactor: gRPC connection setup into auth package 2024-10-02 21:57:57 +05:30
abhisek
fce0410ae3
feat: Add support for publishing policy violation 2024-10-01 12:00:17 +05:30
abhisek
a9b424dc51
refactor: Cloud report sync to enable syncing violation events 2024-10-01 09:09:14 +05:30
abhisek
fca2b8e3ab
feat: Cloud report sync support multi-project sync 2024-09-30 13:59:12 +05:30
abhisek
7a5d637a50
refactor: Enable tool service session pooling in cloud sync reporter 2024-09-30 12:48:44 +05:30
abhisek
5c1052c6c6
feat: Update cloud sync reporter to include namespace 2024-09-30 10:30:04 +05:30
abhisek
95c87b4f7b
refactor: Move get dependencies enumerator to models 2024-09-28 15:59:02 +05:30
abhisek
70511831ce
feat: Update report sync using grpc 2024-09-27 13:40:32 +05:30
abhisek
d7a1508b8e
refactor: Remove OpenAPI specs for deprecated control API and use gRPC report sync client 2024-09-27 08:15:21 +05:30
Abhisek Datta
9b33168aac
Merge pull request #241 from safedep/docs/style-fix-dependency-inventory
docs: Fix style for dependency inventory doc
2024-09-16 11:01:00 +05:30
abhisek
2b478588b7
docs: Add title for dependency inventory doc 2024-09-16 10:50:13 +05:30
abhisek
e62291fc81
docs: Fix style for dependency inventory doc 2024-09-16 10:46:15 +05:30
Abhisek Datta
403c71db9f
Merge pull request #240 from r0075h3ll/main
How to generate BOMs for Dependency Inventory
2024-09-15 08:52:31 +05:30
Hardik Nanda
d6cb45b405 add image 2024-09-14 12:44:36 +05:30
Hardik Nanda
40acc58451
Merge branch 'safedep:main' into main 2024-09-14 12:38:07 +05:30
Hardik Nanda
d0884a2ee5 update dependency inventory doc 2024-09-14 12:35:50 +05:30
Abhisek Datta
377646078f
Merge pull request #239 from safedep/feat/238-add-jar-scanning-support
feat: Add support for jar scanning
2024-09-07 16:07:30 +05:30
abhisek
a69cd670b4
feat: Add support for jar scanning
refactor: Parser target resolver to re-use from lockfile and directory reader

feat: Add support for scope based parse target resolution

refactor: Dir reader to use config struct

test: Fix directory reader tests

refactor: Rename parser utils to resolver

doc: Add jar scanning example in README.md
2024-09-07 15:45:32 +05:30
Abhisek Datta
78af01e42c
Merge pull request #234 from safedep/feat/insights-client-retry-pool
feat: Add retriable http client for insights service
2024-08-13 22:27:17 +05:30
abhisek
8dc557204a
chore: Update dependencies 2024-08-13 22:18:47 +05:30
abhisek
8495e3daa9
feat: Add retriable http client for insights service 2024-08-13 22:01:13 +05:30
Abhisek Datta
32c2b07e5b
Merge pull request #232 from safedep/fix/lfp-npm-accepted-trusted-url-path
fix: Accept trusted URL base for LFP analyser
2024-08-09 15:19:36 +05:30
abhisek
95cc1e3ad7
fix: Accept trusted URL base for LFP analyser
Signed-off-by: abhisek <abhisek.datta@gmail.com>
2024-08-09 15:05:39 +05:30
Abhisek Datta
26d68d49de
Merge pull request #229 from safedep/chore/update-deps-07-2024
chore: Dependency upgrades
2024-07-11 22:47:04 +05:30
abhisek
e49ab7e29a
chore: Dependency upgrades
Signed-off-by: abhisek <abhisek.datta@gmail.com>
2024-07-11 22:32:51 +05:30
Abhisek Datta
5aa2027b61
Merge pull request #228 from safedep/feat/code-analysis-framework
feat: Code Analysis Framework
2024-07-11 22:04:52 +05:30
abhisek
e6f6288701
feat: Code analysis framework infra
feat: Building code graph

Refactor to support import processing

Handle relative import name fixup

Add docs for code analysis framework

Update docs to include additional examples

feat: Function call graph

Update code graph to link function decl and calls

Include call node in function calls

feat: Flatten vulnerabilities in CSV reporter

refactor: Maintain separation of concerns for code analysis framework

refactor: Separate storage entities in its own package

feat: Add callback support in code graph builder

docs: Fix code analysis framework docs
Signed-off-by: abhisek <abhisek.datta@gmail.com>
2024-07-11 15:09:11 +05:30
Abhisek Datta
1645f4003d
Merge pull request #225 from safedep/docs/update-sarif-use-case
docs: Update vet website docs
2024-06-21 14:58:25 +05:30
abhisek
e1a66bb864
docs: Update vet website docs
docs: Update vet website docs

docs: Set draft mode for unused docs

docs: Update reporting doc
2024-06-21 14:54:55 +05:30
abhisek
f81a15d78e
ci: Pin goreleaser version in GHA workflow 2024-06-21 09:59:26 +05:30
Abhisek Datta
f1e78cfc12
Merge pull request #223 from safedep/feat/sarif-reporting
feat: Add support for SARIF reporting #22
2024-06-21 09:42:57 +05:30
abhisek
47c605ee06
feat: Add support for SARIF reporting #22 2024-06-21 09:40:41 +05:30
Abhisek Datta
c2175fe5a2
Merge pull request #219 from safedep/chore/update-deps-06-2024
chore: Update dependencies
2024-06-11 15:04:30 +05:30
abhisek
c4d4cb31b5
fix: Flaky test with graph dependents
Signed-off-by: abhisek <abhisek.datta@gmail.com>
2024-06-11 15:00:52 +05:30
abhisek
48ece84a36
chore: Update dependencies
Signed-off-by: abhisek <abhisek.datta@gmail.com>
2024-06-11 12:35:12 +05:30
Abhisek Datta
14cde55629
Merge pull request #215 from r0075h3ll/main
Add 'advices' field to the generated json report file
2024-05-17 17:20:23 +05:30
abhisek
926837efc3
refactor: JSON report generator add upgrade advice - PR #215
Signed-off-by: abhisek <abhisek.datta@gmail.com>
2024-05-17 17:12:30 +05:30
Hardik Nanda
1d48cf116a
Update json_report.go
Signed-off-by: Hardik Nanda <hnanda21@gmail.com>
2024-05-17 16:44:45 +05:30
Hardik Nanda
6ac40c592d Minor fix 2024-05-17 12:46:35 +05:30
Hardik Nanda
a058f7160c Write 'advices' field to json report file 2024-05-17 12:37:08 +05:30
Abhisek Datta
4eab4092cf
Merge pull request #206 from safedep/feat/color-code-depgraph-filter-match
feat: Color code nodes matching filter in dependency graph
2024-04-16 23:01:58 +05:30
abhisek
c044946c75
feat: Color code nodes matching filter in dependency graph
Signed-off-by: abhisek <abhisek.datta@gmail.com>
2024-04-16 22:54:16 +05:30
Abhisek Datta
818ab810ab
Merge pull request #205 from safedep/docs/add-pac-doc
docs: Add PAC in README.md
2024-04-11 11:52:50 +05:30
abhisek
d7798ab194
docs: Add PAC in README.md
Signed-off-by: abhisek <abhisek.datta@gmail.com>
2024-04-11 11:46:06 +05:30
Abhisek Datta
d3c0d7c279
Merge pull request #199 from safedep/refactor/docs-2024-04-02
chore: Update vet Documentation
2024-04-02 16:40:54 +05:30
abhisek
ebe5411ba1
chore: Fallback to community mode when auth is unavailable
docs: Add contributing and maintainers doc

docs: Update README

docs: Add TOC in README

docs: Fix README TOC

Signed-off-by: abhisek <abhisek.datta@gmail.com>
2024-04-02 16:34:48 +05:30
Abhisek Datta
b5c2b14c7c
Merge pull request #197 from safedep/chore/update-ossf-scorecard-action
chore: Update OpenSSF scorecard action
2024-04-01 16:49:12 +05:30
abhisek
b523eb57aa
chore: Update OpenSSF scorecard action 2024-04-01 16:45:49 +05:30
Abhisek Datta
6e6225c574
Merge pull request #196 from safedep/chore/dependency-upgrade-2024-04-01
chore: Update Go version and dependencies
2024-04-01 16:41:51 +05:30
abhisek
eb6a914a1a
chore: Update Go version and dependencies 2024-04-01 15:55:09 +05:30
Abhisek Datta
948b411a6e
Merge pull request #195 from safedep/chore/vet-integrate-vet
ci: Add vet for vetting PR
2024-04-01 15:49:34 +05:30
abhisek
ab3ee44b6d
ci: Fix e2e test runner rate limiting issue 2024-04-01 15:43:58 +05:30
abhisek
eb7f3cab94
ci: Add vet for vetting PR 2024-04-01 15:40:47 +05:30
Abhisek Datta
c4fcab47d1
Merge pull request #194 from safedep/chore/update-slsa-builder
ci: Update SLSA generator actions version
2024-03-26 19:27:26 +05:30
abhisek
b7260331cc
ci: Update SLSA generator actions version 2024-03-26 19:26:27 +05:30
Abhisek Datta
edb40c01cb
Merge pull request #193 from safedep/feat/markdown-summary-reporting
feat: Add support for markdown summary report generator
2024-03-26 18:58:03 +05:30
abhisek
b6b0381ef2
chore: Add option to render markdown summary report from query command 2024-03-26 18:53:45 +05:30
abhisek
bfa63005e6
chore: Update markdown summary report format 2024-03-26 18:48:29 +05:30
abhisek
cbdec10ca6
refactor: Move emoji text to markdown package 2024-03-26 18:22:39 +05:30
abhisek
38940ea364
feat: Add support for markdown summary report generator 2024-03-26 16:26:38 +05:30
Abhisek Datta
fef3d6d947
Merge pull request #192 from safedep/feat/summary-report-group-by-direct-deps
feat: Summary Report Group by Top Level Dependencies
2024-03-18 10:24:01 +05:30
abhisek
36f229774f
chore: Update go and go deps 2024-02-08 21:50:44 +05:30
abhisek
fbe42b497c
refactor: Summary report generator for grouped reporting 2024-02-06 14:22:50 +05:30
abhisek
7f2f729418
feat: Add command line arg for group by direct dependencies 2024-02-04 23:11:17 +05:30
abhisek
79d7cf9599
feat: Enhance summary report to show vulnerability risk and sample 2024-02-03 12:49:37 +05:30
Abhisek Datta
c0f9848b77
Merge pull request #190 from safedep/feat/cyclonedx-parser-handle-dependency-relation
feat: CycloneDX Graph Parser
2024-02-02 15:42:08 +05:30
abhisek
b662145492
feat: Refactor CycloneDX parser into CycloneDX Graph Parser
refactor: CDX graph parser to improve readability

fix: Set dependency graph present only when BOM contains at least 1 dependency relation

chore: Add a root note while graph rendering (reporter)

chore: Remove old cyclonedx files

test: Add maven cyclonedx sbom test case
2024-02-02 15:29:34 +05:30
Abhisek Datta
774323c28f
Merge pull request #187 from safedep/feat/npm-graph-parser
feat: Add support for npm Dependency Graph
2024-01-22 10:31:19 +05:30
abhisek
d0f6a828c1
refactor: Allow parser configuration 2024-01-22 10:18:58 +05:30
abhisek
6f3c5ad28e
refactor: Simplify load_exceptions function 2024-01-17 18:06:21 +05:30
abhisek
debca4218b
fix: purl parser bug to handle maven group:name convention 2024-01-17 17:24:29 +05:30
abhisek
1dba6fdd8e
refactor: Parser to use dependency graph parsers
feat: Add npm package-lock.json graph parser

fix: Npm graph parser path to root traversal

fix: File naming convention for npm graph parser

feat: Add reporter for graph visualization in dot format

feat: Add support for showing dependency upgrade path in summary report

fix: Bug in summary reporter related to random ordering of entries with same score

chore: Add support for experimental flag in scanner config

refactor: Test cases or npm package name extractor into utils

feat: Add support for dependency graph data in CSV report generator

fix: LFP npm handle package links

test: Improve test for npm name extraction

feat: Add support for reconstructing dependency graph using insights data

fix: purl reader to use package manifest builder

test: Add E2E for gradle dependency graph reconstruction

fix: Handle root node marking heuristics for enriched dependency graph

feat: Allow query command to generate dependency graph

fix: Scanner dependency graph reconstruction using dependency distance

fix: Test case for maven dependency graph reconstruction

chore: Improve summary report text for dependency path to root

refactor: Code re-use in npm graph to find by semver range
2024-01-15 00:20:56 +05:30
Abhisek Datta
f93f786f1d
Merge pull request #173 from safedep/feat/dependency-graph
feat: Add Support for Dependency Graph
2024-01-09 21:32:38 +05:30
abhisek
be81848cc0
feat: Add data structure to build dependency graph
feat: Add support for DG JSON serialization

test: Add test case for dependency graph structure

test: Add test case for dependency graph structure (GetDependents)

test: Add test case for dependency graph structure (PathToRoot, JSON)

refactor: Use factory to initialize package manifest with dependency graph
2024-01-09 21:21:02 +05:30
Abhisek Datta
f6e055d5c5
Merge pull request #182 from safedep/fix/auth-reporting-experience
fix: Auth and Reporting Experience (#180)
2024-01-01 11:11:03 +05:30
abhisek
b08c8a4581
fix: #180 Ensure version update is available before proposing in summary reporter 2024-01-01 11:01:25 +05:30
abhisek
bd743347d0
fix: #180 provide explicit instruction to use vet community mode when auth verify fails 2024-01-01 10:36:47 +05:30
Abhisek Datta
875d96b4f0
Merge pull request #179 from safedep/fix/add-guardrails-against-nil-pkg-in-event
chore: Add guard rails against nil pkg in event for JSON report generator
2023-12-29 15:37:18 +05:30
abhisek
d63e9277c7
chore: Add guard rails against nil pkg in event for JSON report generator 2023-12-29 15:26:48 +05:30
Abhisek Datta
273d999561
Merge pull request #178 from safedep/fix/lfp_npm_unavailable_package
fix: LFP npm handle missing package
2023-12-29 15:19:04 +05:30
abhisek
79377e36fc
fix: LFP npm handle missing package 2023-12-29 14:15:27 +05:30
Abhisek Datta
ffd73b1e7d
Merge pull request #177 from safedep/fix/json-report-spec-threat-id
fix: Add identifiers to JSON report spec for threats
2023-12-29 13:20:49 +05:30
abhisek
c3b3e2525e
fix: Use valid markdown convention for LFP npm 2023-12-29 13:15:18 +05:30
abhisek
4efa6111ed
fix: Add identifiers to JSON report spec for threats 2023-12-29 13:10:06 +05:30
Abhisek Datta
f4df5f845f
Merge pull request #176 from safedep/patch/extend-json-report-for-lfp-threats
feat: Add threat reporting support in JSON report schema
2023-12-29 10:54:06 +05:30
abhisek
31fa59b9e3
test: Add E2E for LFP npm analyzer 2023-12-29 10:42:37 +05:30
abhisek
2a273760e5
feat: Extend JSON report spec to support threats 2023-12-29 10:30:39 +05:30
Abhisek Datta
578c2b4318
Merge pull request #175 from safedep/feat/add-flag-for-enricher-control
feat: Add support for enrichment control as a flag
2023-12-28 10:38:40 +05:30
abhisek
596d58333b
feat: Add support for enrichment control as a flag 2023-12-28 10:30:00 +05:30
Abhisek Datta
86175b6b9d
Merge pull request #174 from safedep/feat/lfp
feat: Add Support for Lockfile Poisoning Detection for npm Ecosystem
2023-12-27 12:34:28 +05:30
abhisek
4200c2b453
feat: Add support for configurable trusted urls 2023-12-27 12:06:33 +05:30
abhisek
0d6fa599a8
chore: Update crypto library to fix security issue 2023-12-27 10:54:48 +05:30
abhisek
e9abffaed5
feat: Add LFP for npm 2023-12-26 00:44:46 +05:30
abhisek
795dc271b2
chore: Add VSCode configuration file 2023-12-25 16:19:36 +05:30
abhisek
12bd28dfad
chore: Update osv-scanner dependency 2023-12-25 16:19:22 +05:30
abhisek
f09faf879f
ci: Fix go releaser action 2023-11-21 19:23:59 +05:30
Abhisek Datta
d7e6de4987
Merge pull request #160 from safedep/feat/concurrent-manifest-scanning
feat: Scan Manifest on Discovery
2023-11-21 19:15:22 +05:30
abhisek
44ad5d6630
refactor: Remove unnecessary func in loop 2023-11-21 19:06:47 +05:30
abhisek
0831a6414c
test: Add E2E test for filter fail-fast 2023-11-21 18:12:34 +05:30
abhisek
34b5356d0c
fix: Deadlock with concurrent scanning 2023-11-21 16:37:12 +05:30
abhisek
9580bf05eb
fix: Deadlock in scanner with filter fail option 2023-11-21 15:39:11 +05:30
abhisek
9578e00630
fix: Bug with tracker being marked as done 2023-11-21 08:43:00 +05:30
abhisek
aeec13cea2
Refactor scanner workflow to enable scanning per manifests instead of batching 2023-11-21 08:43:00 +05:30
Abhisek Datta
7b48d023cd
Merge pull request #158 from safedep/feat/add-pkg-insights-in-json-report
feat: Add Package Insights in JSON Report
2023-11-21 08:41:52 +05:30
abhisek
84d332396f
fix: Test case for schemamapper 2023-11-21 08:35:44 +05:30
abhisek
4a0dc9834a
fix: Protobuf enum scoped within message 2023-11-21 08:30:47 +05:30
abhisek
092d9a78d9
fix: Return 0 on successful auth setup 2023-11-20 16:04:55 +05:30
abhisek
d56b950030
fix: E2E test script use community auth 2023-11-20 15:53:48 +05:30
abhisek
9a7d2f2bbf
test: Add E2E test using bash scripts 2023-11-20 15:51:18 +05:30
abhisek
e014e3833c
test: Add test case json report generator 2023-11-20 11:21:47 +05:30
abhisek
2a8581d515
test: Add test case for schemamapper insights 2023-11-20 10:53:37 +05:30
abhisek
230a1d3eeb
fix: Add vuln severity in JSON report schema 2023-11-20 09:48:02 +05:30
abhisek
bb42821d94
refactor: JSON report generator to maintain SRP on function 2023-11-17 20:22:12 +05:30
abhisek
8adcc8ea5f
fix: Show aliases in JSON report 2023-11-17 19:23:34 +05:30
abhisek
b4c1c2e938
feat: Add license and vulnerability info in JSON report 2023-11-17 16:14:16 +05:30
Abhisek Datta
358e2148d2
Merge pull request #150 from safedep/feat/140-github-org-reader
feat: Add Support for Github Organization Scanning
2023-11-15 23:05:36 +05:30
abhisek
894354e8f9
Add limits to GHA 2023-11-15 22:55:20 +05:30
abhisek
f5ebf92c6a
chore: Misc language fix 2023-11-14 23:30:57 +05:30
abhisek
73c001891d
docs: Add stars history in README 2023-11-14 14:21:03 +05:30
abhisek
6dbe0464a6
docs: Update README to include github org scanning 2023-11-12 17:46:46 +05:30
abhisek
b35485f49b
feat: Add UI feedback message on manifest enumeration 2023-11-12 17:06:37 +05:30
abhisek
7394334ba8
chore: Make command help msg more explicit 2023-11-12 12:49:48 +05:30
abhisek
65e4690f83
test: Update Github org reader E2E to accommodate higher repo count 2023-11-12 10:51:16 +05:30
abhisek
1161fe60bc
fix: Enforce repository limit strictly for Github org reader 2023-11-12 10:47:58 +05:30
abhisek
b945fe36ba
feat: Add UI integration for Github Org reader component 2023-11-12 10:07:51 +05:30
Abhisek Datta
535ee17642
Merge pull request #147 from safedep/chore/dependency-upgrade-2023-11-9
chore: Update dependencies
2023-11-10 00:07:44 +05:30
abhisek
05211b7858
chore: Update dependencies 2023-11-09 23:57:55 +05:30
Abhisek Datta
f6a08ffcd6
Merge pull request #144 from safedep/chore/misc-enhancements-2023-11-04
Misc Fixes and Enhancements
2023-11-09 20:14:55 +05:30
abhisek
96e612981d
test: Improve github reader E2E to cover repo enumeration case 2023-11-09 19:58:44 +05:30
abhisek
ee1f6e9a4b
test: Add E2E test for vet github reader 2023-11-09 18:45:39 +05:30
abhisek
9819e9deb8
fix: Fix #142 by enumerating top level directory for lockfiles 2023-11-09 16:43:45 +05:30
abhisek
810d673088
fix: SPDX parser for Maven ecosystem 2023-11-09 16:12:01 +05:30
abhisek
13847f2db4
feat: Add support for github lockfile enumeration and parsing 2023-11-08 22:30:35 +05:30
abhisek
a6c34fe62f
chore: Improve error for github reader 2023-11-08 14:07:15 +05:30
abhisek
42546ce740
chore: Set display path for manifest when its not a local file 2023-11-04 11:19:53 +05:30
abhisek
b52e9bbc34
refactor: Cleanup internal auth 2023-11-04 02:40:19 +05:30
Abhisek Datta
f4dccaa846
Merge pull request #141 from safedep/chore/misc-enhancements-2023-11-02
Multiple Misc Fixes and Enhancements
2023-11-04 01:22:58 +05:30
abhisek
a8c1f101c7
ci: Fix osx-cross compiler environment 2023-11-03 23:51:47 +05:30
abhisek
b545c5b26a
ci: Add osx-cross compiler tool chain in release action 2023-11-03 23:13:10 +05:30
abhisek
360f6dda22
ci: Use osx-cross tool chain for darwin builds 2023-11-03 20:30:16 +05:30
abhisek
4fad0ff0c7
ci: Make Github action names explicit
ci: Install GCC multi lib to enable cross compiler

ci: Install clang for goreleaser

ci: Update goreleaser cross-compiler config
2023-11-03 16:50:08 +05:30
abhisek
e20604a260
feat: Add support to specific max entries in summary reports table 2023-11-03 15:20:26 +05:30
abhisek
1f5122ab11
refactor: Github reader into its own context 2023-11-02 13:03:10 +05:30
abhisek
99a07a4295
refactor: Parser interface to use PackageManifest reference instead of value 2023-11-02 12:58:36 +05:30
abhisek
91244843ed
fix: Add support for using malware indicators from OSV data 2023-11-02 12:18:05 +05:30
Abhisek Datta
9ea542c9ac
Merge pull request #135 from safedep/feat/rubygems-ecosystem-support
feat: Add Support for RubyGems Ecosystem
2023-10-31 16:12:12 +05:30
abhisek
d47ec57f9c
docs: Update README to include purl scanning example 2023-10-30 13:11:11 +05:30
abhisek
2aa546861a
fix: purl handle rubygems type 2023-10-30 13:01:25 +05:30
abhisek
f43b5674ae
feat: Add support for purl scanning 2023-10-30 09:12:28 +05:30
abhisek
0c2f8ab593
feat: Enable support for RubyGems ecosystem 2023-10-30 08:27:11 +05:30
Abhisek Datta
023686e1f5
Merge pull request #128 from safedep/abhisek/2023-10-24-dependency-update
chore: Dependency Upgrade
2023-10-25 00:15:13 +05:30
abhisek
15747ff0d1
chore: Update Go version in GH workflows 2023-10-24 23:30:04 +05:30
abhisek
a90aae0131
chore: Dependency upgrade 2023-10-24 23:22:53 +05:30
Abhisek Datta
b234cd39ca
Merge pull request #124 from safedep/abhisek/2023-10-17-misc-cleanup
Multiple Fixes and Enhancements
2023-10-24 23:13:39 +05:30
abhisek
c7a1a0ae67
fix: Add remediation generator for low popularity 2023-10-23 14:48:45 +05:30
abhisek
676f85af45
refactor: Clean up JSON report generator by using contract methods 2023-10-23 11:37:31 +05:30
abhisek
d921a148eb
ci: Increase golangci-lint timeout 2023-10-21 19:42:33 +05:30
abhisek
7719545aab
feat: Revamp JSON report to include manifest, package, violation, advices 2023-10-21 19:37:19 +05:30
abhisek
b9d0cac4fd
chore: Run linter as precommit hook 2023-10-21 17:31:59 +05:30
abhisek
e0b89d0e7c
chore: Run linter as precommit hook 2023-10-21 17:15:29 +05:30
abhisek
3b2c2cf72c
refactor: Include filter spec in analyser event 2023-10-21 17:03:54 +05:30
abhisek
80a70dab6f
refactor: Use filtersuite spec filter in eval program 2023-10-21 16:44:54 +05:30
abhisek
8833085d86
feat: Extend filter suite with additional meta data 2023-10-21 14:19:34 +05:30
abhisek
3d1cd033b4
refactor: Create common check type for filters and violations 2023-10-21 13:06:26 +05:30
abhisek
590a4fde15
chore: Add a meta section in the JSON report schema 2023-10-20 17:12:28 +05:30
abhisek
85da069617
refactor: Violation schema into its own proto file 2023-10-20 16:45:26 +05:30
abhisek
f65f7e8bca
style: Fix models.proto 2023-10-19 19:05:46 +05:30
abhisek
96ce79647d
refactor: JSON report generator to use protobuf based schema 2023-10-19 15:19:27 +05:30
abhisek
9a4d07ebd2
chore: Add lefthook and update developer documentation 2023-10-19 12:46:39 +05:30
abhisek
7d191523a6
chore: Migrate to support handle 2023-10-17 19:06:28 +05:30
Abhisek Datta
f5789c6253
Merge pull request #123 from safedep/feat_github_repo_200823
Fixed Issue with NPM sbom, fixed issues with cyclonedx parser, and refactored code
2023-08-27 09:08:47 +05:30
jc
d103b73056 Resolved PR Comments Issues Raised 2023-08-26 17:08:42 +05:30
jc
a63f11b2fb refactored cyclonedx & sbom parsers to common parsing logic 2023-08-25 12:38:23 +05:30
jc
7a9801e8e6 resolving issue with go and npm ecosystem while parsing spdx 2023-08-25 11:04:55 +05:30
Abhisek Datta
8fd40507d9
Merge pull request #122 from safedep/feat/github-repo-scanning
feat: Add Support for Github Connect for Private Repository Scanning
2023-08-24 12:06:40 +05:30
abhisek
5f9c3282a7
refactor: Only show supported contract in command desc 2023-08-24 12:00:07 +05:30
abhisek
6783db789a
refactor: Merge utils under single package 2023-08-24 11:57:29 +05:30
abhisek
ef7c234bb9
docs: Update README 2023-08-24 11:47:04 +05:30
abhisek
14d12e24ab
refactor: Refactor to maintain boundaries for github connect 2023-08-24 11:43:19 +05:30
jc
bed227ad19 Resolving issues by linter 2023-08-21 20:25:58 +05:30
jc
31b4c738e0 Renamed GITHUB_AUTH_TOKEN 2023-08-21 20:16:26 +05:30
jc
0e888a67d0 Updated Readme 2023-08-21 20:08:56 +05:30
jc
99059f46f0 Added Basic functionality to scan remote github urls 2023-08-21 16:35:13 +05:30
jc
ea77a440f6 Added suppor to connect apps. Currently, just github is supported 2023-08-21 14:07:09 +05:30
Abhisek Datta
f9c511806c
Merge pull request #120 from safedep/fe_spdx_180823
FEAT - Added support to parse and scan SBOM in Spdx format
2023-08-20 15:06:51 +05:30
jc
cce7943a78 Resolved Lint Issues 2023-08-20 14:58:03 +05:30
jc
de871dda82 Resolved issues with go lint 2023-08-20 14:50:50 +05:30
jc
8e90c7ba05 Merging with main branch and resovled issues 2023-08-20 14:33:46 +05:30
jc
681eb922b3 Added ability to scan spdx sbom 2023-08-20 14:26:39 +05:30
Abhisek Datta
5807671f5a
Merge pull request #119 from safedep/bug_sitter_tree_docker_23
Fix: Vet Crash on one of the SBOM generate from Github #118
2023-08-20 14:13:04 +05:30
abhisek
cc918c6ab7
chore: Misc styling fixes 2023-08-20 14:11:03 +05:30
jc
d197326010
Fix: Vet Crash on one of the SBOM generate from Github #118 2023-08-20 14:11:02 +05:30
jc
9c2e20175f Added support of SPDX SBOM 2023-08-20 14:01:20 +05:30
jc
f894d5aa3c Merge branch 'bug_sitter_tree_docker_23' into main 2023-08-18 16:43:51 +05:30
jc
79c5940fa5 Fix: Vet Crash on one of the SBOM generate from Github #118 2023-08-18 16:31:33 +05:30
abhisek
70ba72169c
ci: Fix multi-platform goreleaser config 2023-08-18 12:18:40 +05:30
Abhisek Datta
30aa9d8c05
Merge pull request #116 from safedep/fix/cgo-container-builder
fix: enable cgo support as required by tree sitter
2023-08-18 11:32:04 +05:30
abhisek
16f1768f22
fix: enable cgo support as required by tree sitter 2023-08-18 11:27:16 +05:30
Abhisek Datta
1bb8a4d666
Merge pull request #113 from safedep/jc
Added ability to parse setup.py (Pypi) file and scan its dependencies
2023-08-17 23:29:56 +05:30
abhisek
3e3febd523
chore: gofmt fixes and docs update 2023-08-17 23:21:28 +05:30
jc
5ada5206b6 Resolving lint issues 2023-08-17 21:37:54 +05:30
jc
4e0dc6e645 Added support to generate json report 2023-08-17 20:46:01 +05:30
jc
09ec5d0373 Resolving Lint Issues 2023-08-17 19:50:30 +05:30
jc
d605529870 Resolving PR comments 2023-08-17 19:38:12 +05:30
jc
3fb8e9b5fd Added new test cases scenarios 2023-08-17 16:22:05 +05:30
jc
d1c6a84fc0 Fixing Linter issues 2023-08-17 15:58:22 +05:30
Abhisek Datta
a7e503322f
Create dependency-review.yml 2023-08-17 15:39:48 +05:30
jc
de922e2ea2 Parsing requirements line to fix an issue . Perfectly working setup.py parsing 2023-08-17 14:56:12 +05:30
jc
c774854ce1 Integrate setup.py parser with the command line 2023-08-17 13:36:07 +05:30
jc
8fdb1d61b4 Syncing with master 2023-08-17 13:22:11 +05:30
jc
41e484ba0f Adding setuppy custom parser 2023-08-17 13:17:26 +05:30
Abhisek Datta
c55e0a767d
Merge pull request #112 from safedep/jc-staging
feat: CycloneDX SBOM Scanning Introduced by #111
2023-08-16 18:02:34 +05:30
jc
7279b34483
fix: CycloneDX SBOM support introduced in #111
Signed-off-by: abhisek <abhisek.datta@gmail.com>
2023-08-16 17:46:29 +05:30
jc
7fea4f3e54 Removing custom parsers from supported parsers 2023-08-16 16:24:09 +05:30
jc
6f920445c2 Resolved PR concerns and comments 2023-08-16 16:17:42 +05:30
jc
3f7254eee0 Fixing an issue with mardown 2023-08-11 16:22:39 +05:30
jc
2923c4025c Merge branch 'main' of github.com:safedep/vet into jc 2023-08-11 15:57:44 +05:30
jc
0aa75665b8 Adding few test cases 2023-08-11 14:53:28 +05:30
jc
0cf1afacf2 Added support to prase sbom (cyclonedx) and scan the packages 2023-08-11 13:25:28 +05:30
Abhisek Datta
311b83199f
Merge pull request #109 from safedep/feat/cloud-report-sync
feat: Cloud Report Sync : WIP
2023-08-06 12:22:57 +05:30
abhisek
ed2b547d1f
chore: Fix linter issues in cloud sync reporter 2023-08-06 12:08:45 +05:30
abhisek
1cfd346698
[WIP] Add cloud sync reporter
[WIP] Update cloud sync reporter
2023-08-06 12:08:26 +05:30
Abhisek Datta
33d61f1098
Merge pull request #110 from safedep/chore/dependency-upgrade
chore: Dependency upgrade as per dependabot suggestion
2023-08-06 12:00:32 +05:30
abhisek
6bf729d1ac
chore: Dependency upgrade 2023-08-06 11:55:22 +05:30
Abhisek Datta
58b9f75463
Merge pull request #98 from safedep/develop
chore: Update go dependencies
2023-06-21 10:32:03 +05:30
abhisek
07de20a857
chore: Update go dependencies 2023-06-18 21:42:11 +05:30
abhisek
e583c05d91
Merge remote-tracking branch 'origin/dependabot/go_modules/github.com/google/cel-go-0.15.1' 2023-05-13 22:20:19 +05:30
abhisek
d65157b194
Merge remote-tracking branch 'origin/dependabot/go_modules/golang.org/x/term-0.8.0' 2023-05-13 22:17:36 +05:30
dependabot[bot]
503f9b2884
Bump github.com/google/cel-go from 0.14.0 to 0.15.1
Bumps [github.com/google/cel-go](https://github.com/google/cel-go) from 0.14.0 to 0.15.1.
- [Release notes](https://github.com/google/cel-go/releases)
- [Commits](https://github.com/google/cel-go/compare/v0.14.0...v0.15.1)

---
updated-dependencies:
- dependency-name: github.com/google/cel-go
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 04:16:48 +00:00
dependabot[bot]
1009189f90
Bump golang.org/x/term from 0.7.0 to 0.8.0
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.7.0 to 0.8.0.
- [Commits](https://github.com/golang/term/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 04:16:42 +00:00
dependabot[bot]
e1c3689537
Bump github.com/google/osv-scanner from 1.3.1 to 1.3.2
Bumps [github.com/google/osv-scanner](https://github.com/google/osv-scanner) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/google/osv-scanner/releases)
- [Changelog](https://github.com/google/osv-scanner/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/osv-scanner/compare/v1.3.1...v1.3.2)

---
updated-dependencies:
- dependency-name: github.com/google/osv-scanner
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 04:06:57 +00:00
Abhisek Datta
454e87dfa8
Merge pull request #76 from safedep/develop
Add Support for Community Endpoint for Insights API
2023-04-19 20:42:31 +05:30
abhisek
f5370493b8
Add support for using community mode endpoint
Fix golint issue

Docs update

Docs update

Use community URL if community mode enabled

Update docs for community mode

Update docs for community mode

Show community mode msg for auth verify command
2023-04-18 17:26:21 +05:30
abhisek
aea651aa89
Update installation doc in README 2023-04-18 17:20:54 +05:30
Abhisek Datta
8498ee5edb
Merge pull request #72 from safedep/develop
Fix bug in pywheel spec parser
2023-04-13 20:09:23 +05:30
abhisek
68b3d00ebd
Fix bug in pywheel spec parser 2023-04-13 20:05:08 +05:30
Abhisek Datta
a565c8bc9a
Merge pull request #71 from safedep/develop
Sync Develop to Main
2023-04-13 18:45:07 +05:30
abhisek
0f38e0603e
Fix linter issues 2023-04-13 17:05:45 +05:30
abhisek
277aba8813
Add misc improvemnts to CSV reporter 2023-04-13 17:02:33 +05:30
abhisek
2a9360e5e4
Merge remote-tracking branch 'shivamsk/shivamsk/add-csv-output' into develop 2023-04-13 16:49:34 +05:30
Siva Krishna Merugu
7a6b13885b Removes summaryReporter from csv.go 2023-04-13 16:44:03 +05:30
Abhisek Datta
e46f739526
Merge pull request #70 from safedep/develop
Sync Develop to Main
2023-04-13 16:16:43 +05:30
abhisek
035065f5ab
Re-order table in summary reporter 2023-04-13 16:12:44 +05:30
abhisek
e3078acb82
Update docs 2023-04-13 16:09:23 +05:30
abhisek
92efd33855
Fix version for golang-lint 2023-04-13 16:09:23 +05:30
abhisek
ef67b0c34b
Docs update 2023-04-13 16:09:23 +05:30
abhisek
cf4428d608
Add golang-ci 2023-04-13 16:09:23 +05:30
Abhisek Datta
3ba2a456d5
Merge pull request #59 from tarunsamanta2k20/tarunsamanta/#54
Added feature for showing Ecosystem name in the table along with othe…
2023-04-13 16:08:49 +05:30
Siva Krishna Merugu
26e5055581 Modifies CSV Reporter 2023-04-13 15:44:41 +05:30
Tarun Samanta
dfd7927196
Update summary.go
Removed Commented line.
2023-04-13 02:08:38 +05:30
Tarun Samanta
650055a1dd made request changes 2023-04-13 02:05:56 +05:30
Siva Krishna Merugu
00d2fc2027 Modifies CsvRecord 2023-04-12 14:17:17 +05:30
Siva Krishna Merugu
eef27dde5b Removes spaces in csv.go 2023-04-12 13:17:37 +05:30
Siva Krishna Merugu
e1f8a69505 Adds CSV Reporter for Output : Issue :#6 2023-04-12 13:14:56 +05:30
Abhisek Datta
4ffa8a10df
Merge pull request #68 from safedep/develop
Fix Bug in Python Wheel File Package Spec Parsing
2023-04-12 12:44:45 +05:30
abhisek
9f155583b6
Add regexp based python wheel version parsing
Add test cases for regexp based python wheel version parsing
2023-04-12 12:37:13 +05:30
Abhisek Datta
291acb274c
Merge pull request #53 from safedep/feat/21-refactor-pkg-reader
Refactor Package Readers into a Standard Interface
2023-04-12 10:05:55 +05:30
abhisek
765253c015
Add dirsource based package manifest reader
Add excluded path test

Consolidate implementations into readers pkg

Add lockfile reader implementation

Add json dump reader

Refactor scan to use reader interface

Improve test suite for readers

Add pkg docs

Throw error from readers in scan workflow

Misc fixes

Fix docs
2023-04-12 09:52:53 +05:30
Abhisek Datta
517eec9a30
Merge pull request #66 from safedep/add-brew-docs
Added homebrew installation instructions
2023-04-12 09:52:13 +05:30
Madhu Akula
95d6228f77
added homebrew installation instructions 2023-04-11 23:22:53 +02:00
Abhisek Datta
b479f78735
Merge pull request #65 from safedep/develop
Sync Develop to Main
2023-04-11 10:04:18 +05:30
abhisek
8e76ca6844
Update err handling in auth 2023-04-11 09:56:07 +05:30
abhisek
94c76ba3f8
Top level permission should be minimal 2023-04-10 12:04:32 +05:30
abhisek
a900a3d201
Fix goreleaser gh workflow 2023-04-10 11:52:05 +05:30
Abhisek Datta
7f3c36410b
Merge pull request #45 from safedep/goreleaser-update
adding homebrew-vet tap
2023-04-10 11:50:04 +05:30
abhisek
4bff6d1e65
Revert goreleaser to push to main 2023-04-10 11:47:06 +05:30
abhisek
89f70b58f8
Update GO Releaser token for homebrew release 2023-04-10 11:38:33 +05:30
Tarun Samanta
b9fb908ced second commit 2023-04-10 02:33:29 +05:30
Tarun Samanta
f385c0bb72 Added feature for showing Ecosystem name in the table along with other attribute 2023-04-10 02:07:43 +05:30
Madhu Akula
ff103ce891
Update .goreleaser.yaml
Changed to tap, so we can capture all the packages at SafeDep
2023-04-08 12:28:18 +02:00
Abhisek Datta
946d5cbc8a
Merge pull request #52 from safedep/develop
Dependabot updates for go pkg
2023-04-07 18:31:26 +05:30
abhisek
2c9fc1b397
Dependabot updates for go pkg 2023-04-07 18:27:03 +05:30
Abhisek Datta
6a20a91502
Merge pull request #46 from safedep/chore/add-dependabot-config
Create .github/dependabot.yml
2023-04-07 14:20:54 +05:30
Abhisek Datta
32bdf6183c
Create .github/dependabot.yml 2023-04-07 13:36:53 +05:30
Abhisek Datta
95364928c1
Merge pull request #44 from c0d3G33k/fix/27-duplicate-record-summery
Fix-27 Duplicate findings in report summery
2023-04-06 14:29:22 +05:30
c0d3g33k
9ad78d5a4b Fix-27 Duplicate findings in report summery 2023-04-06 14:16:36 +05:30
Abhisek Datta
571ac59e51
Merge pull request #41 from safedep/develop
Sync Develop to Main
2023-04-05 13:06:27 +05:30
Madhu Akula
1cb84f13c0
Update path-exclusion.md
Fixed Markdown MDX stuff
2023-04-04 22:09:32 +02:00
Madhu Akula
f8274a2dd9
adding homebrew-vet tap 2023-04-04 22:05:19 +02:00
abhisek
989c4d5e5b
Fix #40: Add support for path pattern exclusion 2023-04-04 22:09:03 +05:30
abhisek
7474fdc3ec
Handle transitive dependencies in progress UI 2023-04-04 09:46:33 +05:30
Abhisek Datta
cc7de3fdb6
Merge pull request #35 from c0d3G33k/chore/docker-arm-support
Added support for docker ARM, M1 MACS
2023-04-03 11:18:09 +05:30
Abhisek Datta
4dd3b86a6e
Merge pull request #37 from safedep/social-links
added social links
2023-03-31 23:34:40 +05:30
Madhu Akula
4bf6892b7b
added social links 2023-03-31 20:03:49 +02:00
c0d3G33k
61bf471934 Fixed Error 2023-03-31 16:02:43 +05:30
Abhisek Datta
708eb81a0c
Merge pull request #36 from safedep/fix-url
Fixed docs url
2023-03-31 14:09:44 +05:30
Madhu Akula
981887de11
Fixed docs url 2023-03-31 10:35:26 +02:00
c0d3G33k
6593fd40f5 Added support for docker ARM, M1 MACS 2023-03-31 13:24:36 +05:30
Abhisek Datta
f43920e6e6
Merge pull request #34 from safedep/minor-fixes
Minor fixes
2023-03-30 10:04:51 +05:30
Madhu Akula
e16f907027
Added logo 2023-03-30 01:38:31 +02:00
Madhu Akula
39971ed58b
Added logo 2023-03-30 01:35:41 +02:00
Madhu Akula
580362738d
Added gtag for docs 2023-03-30 01:32:01 +02:00
Madhu Akula
2300ea0844
Fixed README & Moved docs 2023-03-30 01:31:25 +02:00
abhisek
93bbc74981
Fix docusaurus broken link 2023-03-29 21:48:41 +05:30
abhisek
33f1173787
Add .node-version for docs 2023-03-29 21:40:15 +05:30
abhisek
bb43ed44d5
Add npm lockfile 2023-03-29 21:33:45 +05:30
Abhisek Datta
b47355bc03
Merge pull request #32 from safedep/develop
Sync Develop to Main
2023-03-29 11:50:54 +05:30
abhisek
6928b858cd
Use exposed method for event test 2023-03-29 11:44:51 +05:30
abhisek
d6ee7ae229
Show filter failures in markdown reporting 2023-03-29 11:31:14 +05:30
abhisek
b338689814
Merge branch 'main' into develop 2023-03-29 09:55:54 +05:30
Abhisek Datta
2f9dbd0c4b
Merge pull request #33 from safedep/docs-update
🔎 vet 📖 documentation update 🚀
2023-03-29 09:53:42 +05:30
Madhu Akula
abf6e71499
Updated vet README.md 2023-03-29 00:57:10 +02:00
Madhu Akula
bf2dbe0f1b
Added vet documentation 2023-03-29 00:56:52 +02:00
abhisek
7f11b3303d
Add release badge to README 2023-03-24 15:37:10 +05:30
abhisek
dccaa5b1ba
Update README docs 2023-03-23 17:22:43 +05:30
abhisek
d63b920974
Add docker usage instructions in README 2023-03-23 17:20:18 +05:30
abhisek
3db93e9726
Publish events from Filter anlayzer module 2023-03-23 16:34:08 +05:30
abhisek
1e492ba31a
Update blurb text 2023-03-23 16:30:34 +05:30
abhisek
1c91ecfb5f
Add a blurb or vet 2023-03-23 16:21:32 +05:30
Abhisek Datta
77dbf4900d
Merge pull request #31 from safedep/develop
Add Support for Python Wheel Packages
2023-03-21 13:29:06 +05:30
abhisek
9e1cc18faa
Refactor to support customer experimental parsers
Add python wheel dist reading support

Run go mod tidy

Update README docs

Add justification for exceptions workflow

Misc fix for markdown reporting template
2023-03-21 12:23:31 +05:30
Abhisek Datta
ab3d7e9f34
Merge pull request #30 from safedep/develop
#7: Add support for auth verify command
2023-03-16 15:47:10 +05:30
abhisek
430d002c3c
#7: Add support for verify auth before scan 2023-03-10 10:33:41 +05:30
abhisek
115b7e4f0b
#7: Handle potential nil response 2023-03-09 23:15:28 +05:30
abhisek
8097829f54
#7: Show success msg for auth verification 2023-03-09 23:00:49 +05:30
abhisek
b9530c0fbe
#7: Add support for auth verify command 2023-03-09 18:32:43 +05:30
abhisek
252e44814b
Update README 2023-02-23 11:10:40 +05:30
Abhisek Datta
56fdeb64ce
Merge pull request #25 from safedep/develop
Add Support for Package Exceptions
2023-02-23 10:38:42 +05:30
abhisek
ca1fbbcee2
Update docs 2023-02-23 10:35:21 +05:30
abhisek
2ecc52ef00
Print excepions statement in summary report 2023-02-23 09:59:04 +05:30
abhisek
4b16c05ff9
Add exceptions generate analyzer 2023-02-22 19:06:48 +05:30
abhisek
46bd7e2d13
Add reader to read packages with exceptions 2023-02-22 15:01:31 +05:30
abhisek
4882e46815
Add exceptions loader in main 2023-02-21 19:42:44 +05:30
abhisek
0c99a6988a
Add file based exceptions loader 2023-02-21 18:57:11 +05:30
abhisek
0c41bdd5b0
Add exception matching logic 2023-02-21 18:00:20 +05:30
abhisek
eadcd1a83d
#13: Add exceptions package 2023-02-21 13:08:06 +05:30
abhisek
d92561ca8f
#13: Add spec for exceptions management 2023-02-21 11:18:40 +05:30
Abhisek Datta
8873a351ab
Merge pull request #20 from safedep/develop
Add Support for Filter Suite
2023-02-19 22:01:53 +05:30
abhisek
b37834af74
Refactor filter stat in common code 2023-02-19 18:25:28 +05:30
abhisek
e8cebfa539
Add top level permission for GH actions 2023-02-19 17:55:50 +05:30
abhisek
0475f5faf2
Fix typo in summary reporter 2023-02-18 19:54:04 +05:30
abhisek
6412840db3
Add demo asciinema 2023-02-18 18:15:03 +05:30
abhisek
24f265359b
Add filter suite option for scan 2023-02-18 18:01:35 +05:30
abhisek
2ca64478cf
Add analysis rules for filter suite analyzer 2023-02-18 16:37:19 +05:30
abhisek
2e9b5fb8e0
Add filter suite analyzer module 2023-02-18 11:19:59 +05:30
abhisek
5b50255c57
Add filter suite spec 2023-02-17 17:05:05 +05:30
abhisek
d4884c0457
Refactor cel filter analyzer to extract CEL evaluator 2023-02-17 16:44:23 +05:30
abhisek
032d0770c7
Show filter fail reason as error msg 2023-02-17 14:53:24 +05:30
Abhisek Datta
5939b9df10
Merge pull request #19 from safedep/develop
Sync Develop with Main for Multiple Fixes
2023-02-16 17:29:06 +05:30
abhisek
b5457c23b1
Fix #11: Add support for tags in summary report table 2023-02-16 17:06:12 +05:30
abhisek
e895f8a0ec
#16: Refactor to use UI utils for printing msg 2023-02-16 16:18:26 +05:30
abhisek
504598de65
Fix #15: Return error when auth sub-command is missing 2023-02-16 16:05:02 +05:30
abhisek
b4c407dddc
Fix #14: Corrected typo 2023-02-16 16:02:53 +05:30
abhisek
9c1e224a43
Add OpenSSF scorecard badge 2023-02-16 15:37:48 +05:30
Abhisek Datta
23c71ee662
Create scorecard.yml 2023-02-10 12:32:02 +05:30
abhisek
0c74a07c0c
Improve markdown report 2023-02-08 12:06:41 +05:30
abhisek
be9f5f37ca
Improve markdown report 2023-02-08 11:46:30 +05:30
Abhisek Datta
1e6c674d67
Multiple Feature Release (#10)
* Refactor workq to support callbacks

* Use workq callbacks in scan

* Add callbacks in scan for progress reporting

* Add a progress tracker based UI

* Add minimal markdown report

* Add container image releaser action

Fix typo in dockerfile

* Release container on tags
2023-02-07 15:15:06 +05:30
Abhisek Datta
654bd5cd6f
Add Support for Security Gating using Filters (#9)
* Add support for ignorable directories

* Update DRY snapshot

* Support log redirection to file

* Add support to redirect log to stdout

* Add summary reporter

* Refactor scan method

* Refactor to introduce Finish method in analyzer

* Refactor to conslidated reporting for CEL filter

* Show unique CEL filter results

* Add support for filter fail option

* Fix README
2023-02-06 18:32:01 +05:30
abhisek
52c72edfee
[noci] Fix filtering doc 2023-02-03 15:01:05 +05:30
551 changed files with 257868 additions and 1799 deletions

24
.cursor/mcp.json Normal file
View File

@ -0,0 +1,24 @@
{
"mcpServers": {
"pinner-mcp-stdio-server": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"ghcr.io/safedep/pinner-mcp:latest"
]
},
"vet-mcp": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"ghcr.io/safedep/vet:latest",
"server",
"mcp"
]
}
}
}

View File

@ -0,0 +1,10 @@
---
description:
globs:
alwaysApply: true
---
Always use vet-mcp to vet any open source library package suggested by AI generated code before installation.
Follow the rules below to use vet-mcp:
- Try to find the latest version. If not found, it is likely a slopsquatting vulnerability
- Always check for critical vulnerabilities and malware

15
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,15 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/docs"
schedule:
interval: "weekly"

42
.github/vet/policy.yml vendored Normal file
View File

@ -0,0 +1,42 @@
name: SafeDep vet OSS suite
description: |
Customized filter suite for vet vetting vet.
tags:
- general
- safedep-vet
filters:
- name: critical-or-high-vulns
check_type: CheckTypeVulnerability
summary: Critical or high risk vulnerabilities were found
value: |
vulns.critical.exists(p, true) || vulns.high.exists(p, true)
- name: low-popularity
check_type: CheckTypePopularity
summary: Component popularity is low by Github stars count
value: |
projects.exists(p, (p.type == "GITHUB") && (p.stars < 10))
- name: risky-oss-licenses
check_type: CheckTypeLicense
summary: Risky OSS license was detected
value: |
licenses.exists(p, p == "GPL-2.0") ||
licenses.exists(p, p == "GPL-2.0-only") ||
licenses.exists(p, p == "GPL-3.0") ||
licenses.exists(p, p == "GPL-3.0-only") ||
licenses.exists(p, p == "BSD-3-Clause OR GPL-2.0")
- name: ossf-unmaintained
check_type: CheckTypeMaintenance
summary: Component appears to be unmaintained
value: |
scorecard.scores["Maintained"] == 0
- name: osv-malware
check_type: CheckTypeMalware
summary: Malicious (malware) component detected
value: |
vulns.all.exists(v, v.id.startsWith("MAL-"))
- name: ossf-dangerous-workflow
check_type: CheckTypeSecurityScorecard
summary: Component release pipeline appear to use dangerous workflows
value: |
scorecard.scores["Dangerous-Workflow"] == 0

138
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,138 @@
name: CI
on:
pull_request:
branches:
- main
push:
branches:
- main
permissions:
contents: read
jobs:
check-generated-code:
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up Go
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5
with:
go-version-file: go.mod
- name: Run code generation
run: make generate
- name: Check for uncommitted changes
run: |
if [[ -n $(git status --porcelain) ]]; then
echo "ERROR: Generated code is out of sync!"
echo "Please run 'make generate' and commit the changes."
echo ""
echo "Files with changes:"
git status --porcelain
echo ""
echo "Diff:"
git diff
exit 1
fi
run-test:
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up Go
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5
with:
go-version-file: go.mod
- name: Build and Test
run: |
go mod tidy
go build
go test -coverprofile=coverage.txt -v ./...
env:
VET_E2E: true
# Used to avoid rate limiting issue while running
# test suites that use GitHub API
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Coverage
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push'
uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
run-e2e:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up Go
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5
with:
go-version-file: go.mod
check-latest: true
- name: Build vet
run: |
go mod tidy
go build
- name: Run E2E Scenarios
run: |
./test/scenarios/all.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run E2E Scenarios with Insights V2
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
run: |
./test/scenarios/all.sh
env:
E2E_INSIGHTS_V2: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# This will not be available when there is a PR from a forked repository
VET_API_KEY: ${{ secrets.SAFEDEP_CLOUD_API_KEY }}
VET_CONTROL_TOWER_TENANT_ID: ${{ secrets.SAFEDEP_CLOUD_TENANT_DOMAIN }}
build-container-test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout Source
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
- name: Build Multi-Platform Container Image (verification only)
run: |
docker buildx build --platform linux/amd64,linux/arm64 \
-t build-container-test:latest .
- name: Build and Load Native Platform Image for Testing
run: |
docker buildx build --platform linux/amd64 --load \
-t build-container-test:latest .
- name: Test Container Image
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
run: |
docker run --rm \
-e VET_API_KEY=${{ secrets.SAFEDEP_CLOUD_API_KEY }} \
-e VET_CONTROL_TOWER_TENANT_ID=${{ secrets.SAFEDEP_CLOUD_TENANT_DOMAIN }} \
build-container-test:latest \
auth verify

View File

@ -13,14 +13,18 @@ name: "CodeQL"
on:
push:
branches: [ "main" ]
branches: ["main"]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
branches: ["main"]
permissions:
contents: read
jobs:
analyze:
if: "!contains(github.event.commits[0].message, '[noci]')"
timeout-minutes: 30
name: Analyze
runs-on: ubuntu-latest
permissions:
@ -31,28 +35,34 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
language: ["go"]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
- name: Set up Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34
with:
go-version-file: go.mod
check-latest: true
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
- name: Initialize CodeQL
uses: github/codeql-action/init@b8d3b6e8af63cde30bdc382c0bc28114f4346c88 # v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
- run: |
go mod tidy
go build
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"
- run: |
go mod tidy
go build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@b8d3b6e8af63cde30bdc382c0bc28114f4346c88 # v2
with:
category: "/language:${{matrix.language}}"

156
.github/workflows/container.yml vendored Normal file
View File

@ -0,0 +1,156 @@
name: Container Image Releaser
on:
push:
tags:
- "*"
branches:
- "main"
concurrency: ci-container-release
permissions:
contents: read
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
if: "!contains(github.event.commits[0].message, '[noci]')"
timeout-minutes: 30
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
with:
submodules: true
fetch-depth: 0
- name: Registry Login
uses: docker/login-action@dd4fa0671be5250ee6f50aedf4cb05514abda2c7
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Setup QEMU
uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55
- name: Build and Push Container Image
run: |
# Get the tag if this was a tag push event
if [[ "${{ github.ref_type }}" == "tag" ]]; then
TAG=${{ github.ref_name }}
# Validate tag format (must be vX.Y.Z)
if [[ $TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
# Build and push with both version tag and latest
docker buildx build --push --platform linux/amd64,linux/arm64 \
-t $REGISTRY/$IMAGE_NAME:$TAG \
-t $REGISTRY/$IMAGE_NAME:latest \
.
else
echo "Invalid tag format. Must be in format vX.Y.Z (e.g. v1.2.3)"
exit 1
fi
else
# For non-tag pushes, just use latest tag
docker buildx build --push --platform linux/amd64,linux/arm64 \
-t $REGISTRY/$IMAGE_NAME:latest \
.
fi
publish-mcp-registry:
if: startsWith(github.ref, 'refs/tags/') # only run this when new tag is publish
needs: build
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC authentication
contents: read
defaults:
run:
working-directory: ./.mcp-publisher
steps:
- name: Checkout code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
- name: Ensure jq is installed
run: sudo apt-get update && sudo apt-get install -y jq
- name: Get version from tag
# Strip 'v' prefix from tag (e.g., v1.0.0 -> 1.0.0) as
# - we want clean version (x.y.z) without v prefix, since its already added by registry UI
# - in case of docker image, we hardcode in server.json docker image identifier
run: echo "VET_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: fill version in server.json
run: sed -i "s/VERSION_FROM_ENV/$VET_VERSION/g" server.json
# publish mcp server
- name: Install mcp-publisher
run: |
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
- name: Authenticate to MCP Registry
run: ./mcp-publisher login github-oidc
- name: Publish server to MCP Registry
run: ./mcp-publisher publish
verify-publish-mcp-registry:
needs: publish-mcp-registry
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Ensure jq is installed
run: sudo apt-get update && sudo apt-get install -y jq
- name: Get version from tag
# Strip 'v' prefix from tag (e.g., v1.0.0 -> 1.0.0) as
# - we want clean version (x.y.z) without v prefix, since its already added by registry UI
# - in case of docker image, we hardcode in server.json docker image identifier
run: echo "VET_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Query MCP Registry and verify server is published
env:
SERVER_NAME: "io.github.safedep/vet-mcp"
REGISTRY_URL: "https://registry.modelcontextprotocol.io/v0.1/servers"
run: |
export EXPECTED_VERSION=$VET_VERSION
echo "Checking MCP Registry for $SERVER_NAME"
# Query registry
url="${REGISTRY_URL}?search=${SERVER_NAME}"
echo "Requesting: $url"
http_status=$(curl -s -o response.json -w "%{http_code}" "$url")
if [ "$http_status" -ne 200 ]; then
echo "Registry query failed with HTTP status $http_status"
cat response.json || true
exit 1
fi
# Pretty print the response for debugging
echo "Registry response (truncated):"
jq 'if .servers then {servers: (.servers | length)} else . end' response.json
# Check for name and version match
jq -e --arg name "$SERVER_NAME" --arg ver "$EXPECTED_VERSION" 'any(.servers[]; .server.name == $name and .server.version == $ver)' response.json >/dev/null || {
echo "ERROR: Server $SERVER_NAME with version $EXPECTED_VERSION not found"
echo "Full response:"
cat response.json
exit 1
}
echo "Found server $SERVER_NAME with version $EXPECTED_VERSION"

21
.github/workflows/dependency-review.yml vendored Normal file
View File

@ -0,0 +1,21 @@
# Dependency Review Action
#
# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
#
# Source repository: https://github.com/actions/dependency-review-action
# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
name: "Dependency Review"
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: "Checkout Repository"
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3
- name: "Dependency Review"
uses: actions/dependency-review-action@cc4f6536e38d1126c5e3b0683d469a14f23bfea4 # v3

65
.github/workflows/gh-pages-deploy.yml vendored Normal file
View File

@ -0,0 +1,65 @@
name: CLI Reference Manual GitHub Pages Deploy
on:
push:
branches:
- main
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
env:
SOURCE_GEN_DIR: ./docs/manual
jobs:
# Build Jekkll (md -> html)
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5
with:
go-version-file: go.mod
- name: Build vet
run: go build
- name: Generate MD Docs in ${{ env.SOURCE_GEN_DIR }}
run:
./vet doc generate --markdown ${{ env.SOURCE_GEN_DIR }}
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Build with Jekyll
uses: actions/jekyll-build-pages@v1
with:
source: ${{ env.SOURCE_GEN_DIR }}
destination: ./_site
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
# Deployment job
deploy:
needs: build
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

27
.github/workflows/golangci-lint.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: Go Linter
on:
pull_request:
branches:
- main
permissions:
contents: read
pull-requests: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6
with:
go-version-file: go.mod
- name: golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8
with:
version: latest
args: --issues-exit-code=1 --timeout=10m
only-new-issues: true

View File

@ -1,19 +1,26 @@
name: goreleaser
name: Release Automation
on:
push:
tags:
- "*" # triggers only if push new tag version, like `0.8.4` or else
- "v[0-9]+.[0-9]+.[0-9]+"
concurrency: ci-release-automation
permissions:
contents: read
env:
OSX_CROSS_TOOLCHAIN_REPOSITORY: https://github.com/abhisek/osxcross
OSX_CROSS_MACOS_SDK_VERSION: "12.3"
jobs:
goreleaser:
timeout-minutes: 60
outputs:
hashes: ${{ steps.hash.outputs.hashes }}
permissions:
contents: write # for goreleaser/goreleaser-action to create a GitHub release
contents: write # for goreleaser/goreleaser-action to create a GitHub release
packages: write # for goreleaser/goreleaser-action to publish docker images
runs-on: ubuntu-latest
env:
@ -27,9 +34,9 @@ jobs:
- uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2
- uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 # v2
- name: Set up Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34
with:
go-version: 1.19
go-version-file: go.mod
check-latest: true
- name: ghcr-login
uses: docker/login-action@dd4fa0671be5250ee6f50aedf4cb05514abda2c7 # v1
@ -37,31 +44,62 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install OSX Cross Compiler Build Tools
run: sudo apt-get install -y -qq build-essential clang gcc g++ gcc-mingw-w64 zlib1g-dev libmpc-dev libmpfr-dev libgmp-dev cmake libxml2-dev libssl-dev xz-utils
- name: Setup OSX Cross Compiler Tool Chain Environment
run: |
echo "OSXCROSS_DIR=$(dirname $GITHUB_WORKSPACE)/osxcross" >> $GITHUB_ENV
- name: Clone OSX Cross Compiler Tool Chain
run: git clone $OSX_CROSS_TOOLCHAIN_REPOSITORY $OSXCROSS_DIR
- name: Setup Cache for OSX Cross Compiler Tool Chain
id: osxcross-cache
uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # v3
with:
key: ${{ runner.os }}-osxcross-${{ env.OSX_CROSS_MACOS_SDK_VERSION }}
path: |
${{ env.OSXCROSS_DIR }}/target/bin
- name: Build OSX Cross Compiler Tool Chain
if: steps.osxcross-cache.outputs.cache-hit != 'true'
run: |
cd $OSXCROSS_DIR
SDK_VERSION=$OSX_CROSS_MACOS_SDK_VERSION UNATTENDED=yes ./build.sh
- name: Add OSX Cross Compiler Tool Chain to Path
run: |
echo "$OSXCROSS_DIR/target/bin" >> $GITHUB_PATH
- name: Run GoReleaser
id: run-goreleaser
uses: goreleaser/goreleaser-action@8f67e590f2d095516493f017008adc464e63adb1 # v4.1.0
uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0
with:
version: latest
args: release --rm-dist
distribution: goreleaser
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate subject
id: hash
env:
ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
run: |
set -euo pipefail
checksum_file=$(echo "$ARTIFACTS" | jq -r '.[] | select (.type=="Checksum") | .path')
echo "hashes=$(cat $checksum_file | base64 -w0)" >> "$GITHUB_OUTPUT"
GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN }}
- name: Upload dist folder
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: dist-artifacts
path: dist/
provenance:
needs: [goreleaser]
permissions:
actions: read # To read the workflow path.
id-token: write # To sign the provenance.
contents: write # To add assets to a release.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.4.0
with:
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
upload-assets: true
private-repository: true
attestations: write # To write attestations
runs-on: ubuntu-latest
steps:
- name: Download dist folder
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: dist-artifacts
path: dist/
- name: Attest build provenance (checksums)
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
with:
subject-checksums: dist/checksums.txt

63
.github/workflows/publish-npm.yml vendored Normal file
View File

@ -0,0 +1,63 @@
name: Publish NPM Package
on:
workflow_run:
workflows: ["Release Automation"]
types:
- completed
jobs:
publish-npm:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
permissions:
contents: read
id-token: write
outputs:
package_version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "18"
registry-url: "https://registry.npmjs.org"
- name: Extract Tag Version
id: version
run: |
TAG_VERSION="${{ github.event.workflow_run.head_branch }}"
if [[ "$TAG_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
VERSION="${TAG_VERSION#v}" # Remove leading 'v'
echo "version=$VERSION" >> $GITHUB_OUTPUT
else
echo "No valid tag found in head_branch: $TAG_VERSION"
exit 1
fi
- name: Prepare package
run: |
cd publish/npm
npm version ${{ steps.version.outputs.version }} --no-git-tag-version
- name: Publish to npm
run: |
cd publish/npm
npm publish --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
test-installation:
needs: publish-npm
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: ["14", "18", "20"]
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Test installation
run: |
npm install -g @safedep/vet@${{ needs.publish-npm.outputs.package_version }}
vet version
vet --help || true

60
.github/workflows/scorecard.yml vendored Normal file
View File

@ -0,0 +1,60 @@
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: "35 22 * * 0"
push:
branches: ["main"]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
timeout-minutes: 30
runs-on: ubuntu-latest
permissions:
security-events: write
id-token: write
steps:
- name: "Checkout code"
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecard on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@807578363a7869ca324a79039e6db9c843e0e100 # v2.1.27
with:
sarif_file: results.sarif

View File

@ -3,18 +3,23 @@ on:
pull_request:
branches:
- main
permissions:
contents: read
jobs:
trufflehog:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout Source
uses: actions/checkout@v2
uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5
with:
fetch-depth: '0'
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@main
uses: trufflesecurity/trufflehog@8b6f55b592e46ac44a42dc3e3dee0ebcc0f56df5
with:
path: ./
base: main
head: HEAD
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.event.pull_request.head.sha }}

46
.github/workflows/vet-ci.yml vendored Normal file
View File

@ -0,0 +1,46 @@
name: vet OSS Components
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
issues: write
pull-requests: write
security-events: write
jobs:
vet:
name: vet
runs-on: ubuntu-latest
steps:
- name: Checkout
id: checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Enable Cloud Mode
run: echo "SAFEDEP_CLOUD_MODE=true" >> $GITHUB_ENV
- name: Override Cloud Mode if Actor is Dependabot
if: github.actor == 'dependabot[bot]'
run: echo "SAFEDEP_CLOUD_MODE=false" >> $GITHUB_ENV
- name: Override Cloud Mode if PR is from External Repository
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
run: echo "SAFEDEP_CLOUD_MODE=false" >> $GITHUB_ENV
- name: Run vet
uses: safedep/vet-action@01f547ee95dfd4f8f11fa64b399e5e00f22b0801
with:
policy: .github/vet/policy.yml
cloud: ${{ env.SAFEDEP_CLOUD_MODE }}
cloud-key: ${{ secrets.SAFEDEP_CLOUD_API_KEY }}
cloud-tenant: ${{ secrets.SAFEDEP_CLOUD_TENANT_DOMAIN }}
enable-comments-proxy: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SAFEDEP_CLOUD_MODE: ${{ env.SAFEDEP_CLOUD_MODE }}

View File

@ -0,0 +1,36 @@
name: Container Scan E2E
on:
push:
branches: [ main ]
pull_request:
permissions: read-all
jobs:
e2e-scan:
name: E2E Scan on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- name: Checkout Source
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- name: Set up Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6
with:
go-version-file: go.mod
- name: Build and Test
run: |
make
- name: Run container scan tests
shell: bash
run: |
./vet scan --image alpine:latest
./vet scan --image ghcr.io/safedep/vet:latest
./vet scan --image node:20

14
.gitignore vendored
View File

@ -5,6 +5,11 @@
*.so
*.dylib
# Test database files
*.db
*.sqlite
*.sqlite3
# Test binary, built with `go test -c`
*.test
@ -15,5 +20,12 @@
# vendor/
/vet
dist/
/.env.dev
.vscode/
# MacOS specific files
**/.DS_Store
# Auto-generated context files
CLAUDE.md

18
.golangci.yml Normal file
View File

@ -0,0 +1,18 @@
# golangci-lint configuration file
# See https://golangci-lint.run/usage/configuration/
version: "2"
linters:
exclusions:
paths: []
formatters:
enable:
- gci
- gofumpt
settings:
gci:
sections:
- standard
- default
- localmodule

View File

@ -1,18 +1,58 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
version: 2
before:
hooks:
- go mod tidy
- go generate ./...
env:
- CGO_ENABLED=1
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- darwin
goarch:
- amd64
- arm64
- id: linux
goos: [linux]
goarch: [amd64]
env:
- CC=x86_64-linux-gnu-gcc
- CXX=x86_64-linux-gnu-g++
- id: darwin
goos: [darwin]
goarch: [amd64, arm64]
env:
- CC=o64-clang
- CXX=o64-clang++
- id: windows
goos: [windows]
goarch: [amd64]
env:
- CC=x86_64-w64-mingw32-gcc
- CXX=x86_64-w64-mingw32-g++
release:
# for prerelease it doesn't build and distribute
prerelease: auto
universal_binaries:
- replace: true
brews:
- name: vet
homepage: https://safedep.io
description: "SafeDep vet is a tool for identifying open source software supply chain risks"
license: "Apache-2.0"
repository:
owner: safedep
name: homebrew-tap
branch: main
# TODO: Move to PR workflow once v1.17 is released
# branch: develop/vet
# pull_request:
# enabled: true
# base: main
archives:
- format: tar.gz
@ -28,8 +68,9 @@ archives:
format: zip
checksum:
name_template: 'checksums.txt'
algorithm: sha256
snapshot:
name_template: "{{ incpatch .Version }}-next"
version_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:

View File

@ -0,0 +1,74 @@
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.safedep/vet-mcp",
"title": "SafeDep Vet MCP",
"description": "Protect your AI agents and IDEs from malicious open-source packages.",
"version": "VERSION_FROM_ENV",
"websiteUrl": "https://safedep.io",
"repository": {
"url": "https://github.com/safedep/vet",
"source": "github"
},
"icons": [
{
"src": "https://raw.githubusercontent.com/safedep/.github/9275c7d1b59f718d73e47cecd93df92e7bfbea25/assets/logo/safedep-logo-darkshade.svg",
"mimeType": "image/svg+xml",
"sizes": [
"48x48",
"96x96"
],
"theme": "light"
},
{
"src": "https://raw.githubusercontent.com/safedep/.github/9275c7d1b59f718d73e47cecd93df92e7bfbea25/assets/logo/safedep-logo.svg",
"mimeType": "image/svg+xml",
"sizes": [
"48x48",
"96x96"
],
"theme": "dark"
}
],
"packages": [
{
"registryType": "oci",
"identifier": "ghcr.io/safedep/vet:vVERSION_FROM_ENV",
"runtimeHint": "docker",
"runtimeArguments": [
{
"type": "named",
"name": "--rm",
"value": ""
},
{
"type": "named",
"name": "-i",
"value": ""
}
],
"packageArguments": [
{
"type": "positional",
"value": "-s"
},
{
"type": "named",
"name": "-l",
"value": "/tmp/vet-mcp.log"
},
{
"type": "positional",
"value": "server"
},
{
"type": "positional",
"value": "mcp"
}
],
"transport": {
"type": "stdio"
}
}
]
}

2
.tool-versions Normal file
View File

@ -0,0 +1,2 @@
golang 1.25.1
gitleaks 8.16.4

23
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,23 @@
{
"[go]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "golang.go",
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
},
"gopls": {
"usePlaceholders": true,
"completeUnimported": true,
"staticcheck": true,
"ui.semanticTokens": true,
"formatting.gofumpt": true,
},
"cSpell.words": [
"ineffassign",
"Infof",
"lockfiles",
"nolint",
"safedep"
]
}

102
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,102 @@
# Contributing Guide
You can contribute to `vet` and help make it better. Apart from bug fixes,
features, we particularly value contributions in the form of:
- Documentation improvements
- Bug reports
- Using `vet` in your projects and providing feedback
## How to contribute
1. Fork the repository
2. Add your changes
3. Submit a pull request
## How to report a bug
Create a new issue and add the label "bug".
## How to suggest a new feature
Create a new issue and add the label "enhancement".
## Development workflow
When contributing changes to repository, follow these steps:
1. If you modified code that requires generation (e.g., enum registrations, ent schemas), run `make generate` and commit the generated files
2. Ensure tests are passing
3. Ensure you write test cases for new code
4. `Signed-off-by` line is required in commit message (use `-s` flag while committing)
## Developer Setup
### Requirements
- Go 1.25.0+
### Install Dependencies
- Install [ASDF](https://asdf-vm.com/)
- Install the development tools
```bash
asdf plugin add golang
asdf plugin add gitleaks
asdf install
```
- Install git hooks (using Go toolchain)
```bash
go tool github.com/evilmartians/lefthook install
```
Install `golangci-lint`
```shell
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.5.0
```
### Build
Install build tools
```bash
make dev-setup
```
Generate code from API specs and build `vet`
```bash
make
```
Quick build without regenerating code from API specs
```bash
make quick-vet
```
### Generate Code
If you modify code that requires generation (enum registrations in `pkg/analyzer/filterv2/enums.go`, ent schemas in `ent/schema/*.go`), run:
```bash
make generate
```
**Important**: Generated files must be committed to the repository. CI will fail if generated code is out of sync.
### Format Code
```bash
golangci-lint fmt
```
### Run Tests
```bash
make test
```

45
Dockerfile Normal file
View File

@ -0,0 +1,45 @@
FROM --platform=$BUILDPLATFORM golang:1.25-bookworm@sha256:c4bc0741e3c79c0e2d47ca2505a06f5f2a44682ada94e1dba251a3854e60c2bd AS build
WORKDIR /build
# Install cross-compilation tools
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
&& rm -rf /var/lib/apt/lists/*
COPY go.mod go.sum ./
RUN go mod download
COPY . .
ARG TARGETPLATFORM
ENV CGO_ENABLED=1
# Set up cross-compilation environment based on target platform
RUN case "${TARGETPLATFORM}" in \
"linux/amd64") \
CC=gcc CXX=g++ GOOS=linux GOARCH=amd64 make quick-vet ;; \
"linux/arm64") \
CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ GOOS=linux GOARCH=arm64 make quick-vet ;; \
*) echo "Unsupported platform: ${TARGETPLATFORM}" && exit 1 ;; \
esac
FROM debian:12-slim@sha256:b1a741487078b369e78119849663d7f1a5341ef2768798f7b7406c4240f86aef
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates git \
&& rm -rf /var/lib/apt/lists/*
ARG TARGETPLATFORM
LABEL org.opencontainers.image.source=https://github.com/safedep/vet
LABEL org.opencontainers.image.description="Open source software supply chain security tool"
LABEL org.opencontainers.image.licenses=Apache-2.0
LABEL io.modelcontextprotocol.server.name="io.github.safedep/vet-mcp"
COPY ./samples/ /vet/samples
COPY --from=build /build/vet /usr/local/bin/vet
ENTRYPOINT ["vet"]

12
MAINTAINERS.txt Normal file
View File

@ -0,0 +1,12 @@
vet is built and maintained by SafeDep with the help of the community.
https://safedep.io
Abhisek Datta
Email: abhisek@safedep.io
GitHub username: @abhisek
Affiliation: SafeDep
Nikhil Mittal
Email: nikhil.mittal641@gmail.com
GitHub username: @c0d3G33k
Affiliation: Chargebee

View File

@ -2,32 +2,77 @@ SHELL := /bin/bash
GITCOMMIT := $(shell git rev-parse HEAD)
VERSION := "$(shell git describe --tags --abbrev=0)-$(shell git rev-parse --short HEAD)"
all: clean setup vet
all: quick-vet
oapi-codegen-install:
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.10.1
.PHONY: ent
ent:
go generate ./ent
.PHONY: filterv2-gen
filterv2-gen:
go generate ./pkg/analyzer/filterv2/...
generate: ent filterv2-gen
protoc-install:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
oapi-codegen:
oapi-codegen -package insightapi -generate types ./api/insights-v1.yml > ./gen/insightapi/insights.types.go
oapi-codegen -package insightapi -generate client ./api/insights-v1.yml > ./gen/insightapi/insights.client.go
oapi-codegen -package controlplane -generate types ./api/cp-v1-trials.yml > ./gen/controlplane/trials.types.go
oapi-codegen -package controlplane -generate client ./api/cp-v1-trials.yml > ./gen/controlplane/trials.client.go
dev-setup: protoc-install
protoc-codegen:
protoc -I ./api \
--go_out=./gen/filterinput \
--go_opt=paths=source_relative \
./api/filter_input_spec.proto
protoc -I ./api \
--go_out=./gen/filtersuite \
--go_opt=paths=source_relative \
./api/filter_suite_spec.proto
protoc -I ./api \
--go_out=./gen/exceptionsapi \
--go_opt=paths=source_relative \
./api/exceptions_spec.proto
protoc -I ./api \
--go_out=./gen/models \
--go_opt=paths=source_relative \
./api/models.proto
protoc -I ./api \
--go_out=./gen/models \
--go_opt=paths=source_relative \
./api/insights_models.proto
protoc -I ./api \
--go_out=./gen/jsonreport \
--go_opt=paths=source_relative \
./api/json_report_spec.proto
protoc -I ./api \
--go_out=./gen/violations \
--go_opt=paths=source_relative \
./api/violations.proto
protoc -I ./api \
--go_out=./gen/checks \
--go_opt=paths=source_relative \
./api/checks.proto
setup:
mkdir -p out gen/insightapi gen/controlplane gen/filterinput
mkdir -p out \
gen/insightapi \
gen/cpv1trials \
gen/cpv1 \
gen/syncv1 \
gen/filterinput \
gen/filtersuite \
gen/exceptionsapi \
gen/models \
gen/jsonreport \
gen/violations \
gen/checks
GO_CFLAGS=-X main.commit=$(GITCOMMIT) -X main.version=$(VERSION)
GO_LDFLAGS=-ldflags "-w $(GO_CFLAGS)"
quick-vet:
go build ${GO_LDFLAGS}
vet: oapi-codegen protoc-codegen
go build ${GO_LDFLAGS}

590
README.md
View File

@ -1,106 +1,582 @@
# vet
<div align="center">
<img width="3024" height="1964" alt="image" src="./docs/assets/vet-terminal.png" />
`vet` is a tool for identifying risks in open source software supply chain. It
helps engineering and security teams to identify potential issues in their open
source dependencies and evaluate them against organizational policies.
<h1>SafeDep VET</h1>
<p><strong>🚀 Enterprise grade open source software supply chain security</strong></p>
<p>
<a href="https://github.com/safedep/vet/releases"><strong>Download</strong></a>
<a href="#-quick-start"><strong>Quick Start</strong></a>
<a href="https://docs.safedep.io/"><strong>Documentation</strong></a>
<a href="#-community"><strong>Community</strong></a>
</p>
</div>
<div align="center">
[![Go Report Card](https://goreportcard.com/badge/github.com/safedep/vet)](https://goreportcard.com/report/github.com/safedep/vet)
[![License](https://img.shields.io/github/license/safedep/vet)](https://github.com/safedep/vet/blob/main/LICENSE)
[![Release](https://img.shields.io/github/v/release/safedep/vet)](https://github.com/safedep/vet/releases)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/safedep/vet/badge)](https://api.securityscorecards.dev/projects/github.com/safedep/vet)
[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev)
[![CodeQL](https://github.com/safedep/vet/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/safedep/vet/actions/workflows/codeql.yml)
[![Go Reference](https://pkg.go.dev/badge/github.com/safedep/vet.svg)](https://pkg.go.dev/github.com/safedep/vet)
## TL;DR
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/safedep/vet)
> Ensure `$(go env GOPATH)/bin` is in your `$PATH`
</div>
Install using `go get`
---
## 🎯 Why vet?
> **70-90% of modern software constitute code from open sources** — How do we know if it's safe?
**vet** is an open source software supply chain security tool built for **developers and security engineers** who need:
**Next-gen Software Composition Analysis** — Vulnerability and malicious package detection
**Policy as Code** — Express opinionated security policies using [CEL](https://cel.dev/)
**Real-time malicious package detection** — Powered by [SafeDep Cloud](https://docs.safedep.io/cloud/malware-analysis) active scanning
**Multi-ecosystem support** — npm, PyPI, Maven, Go, Docker, GitHub Actions, and more
**CI/CD native** — Built for DevSecOps workflows with support for GitHub Actions, GitLab CI, and more
**MCP Server** — Run `vet` as a MCP server to vet open source packages from AI suggested code
**Agents** — Run AI agents to query and analyze scan results
## ⚡ Quick Start
**Install in seconds:**
```bash
# macOS & Linux
brew install safedep/tap/vet
```
or download a [pre-built binary](https://github.com/safedep/vet/releases)
**Scan your project:**
```bash
# Scan current directory
vet scan -D .
# Scan a single file
vet scan -M package-lock.json
# Fail CI on critical vulnerabilities
vet scan -D . --filter 'vulns.critical.exists(p, true)' --filter-fail
# Fail CI on OpenSSF Scorecard requirements
vet scan -D . --filter 'scorecard.scores.Maintained < 5' --filter-fail
# Fail CI if a package is published from a GitHub repository with less than 5 stars
vet scan -D . --filter 'projects.exists(p, p.type == "GITHUB" && p.stars < 5)' --filter-fail
```
## 🔒 Key Features
### 🕵️ **Code Analysis**
Unlike dependency scanners that flood you with noise, `vet` analyzes your **actual code usage** to prioritize real risks. See [dependency usage evidence](https://docs.safedep.io/guides/dependency-usage-identification) for more details.
### 🛡️ **Malicious Package Detection**
Integrated with [SafeDep Cloud](https://docs.safedep.io/cloud/malware-analysis) for real-time protection against malicious packages in the wild. Free for open source projects. Fallback to _Query Mode_ when API key is not provided. Read more [about malicious package scanning](#-malicious-package-detection-1).
### 📋 **Policy as Code**
Define security policies using CEL expressions to enforce context specific security requirements.
```bash
# Block packages with critical CVEs
vet scan \
--filter 'vulns.critical.exists(p, true)'
# Enforce license compliance
vet scan \
--filter 'licenses.contains_license("GPL-3.0")'
# Enforce OpenSSF Scorecard requirements
# Require minimum OpenSSF Scorecard scores
vet scan \
--filter 'scorecard.scores.Maintained < 5'
```
### 🎯 **Multi-Format Support**
- **Package Managers**: npm, PyPI, Maven, Go, Ruby, Rust, PHP
- **Container Images**: Docker, OCI
- **SBOMs**: CycloneDX, SPDX
- **Binary Artifacts**: JAR files, Python wheels
- **Source Code**: Direct repository scanning
## 🔥 See vet in Action
<div align="center">
<img src="./docs/assets/vet-demo.gif" alt="vet Demo" width="100%" />
</div>
## 🚀 Production Ready Integrations
### 📦 **GitHub Actions**
Zero config security guardrails against vulnerabilities and malicious packages in your CI/CD pipeline
**with your own opinionated policies**:
```yaml
- uses: safedep/vet-action@v1
with:
policy: ".github/vet/policy.yml"
```
See more in [vet-action](https://github.com/safedep/vet-action) documentation.
### 🔧 **GitLab CI**
Enterprise grade scanning with [vet CI Component](https://gitlab.com/explore/catalog/safedep/ci-components/vet):
```yaml
include:
- component: gitlab.com/safedep/ci-components/vet/scan@main
```
### 🐳 **Container Integration**
Run `vet` anywhere, even your internal developer platform or custom CI/CD environment using our container image.
```bash
docker run --rm -v $(pwd):/app ghcr.io/safedep/vet:latest scan -D /app
```
## 📚 Table of Contents
- [🎯 Why vet?](#-why-vet)
- [⚡ Quick Start](#-quick-start)
- [🔒 Key Features](#-key-features)
- [🕵️ **Code Analysis**](#-code-analysis)
- [🛡️ **Malicious Package Detection**](#-malicious-package-detection)
- [📋 **Policy as Code**](#-policy-as-code)
- [🎯 **Multi-Format Support**](#-multi-format-support)
- [🔥 See vet in Action](#-see-vet-in-action)
- [🚀 Production Ready Integrations](#-production-ready-integrations)
- [📦 **GitHub Actions**](#-github-actions)
- [🔧 **GitLab CI**](#-gitlab-ci)
- [🐳 **Container Integration**](#-container-integration)
- [📚 Table of Contents](#-table-of-contents)
- [📦 Installation Options](#-installation-options)
- [🍺 **Homebrew (Recommended)**](#-homebrew-recommended)
- [📥 **Direct Download**](#-direct-download)
- [🐹 **Go Install**](#-go-install)
- [🐳 **Container Image**](#-container-image)
- [⚙️ **Verify Installation**](#-verify-installation)
- [🎮 Advanced Usage](#-advanced-usage)
- [🔍 **Scanning Options**](#-scanning-options)
- [🎯 **Policy Enforcement Examples**](#-policy-enforcement-examples)
- [🔧 **SBOM Support**](#-sbom-support)
- [📊 **Query Mode \& Data Persistence**](#-query-mode--data-persistence)
- [📊 Reporting](#-reporting)
- [📋 **Report Formats**](#-report-formats)
- [🎯 **Report Examples**](#-report-examples)
- [🤖 **MCP Server**](#-mcp-server)
- [🤖 **Agents**](#-agents)
- [🛡️ Malicious Package Detection](#-malicious-package-detection-1)
- [🚀 **Quick Setup**](#-quick-setup)
- [🎯 **Advanced Malicious Package Analysis**](#-advanced-malicious-package-analysis)
- [🔒 **Security Features**](#-security-features)
- [📊 Privacy and Telemetry](#-privacy-and-telemetry)
- [🎊 Community \& Support](#-community--support)
- [🌟 **Join the Community**](#-join-the-community)
- [💡 **Get Help \& Share Ideas**](#-get-help--share-ideas)
- [⭐ **Star History**](#-star-history)
- [🙏 **Built With Open Source**](#-built-with-open-source)
## 📦 Installation Options
### 🍺 **Homebrew (Recommended)**
```bash
brew tap safedep/tap
brew install safedep/tap/vet
```
### 📥 **Direct Download**
See [releases](https://github.com/safedep/vet/releases) for the latest version.
### 🐹 **Go Install**
```bash
go install github.com/safedep/vet@latest
```
Alternatively, look at [Releases](https://github.com/safedep/vet/releases) for
a pre-built binary for your platform. [SLSA Provenance](https://slsa.dev/provenance/v0.1) is published
along with each binary release.
Get a trial API key for [Insights API](https://safedep.io/docs/concepts/raya-data-platform-overview) access
### 🐳 **Container Image**
```bash
vet auth trial --email john.doe@example.com
# Quick test
docker run --rm ghcr.io/safedep/vet:latest version
# Scan local directory
docker run --rm -v $(pwd):/workspace ghcr.io/safedep/vet:latest scan -D /workspace
```
> A time limited trial API key will be sent over email.
Configure `vet` to use API Key to access [Insights API](https://safedep.io/docs/concepts/raya-data-platform-overview)
### ⚙️ **Verify Installation**
```bash
vet auth configure
vet version
# Should display version and build information
```
> Insights API is used to enrich OSS packages with meta-data for rich query and policy
> decisions
## 🎮 Advanced Usage
Run `vet` to identify risks
### 🔍 **Scanning Options**
<table>
<tr>
<td width="50%">
**📁 Directory Scanning**
```bash
vet scan -D /path/to/repository
# Scan current directory
vet scan
# Scan a given directory
vet scan -D /path/to/project
# Resolve and scan transitive dependencies
vet scan -D . --transitive
```
or scan a specific (supported) package manifest
**📄 Manifest Files**
```bash
vet scan --lockfiles /path/to/pom.xml
vet scan --lockfiles /path/to/requirements.txt
vet scan --lockfiles /path/to/package-lock.json
# Package managers
vet scan -M package-lock.json
vet scan -M requirements.txt
vet scan -M pom.xml
vet scan -M go.mod
vet scan -M Gemfile.lock
```
> Use `vet scan parsers` to list supported package manifest parsers
</td>
<td width="50%">
The default scan uses an opinionated [Console Reporter](#) which presents
a summary of findings per package manifest. Thats NOT about it. Read more for
expression based filtering and policy evaluation.
## Filtering
Find dependencies that seems not very popular
**🐙 GitHub Integration**
```bash
vet scan --lockfiles /path/to/pom.xml --report-console=false \
--filter='projects.exists(x, x.stars < 10)'
# Setup GitHub access
vet connect github
# Scan repositories
vet scan --github https://github.com/user/repo
# Organization scanning
vet scan --github-org https://github.com/org
```
Find dependencies with a critical vulnerability
**📦 Artifact Scanning**
```bash
vet scan --lockfiles /path/to/pom.xml --report-console=false \
--filter='vulns.critical.exists_one(x, true)'
# Container images
vet scan --image nginx:latest
vet scan --image /path/to/image-saved-file.tar
# Binary artifacts
vet scan -M app.jar
vet scan -M package.whl
```
> Use filtering along with `query` command for offline slicing and dicing of
> enriched package manifests. Read [filtering guide](docs/filtering.md)
</td>
</tr>
</table>
[Common Expressions Language](https://github.com/google/cel-spec) is used to
evaluate filters on packages. Learn more about [filtering with vet](docs/filtering.md).
Look at [filter input spec](api/filter_input_spec.proto) on attributes
available to the filter expression.
### 🎯 **Policy Enforcement Examples**
## Policy Evaluation
```bash
# Security-first scanning
vet scan -D . \
--filter 'vulns.critical.exists(p, true) || vulns.high.exists(p, true)' \
--filter-fail
TODO
# License compliance
vet scan -D . \
--filter 'licenses.contains_license("GPL-3.0")' \
--filter-fail
## FAQ
# OpenSSF Scorecard requirements
vet scan -D . \
--filter 'scorecard.scores.Maintained < 5' \
--filter-fail
### How do I disable the stupid banner?
# Popularity-based filtering
vet scan -D . \
--filter 'projects.exists(p, p.type == "GITHUB" && p.stars < 50)' \
--filter-fail
```
Set environment variable `VET_DISABLE_BANNER=1`
### 🔧 **SBOM Support**
### Can I use this tool without an API Key for Insight Service?
```bash
# Scan a CycloneDX SBOM
vet scan -M sbom.json --type bom-cyclonedx
Probably no. All useful data (enrichments) for a detected package comes from
a backend service. The service is rate limited with quotas to prevent abuse.
# Scan a SPDX SBOM
vet scan -M sbom.spdx.json --type bom-spdx
Look at `api/insights-v1.yml`. It contains the contract expected for Insights
API. You can perhaps consider rolling out your own to avoid dependency with our
backend.
# Generate SBOM output
vet scan -D . --report-cdx=output.sbom.json
## References
# Package URL scanning
vet scan --purl pkg:npm/lodash@4.17.21
```
* https://github.com/google/osv-scanner
### 📊 **Query Mode & Data Persistence**
For large codebases and repeated analysis:
```bash
# Scan once, query multiple times
vet scan -D . --json-dump-dir ./scan-data
# Query with different filters
vet query --from ./scan-data \
--filter 'vulns.critical.exists(p, true)'
# Generate focused reports
vet query --from ./scan-data \
--filter 'licenses.contains_license("GPL")' \
--report-json license-violations.json
```
## 📊 Reporting
**vet** generate reports that are tailored for different stakeholders:
### 📋 **Report Formats**
<table>
<tr>
<td width="30%"><strong>🔍 For Security Teams</strong></td>
<td width="70%">
```bash
# SARIF for GitHub Security tab
vet scan -D . --report-sarif=report.sarif
# JSON for custom tooling
vet scan -D . --report-json=report.json
# CSV for spreadsheet analysis
vet scan -D . --report-csv=report.csv
# HTML for web-based analysis
vet scan -D . --report-html=report.html
```
</td>
</tr>
<tr>
<td><strong>📖 For Developers</strong></td>
<td>
```bash
# Markdown reports for PRs
vet scan -D . --report-markdown=report.md
# Console summary (default)
vet scan -D . --report-summary
```
</td>
</tr>
<tr>
<td><strong>🏢 For Compliance</strong></td>
<td>
```bash
# SBOM generation
vet scan -D . --report-cdx=sbom.json
# Dependency graphs
vet scan -D . --report-graph=dependencies.dot
```
</td>
</tr>
</table>
### 🎯 **Report Examples**
```bash
# Multi-format output
vet scan -D . \
--report-json=report.json \
--report-sarif=report.sarif \
--report-markdown=report.md \
--report-html=report.html
# Focus on specific issues
vet scan -D . \
--filter 'vulns.high.exists(p, true)' \
--report-json=report.json
```
### 🤖 **MCP Server**
**vet** can be used as an MCP server to vet open source packages from AI suggested code.
```bash
# Start the MCP server with SSE transport
vet server mcp --server-type sse
```
For more details, see [vet MCP Server](./docs/mcp.md) documentation.
### 🤖 **Agents**
See [vet Agents](./docs/agent.md) documentation for more details.
## 🛡️ Malicious Package Detection
**Malicious package detection through active scanning and code analysis** powered by
[SafeDep Cloud](https://docs.safedep.io/cloud/malware-analysis). `vet` requires an API
key for active scanning of unknown packages. When API key is not provided, `vet` will
fallback to _Query Mode_ which detects known malicious packages from [SafeDep](https://safedep.io)
and [OSV](https://osv.dev) databases.
- Grab a free API key by running `vet cloud quickstart`
- API access is free forever for open source projects
- No proprietary code is collected for malicious package detection
- Only open source package scanning from public repositories is supported
### 🚀 **Quick Setup**
> Malicious package detection requires an API key for [SafeDep Cloud](https://docs.safedep.io/cloud/malware-analysis).
```bash
# One-time setup
vet cloud quickstart
# Enable malware scanning
vet scan -D . --malware
# Query for known malicious packages without API key
vet scan -D . --malware-query
```
Example malicious packages detected and reported by [SafeDep Cloud](https://docs.safedep.io/cloud/malware-analysis)
malicious package detection:
- [MAL-2025-3541: express-cookie-parser](https://safedep.io/malicious-npm-package-express-cookie-parser/)
- [MAL-2025-4339: eslint-config-airbnb-compat](https://safedep.io/digging-into-dynamic-malware-analysis-signals/)
- [MAL-2025-4029: ts-runtime-compat-check](https://safedep.io/digging-into-dynamic-malware-analysis-signals/)
- [MAL-2025-2227: nyc-config](https://safedep.io/nyc-config-malicious-package/)
### 🎯 **Advanced Malicious Package Analysis**
<table>
<tr>
<td width="50%">
**🔍 Scan packages with malicious package detection enabled**
```bash
# Real-time scanning
vet scan -D . --malware
# Timeout adjustment
vet scan -D . --malware \
--malware-analysis-timeout=300s
# Batch analysis
vet scan -D . --malware \
--json-dump-dir=./analysis
```
</td>
<td width="50%">
**🎭 Specialized Scans**
```bash
# VS Code extensions
vet scan --vsx --malware
# GitHub Actions
vet scan -D .github/workflows --malware
# Container Images
vet scan --image nats:2.10 --malware
# Scan a single package and fail if its malicious
vet scan --purl pkg:/npm/nyc-config@10.0.0 --fail-fast
# Active scanning of a single package (requires API key)
vet inspect malware \
--purl pkg:npm/nyc-config@10.0.0
```
</td>
</tr>
</table>
### 🔒 **Security Features**
- ✅ **Real-time analysis** of packages against known malware databases
- ✅ **Behavioral analysis** using static and dynamic analysis
- ✅ **Zero day protection** through active code scanning
- ✅ **Human in the loop** for triaging and investigation of high impact findings
- ✅ **Real time analysis** with public [analysis log](https://vetpkg.dev/mal)
## 📊 Privacy and Telemetry
`vet` collects anonymous usage telemetry to improve the product. **Your code and package information is never transmitted.**
```bash
# Disable telemetry (optional)
export VET_DISABLE_TELEMETRY=true
```
## 🎊 Community & Support
<div align="center">
### 🌟 **Join the Community**
[![Discord](https://img.shields.io/discord/1090352019379851304?color=7289da&label=Discord&logo=discord&logoColor=white)](https://rebrand.ly/safedep-community)
[![GitHub Discussions](https://img.shields.io/badge/GitHub-Discussions-green?logo=github)](https://github.com/safedep/vet/discussions)
[![Twitter Follow](https://img.shields.io/twitter/follow/safedepio?style=social)](https://twitter.com/safedepio)
</div>
### 💡 **Get Help & Share Ideas**
- 🚀 **[Interactive Tutorial](https://killercoda.com/safedep/scenario/101-intro)** - Learn vet hands-on
- 📚 **[Complete Documentation](https://docs.safedep.io/)** - Comprehensive guides
- 💬 **[Discord Community](https://rebrand.ly/safedep-community)** - Real-time support
- 🐛 **[Issue Tracker](https://github.com/safedep/vet/issues)** - Bug reports & feature requests
- 🤝 **[Contributing Guide](CONTRIBUTING.md)** - Join the development
---
<div align="center">
### ⭐ **Star History**
[![Star History Chart](https://api.star-history.com/svg?repos=safedep/vet&type=Date)](https://star-history.com/#safedep/vet&Date)
### 🙏 **Built With Open Source**
vet stands on the shoulders of giants:
[OSV](https://osv.dev) • [OpenSSF Scorecard](https://securityscorecards.dev/) • [SLSA](https://slsa.dev/) • [OSV-SCALIBR](https://github.com/google/osv-scalibr) • [Syft](https://github.com/anchore/syft)
---
<p><strong>⚡ Secure your supply chain today. Star the repo ⭐ and get started!</strong></p>
Created with ❤️ by [SafeDep](https://safedep.io) and the open source community
</div>
<img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=304d1856-fcb3-4166-bfbf-b3e40d0f1e3b" />

69
agent/agent.go Normal file
View File

@ -0,0 +1,69 @@
// Package agent declares the building blocks for implement vet agent.
package agent
import (
"context"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
)
type Input struct {
Query string
}
type AnswerFormat string
const (
AnswerFormatMarkdown AnswerFormat = "markdown"
AnswerFormatJSON AnswerFormat = "json"
)
type Output struct {
Answer string
Format AnswerFormat
}
type Memory interface {
AddInteraction(ctx context.Context, interaction *schema.Message) error
GetInteractions(ctx context.Context) ([]*schema.Message, error)
Clear(ctx context.Context) error
}
type Session interface {
ID() string
Memory() Memory
}
// AgentExecutionContext is to pass additional context to the agent
// on a per execution basis. This is required so that an agent can be configured
// and shared with different components while allowing the component to pass
// additional context to the agent.
type AgentExecutionContext struct {
// OnToolCall is called when the agent is about to call a tool.
// This is used for introspection only and not to mutate the agent's behavior.
OnToolCall func(context.Context, Session, Input, string, string) error
}
type AgentExecutionContextOpt func(*AgentExecutionContext)
func WithToolCallHook(fn func(context.Context, Session, Input, string, string) error) AgentExecutionContextOpt {
return func(a *AgentExecutionContext) {
a.OnToolCall = fn
}
}
type Agent interface {
// Execute executes the agent with the given input and returns the output.
// Internally the agent may perform a multi-step operation based on config,
// instructions and available tools.
Execute(context.Context, Session, Input, ...AgentExecutionContextOpt) (Output, error)
}
// AgentToolCallIntrospectionFn is a function that introspects a tool call.
// This is aligned with eino contract.
type AgentToolCallIntrospectionFn func(context.Context /* name */, string /* args */, string) ( /* args */ string, error)
type ToolBuilder interface {
Build(context.Context) ([]tool.BaseTool, error)
}

165
agent/llm.go Normal file
View File

@ -0,0 +1,165 @@
package agent
import (
"context"
"fmt"
"os"
"github.com/cloudwego/eino-ext/components/model/claude"
"github.com/cloudwego/eino-ext/components/model/gemini"
"github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/components/model"
"google.golang.org/genai"
)
// Map of fast vs. default models.
var defaultModelMap = map[string]map[string]string{
"openai": {
"default": "gpt-4o",
"fast": "gpt-4o-mini",
},
"claude": {
"default": "claude-sonnet-4-20250514",
"fast": "claude-sonnet-4-20250514",
},
"gemini": {
"default": "gemini-2.5-pro",
"fast": "gemini-2.5-flash",
},
}
type Model struct {
Vendor string
Name string
Fast bool
Client model.ToolCallingChatModel
}
// BuildModelFromEnvironment builds a model from the environment variables.
// The order of preference is:
// 1. OpenAI
// 2. Claude
// 3. Gemini
// 4. Others..
func BuildModelFromEnvironment(fastMode bool) (*Model, error) {
if model, err := buildOpenAIModelFromEnvironment(fastMode); err == nil {
return model, nil
}
if model, err := buildClaudeModelFromEnvironment(fastMode); err == nil {
return model, nil
}
if model, err := buildGeminiModelFromEnvironment(fastMode); err == nil {
return model, nil
}
return nil, fmt.Errorf("no usable LLM found for use with agent")
}
func buildOpenAIModelFromEnvironment(fastMode bool) (*Model, error) {
defaultModel := defaultModelMap["openai"]["default"]
if fastMode {
defaultModel = defaultModelMap["openai"]["fast"]
}
modelName := os.Getenv("OPENAI_MODEL_OVERRIDE")
if modelName == "" {
modelName = defaultModel
}
apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
return nil, fmt.Errorf("OPENAI_API_KEY is not set")
}
model, err := openai.NewChatModel(context.Background(), &openai.ChatModelConfig{
Model: modelName,
APIKey: apiKey,
})
if err != nil {
return nil, fmt.Errorf("failed to create openai model: %w", err)
}
return &Model{
Vendor: "openai",
Name: modelName,
Fast: fastMode,
Client: model,
}, nil
}
func buildClaudeModelFromEnvironment(fastMode bool) (*Model, error) {
defaultModel := defaultModelMap["claude"]["default"]
if fastMode {
defaultModel = defaultModelMap["claude"]["fast"]
}
modelName := os.Getenv("ANTHROPIC_MODEL_OVERRIDE")
if modelName == "" {
modelName = defaultModel
}
apiKey := os.Getenv("ANTHROPIC_API_KEY")
if apiKey == "" {
return nil, fmt.Errorf("ANTHROPIC_API_KEY is not set")
}
model, err := claude.NewChatModel(context.Background(), &claude.Config{
Model: modelName,
APIKey: apiKey,
})
if err != nil {
return nil, fmt.Errorf("failed to create claude model: %w", err)
}
return &Model{
Vendor: "claude",
Name: modelName,
Fast: fastMode,
Client: model,
}, nil
}
func buildGeminiModelFromEnvironment(fastMode bool) (*Model, error) {
defaultModel := defaultModelMap["gemini"]["default"]
if fastMode {
defaultModel = defaultModelMap["gemini"]["fast"]
}
modelName := os.Getenv("GEMINI_MODEL_OVERRIDE")
if modelName == "" {
modelName = defaultModel
}
apiKey := os.Getenv("GEMINI_API_KEY")
if apiKey == "" {
return nil, fmt.Errorf("GEMINI_API_KEY is not set")
}
client, err := genai.NewClient(context.Background(), &genai.ClientConfig{
APIKey: apiKey,
})
if err != nil {
return nil, fmt.Errorf("failed to create gemini client: %w", err)
}
model, err := gemini.NewChatModel(context.Background(), &gemini.Config{
Model: modelName,
Client: client,
ThinkingConfig: &genai.ThinkingConfig{
IncludeThoughts: false,
ThinkingBudget: nil,
},
})
if err != nil {
return nil, fmt.Errorf("failed to create gemini model: %w", err)
}
return &Model{
Vendor: "gemini",
Name: modelName,
Fast: fastMode,
Client: model,
}, nil
}

17
agent/llm_test.go Normal file
View File

@ -0,0 +1,17 @@
package agent
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDefaultModelsMap(t *testing.T) {
t.Run("default model map must have vendor, model, and fast model", func(t *testing.T) {
for vendor, models := range defaultModelMap {
assert.NotEmpty(t, vendor)
assert.NotEmpty(t, models["default"])
assert.NotEmpty(t, models["fast"])
}
})
}

145
agent/mcp.go Normal file
View File

@ -0,0 +1,145 @@
package agent
import (
"context"
"fmt"
"os"
"path/filepath"
einomcp "github.com/cloudwego/eino-ext/components/tool/mcp"
"github.com/cloudwego/eino/components/tool"
"github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
)
type McpClientToolBuilderConfig struct {
// Common config
ClientName string
ClientVersion string
// SSE client config
SseURL string
Headers map[string]string
// Stdout client config
SkipDefaultTools bool
SQLQueryToolEnabled bool
SQLQueryToolDBPath string
PackageRegistryToolEnabled bool
// Enable debug mode for the MCP client.
Debug bool
}
type mcpClientToolBuilder struct {
config McpClientToolBuilderConfig
}
var _ ToolBuilder = (*mcpClientToolBuilder)(nil)
// NewMcpClientToolBuilder creates a new MCP client tool builder for `vet` MCP server.
// This basically connects to vet MCP server over SSE or executes the `vet server mcp` command
// to start a MCP server in stdio mode. We maintain loose coupling between the MCP client and the MCP server
// by allowing the client to be configured with a set of flags to enable/disable specific tools. We do this
// to ensure vet MCP contract is not violated and evolves independently. vet Agents will in turn depend on
// vet MCP server for data access.
func NewMcpClientToolBuilder(config McpClientToolBuilderConfig) (*mcpClientToolBuilder, error) {
return &mcpClientToolBuilder{
config: config,
}, nil
}
func (b *mcpClientToolBuilder) Build(ctx context.Context) ([]tool.BaseTool, error) {
var cli *client.Client
var err error
if b.config.SseURL != "" {
cli, err = b.buildSseClient()
if err != nil {
return nil, fmt.Errorf("failed to create sse client: %w", err)
}
} else {
cli, err = b.buildStdioClient()
if err != nil {
return nil, fmt.Errorf("failed to create stdio client: %w", err)
}
}
err = cli.Start(ctx)
if err != nil {
return nil, fmt.Errorf("failed to start mcp client: %w", err)
}
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
Name: b.config.ClientName,
Version: b.config.ClientVersion,
}
_, err = cli.Initialize(ctx, initRequest)
if err != nil {
return nil, fmt.Errorf("failed to initialize mcp client: %w", err)
}
tools, err := einomcp.GetTools(ctx, &einomcp.Config{
Cli: cli,
})
if err != nil {
return nil, fmt.Errorf("failed to get tools: %w", err)
}
return tools, nil
}
func (b *mcpClientToolBuilder) buildSseClient() (*client.Client, error) {
cli, err := client.NewSSEMCPClient(b.config.SseURL, client.WithHeaders(b.config.Headers))
if err != nil {
return nil, fmt.Errorf("failed to create sse client: %w", err)
}
return cli, nil
}
// buildStdioClient is used to start vet mcp server with arguments
// based on the configuration.
func (b *mcpClientToolBuilder) buildStdioClient() (*client.Client, error) {
binaryPath, err := os.Executable()
if err != nil {
return nil, fmt.Errorf("failed to get running binary path: %w", err)
}
// vet-mcp server defaults to stdio transport. See cmd/server/mcp.go
vetMcpServerCommandArgs := []string{"server", "mcp"}
if b.config.Debug {
vetMcpServerLogFile := filepath.Join(os.TempDir(), "vet-mcp-server.log")
vetMcpServerCommandArgs = append(vetMcpServerCommandArgs, "-l", vetMcpServerLogFile)
}
if b.config.SQLQueryToolEnabled {
vetMcpServerCommandArgs = append(vetMcpServerCommandArgs, "--sql-query-tool")
vetMcpServerCommandArgs = append(vetMcpServerCommandArgs, "--sql-query-tool-db-path",
b.config.SQLQueryToolDBPath)
}
if b.config.PackageRegistryToolEnabled {
vetMcpServerCommandArgs = append(vetMcpServerCommandArgs, "--package-registry-tool")
}
if b.config.SkipDefaultTools {
vetMcpServerCommandArgs = append(vetMcpServerCommandArgs, "--skip-default-tools")
}
environmentVariables := []string{}
if b.config.Debug {
environmentVariables = append(environmentVariables, "APP_LOG_LEVEL=debug")
}
cli, err := client.NewStdioMCPClient(binaryPath, environmentVariables, vetMcpServerCommandArgs...)
if err != nil {
return nil, fmt.Errorf("failed to create stdio client: %w", err)
}
return cli, nil
}

45
agent/memory.go Normal file
View File

@ -0,0 +1,45 @@
package agent
import (
"context"
"sync"
"github.com/cloudwego/eino/schema"
)
type simpleMemory struct {
mutex sync.RWMutex
interactions []*schema.Message
}
var _ Memory = (*simpleMemory)(nil)
func NewSimpleMemory() (*simpleMemory, error) {
return &simpleMemory{
interactions: make([]*schema.Message, 0),
}, nil
}
func (m *simpleMemory) AddInteraction(ctx context.Context, interaction *schema.Message) error {
m.mutex.Lock()
defer m.mutex.Unlock()
m.interactions = append(m.interactions, interaction)
return nil
}
func (m *simpleMemory) GetInteractions(ctx context.Context) ([]*schema.Message, error) {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.interactions, nil
}
func (m *simpleMemory) Clear(ctx context.Context) error {
m.mutex.Lock()
defer m.mutex.Unlock()
m.interactions = make([]*schema.Message, 0)
return nil
}

279
agent/memory_test.go Normal file
View File

@ -0,0 +1,279 @@
package agent
import (
"context"
"fmt"
"sync"
"testing"
"github.com/cloudwego/eino/schema"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewSimpleMemory(t *testing.T) {
memory, err := NewSimpleMemory()
require.NoError(t, err)
assert.NotNil(t, memory)
// Test that initial interactions are empty
ctx := context.Background()
interactions, err := memory.GetInteractions(ctx)
require.NoError(t, err)
assert.Equal(t, 0, len(interactions))
}
func TestSimpleMemory_AddInteraction(t *testing.T) {
memory, err := NewSimpleMemory()
require.NoError(t, err)
ctx := context.Background()
message := &schema.Message{
Role: schema.User,
Content: "test message",
}
err = memory.AddInteraction(ctx, message)
assert.NoError(t, err)
interactions, err := memory.GetInteractions(ctx)
require.NoError(t, err)
assert.Equal(t, 1, len(interactions))
assert.Equal(t, message, interactions[0])
}
func TestSimpleMemory_AddMultipleInteractions(t *testing.T) {
memory, err := NewSimpleMemory()
require.NoError(t, err)
ctx := context.Background()
messages := []*schema.Message{
{Role: schema.User, Content: "first message"},
{Role: schema.Assistant, Content: "second message"},
{Role: schema.User, Content: "third message"},
}
for _, msg := range messages {
err = memory.AddInteraction(ctx, msg)
assert.NoError(t, err)
}
interactions, err := memory.GetInteractions(ctx)
require.NoError(t, err)
assert.Equal(t, 3, len(interactions))
for i, msg := range messages {
assert.Equal(t, msg, interactions[i])
}
}
func TestSimpleMemory_GetInteractions(t *testing.T) {
memory, err := NewSimpleMemory()
require.NoError(t, err)
ctx := context.Background()
// Test empty interactions
interactions, err := memory.GetInteractions(ctx)
assert.NoError(t, err)
assert.NotNil(t, interactions)
assert.Equal(t, 0, len(interactions))
// Add some interactions
message1 := &schema.Message{Role: schema.User, Content: "message 1"}
message2 := &schema.Message{Role: schema.Assistant, Content: "message 2"}
err = memory.AddInteraction(ctx, message1)
require.NoError(t, err)
err = memory.AddInteraction(ctx, message2)
require.NoError(t, err)
interactions, err = memory.GetInteractions(ctx)
assert.NoError(t, err)
assert.Equal(t, 2, len(interactions))
assert.Equal(t, message1, interactions[0])
assert.Equal(t, message2, interactions[1])
}
func TestSimpleMemory_Clear(t *testing.T) {
memory, err := NewSimpleMemory()
require.NoError(t, err)
ctx := context.Background()
// Add some interactions
message1 := &schema.Message{Role: schema.User, Content: "message 1"}
message2 := &schema.Message{Role: schema.Assistant, Content: "message 2"}
err = memory.AddInteraction(ctx, message1)
require.NoError(t, err)
err = memory.AddInteraction(ctx, message2)
require.NoError(t, err)
// Verify interactions exist
interactions, err := memory.GetInteractions(ctx)
require.NoError(t, err)
assert.Equal(t, 2, len(interactions))
// Clear interactions
err = memory.Clear(ctx)
assert.NoError(t, err)
// Verify interactions are cleared
interactions, err = memory.GetInteractions(ctx)
assert.NoError(t, err)
assert.Equal(t, 0, len(interactions))
}
func TestSimpleMemory_NilInteraction(t *testing.T) {
memory, err := NewSimpleMemory()
require.NoError(t, err)
ctx := context.Background()
// Test adding nil interaction
err = memory.AddInteraction(ctx, nil)
assert.NoError(t, err)
interactions, err := memory.GetInteractions(ctx)
require.NoError(t, err)
assert.Equal(t, 1, len(interactions))
assert.Nil(t, interactions[0])
}
func TestSimpleMemory_ConcurrentAccess(t *testing.T) {
memory, err := NewSimpleMemory()
require.NoError(t, err)
ctx := context.Background()
numGoroutines := 100
messagesPerGoroutine := 10
var wg sync.WaitGroup
wg.Add(numGoroutines)
// Concurrent writes
for i := 0; i < numGoroutines; i++ {
go func(goroutineID int) {
defer wg.Done()
for j := 0; j < messagesPerGoroutine; j++ {
message := &schema.Message{
Role: schema.User,
Content: fmt.Sprintf("goroutine-%d-message-%d", goroutineID, j),
}
err := memory.AddInteraction(ctx, message)
assert.NoError(t, err)
}
}(i)
}
wg.Wait()
// Verify all interactions were added
interactions, err := memory.GetInteractions(ctx)
require.NoError(t, err)
assert.Equal(t, numGoroutines*messagesPerGoroutine, len(interactions))
}
func TestSimpleMemory_ConcurrentReadWrite(t *testing.T) {
memory, err := NewSimpleMemory()
require.NoError(t, err)
ctx := context.Background()
numReaders := 10
numWriters := 10
messagesPerWriter := 5
var wg sync.WaitGroup
wg.Add(numReaders + numWriters)
// Concurrent writers
for i := 0; i < numWriters; i++ {
go func(writerID int) {
defer wg.Done()
for j := 0; j < messagesPerWriter; j++ {
message := &schema.Message{
Role: schema.User,
Content: fmt.Sprintf("writer-%d-message-%d", writerID, j),
}
err := memory.AddInteraction(ctx, message)
assert.NoError(t, err)
}
}(i)
}
// Concurrent readers
for i := 0; i < numReaders; i++ {
go func() {
defer wg.Done()
for j := 0; j < messagesPerWriter; j++ {
interactions, err := memory.GetInteractions(ctx)
assert.NoError(t, err)
assert.NotNil(t, interactions)
// Length can vary due to concurrent writes
assert.GreaterOrEqual(t, len(interactions), 0)
}
}()
}
wg.Wait()
// Final verification
interactions, err := memory.GetInteractions(ctx)
require.NoError(t, err)
assert.Equal(t, numWriters*messagesPerWriter, len(interactions))
}
func TestSimpleMemory_ClearDuringConcurrentAccess(t *testing.T) {
memory, err := NewSimpleMemory()
require.NoError(t, err)
ctx := context.Background()
numWriters := 5
messagesPerWriter := 10
var wg sync.WaitGroup
wg.Add(numWriters + 1) // +1 for the clearer
// Add some initial interactions
for i := 0; i < 5; i++ {
message := &schema.Message{
Role: schema.User,
Content: fmt.Sprintf("initial-message-%d", i),
}
err := memory.AddInteraction(ctx, message)
require.NoError(t, err)
}
// Concurrent writers
for i := 0; i < numWriters; i++ {
go func(writerID int) {
defer wg.Done()
for j := 0; j < messagesPerWriter; j++ {
message := &schema.Message{
Role: schema.User,
Content: fmt.Sprintf("writer-%d-message-%d", writerID, j),
}
err := memory.AddInteraction(ctx, message)
assert.NoError(t, err)
}
}(i)
}
// Clear operation
go func() {
defer wg.Done()
// Clear after some writes have happened
err := memory.Clear(ctx)
assert.NoError(t, err)
}()
wg.Wait()
// Final state check - should be consistent
interactions, err := memory.GetInteractions(ctx)
require.NoError(t, err)
assert.NotNil(t, interactions)
// The exact number depends on timing of clear operation
assert.GreaterOrEqual(t, len(interactions), 0)
}

156
agent/mock.go Normal file
View File

@ -0,0 +1,156 @@
package agent
import (
"context"
"fmt"
"strings"
"github.com/cloudwego/eino/schema"
)
// MockAgent provides a simple implementation of the Agent interface for testing
type mockAgent struct{}
// MockSession is a simple session implementation
type mockSession struct {
sessionID string
memory Memory
}
type mockMemory struct {
interactions []*schema.Message
}
func (m *mockMemory) AddInteraction(ctx context.Context, interaction *schema.Message) error {
m.interactions = append(m.interactions, interaction)
return nil
}
func (m *mockMemory) GetInteractions(ctx context.Context) ([]*schema.Message, error) {
return m.interactions, nil
}
func (m *mockMemory) Clear(ctx context.Context) error {
m.interactions = make([]*schema.Message, 0)
return nil
}
// NewMockAgent creates a new mock agent
func NewMockAgent() *mockAgent {
return &mockAgent{}
}
// NewMockSession creates a new mock session
func NewMockSession() *mockSession {
return &mockSession{
sessionID: "mock-session-1",
memory: &mockMemory{},
}
}
func (s *mockSession) ID() string {
return s.sessionID
}
func (s *mockSession) Memory() Memory {
return s.memory
}
// Execute implements the Agent interface with mock responses
func (m *mockAgent) Execute(ctx context.Context, session Session, input Input, opts ...AgentExecutionContextOpt) (Output, error) {
// Simple mock responses based on input
query := strings.ToLower(input.Query)
var response string
switch {
case strings.Contains(query, "vulnerability") || strings.Contains(query, "vuln"):
response = `🔍 **Vulnerability Analysis**
I found 3 critical vulnerabilities in your dependencies:
**Critical Issues:**
lodash@4.17.19: CVE-2021-23337 (Command Injection)
jackson-databind@2.9.8: CVE-2020-36518 (Deserialization)
urllib3@1.24.1: CVE-2021-33503 (SSRF)
**Recommendation:** Update these packages immediately. All have fixes available in newer versions.
Would you like me to analyze the impact of updating these packages?`
case strings.Contains(query, "malware") || strings.Contains(query, "malicious"):
response = `🚨 **Malware Detection Results**
I detected 2 potentially malicious packages:
**High Risk:**
suspicious-package@1.0.0: Contains obfuscated code and cryptocurrency mining
typosquatted-lib@2.1.0: Mimics popular library with malicious payload
**Action Required:** Remove these packages immediately and scan your systems.
Would you like me to suggest secure alternatives?`
case strings.Contains(query, "secure") || strings.Contains(query, "security"):
response = `🛡 **Security Posture Assessment**
**Overall Security Score: 6.2/10 (Moderate Risk)**
**Summary:**
23 total security issues found
3 critical vulnerabilities requiring immediate action
2 malicious packages detected
15 packages with maintenance concerns
**Priority Actions:**
1. Remove malicious packages (Critical)
2. Update vulnerable dependencies (High)
3. Implement dependency scanning in CI/CD (Medium)
Would you like me to create a detailed remediation plan?`
case strings.Contains(query, "update"):
response = ` **Update Analysis**
Analyzing update recommendations for your dependencies...
**Safe Updates Available:**
12 packages can be safely updated (patch versions)
5 packages have minor version updates with new features
3 packages require major version updates (breaking changes)
**Priority Updates:**
1. lodash: 4.17.19 4.17.21 (Security fix, no breaking changes)
2. urllib3: 1.24.1 1.26.18 (Security fix, minimal risk)
Would you like detailed impact analysis for any specific package?`
default:
response = fmt.Sprintf(`🤖 **Security Analysis**
I'm analyzing your question about: "%s"
I have access to comprehensive security data including:
Vulnerability databases
Malware detection results
Dependency analysis
License compliance
Maintainer health metrics
**Available Analysis Types:**
Security posture assessment
Vulnerability impact analysis
Malware detection
Update recommendations
Compliance checking
What specific aspect would you like me to analyze in detail?`, input.Query)
}
return Output{
Answer: response,
Format: AnswerFormatMarkdown,
}, nil
}

164
agent/react.go Normal file
View File

@ -0,0 +1,164 @@
package agent
import (
"context"
"encoding/json"
"fmt"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/components/tool"
einoutils "github.com/cloudwego/eino/components/tool/utils"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/flow/agent/react"
"github.com/cloudwego/eino/schema"
)
type ReactQueryAgentConfig struct {
MaxSteps int
SystemPrompt string
}
type reactQueryAgent struct {
config ReactQueryAgentConfig
model model.ToolCallingChatModel
tools []tool.BaseTool
}
var _ Agent = (*reactQueryAgent)(nil)
type reactQueryAgentOpt func(*reactQueryAgent)
func WithTools(tools []tool.BaseTool) reactQueryAgentOpt {
return func(a *reactQueryAgent) {
a.tools = tools
}
}
func NewReactQueryAgent(model model.ToolCallingChatModel,
config ReactQueryAgentConfig, opts ...reactQueryAgentOpt,
) (*reactQueryAgent, error) {
a := &reactQueryAgent{
config: config,
model: model,
}
for _, opt := range opts {
opt(a)
}
if a.config.MaxSteps == 0 {
a.config.MaxSteps = 30
}
return a, nil
}
func (a *reactQueryAgent) Execute(ctx context.Context, session Session, input Input, opts ...AgentExecutionContextOpt) (Output, error) {
executionContext := &AgentExecutionContext{}
for _, opt := range opts {
opt(executionContext)
}
agent, err := react.NewAgent(ctx, &react.AgentConfig{
ToolCallingModel: a.model,
ToolsConfig: compose.ToolsNodeConfig{
Tools: a.wrapToolsForError(a.tools),
ToolArgumentsHandler: func(ctx context.Context, name string, arguments string) (string, error) {
// Only allow introspection if the function is provided. Do not allow mutation.
if executionContext.OnToolCall != nil {
_ = executionContext.OnToolCall(ctx, session, input, name, arguments)
}
return arguments, nil
},
},
MaxStep: a.config.MaxSteps,
})
if err != nil {
return Output{}, fmt.Errorf("failed to create react agent: %w", err)
}
var messages []*schema.Message
// Start with the system prompt if available
if a.config.SystemPrompt != "" {
messages = append(messages, &schema.Message{
Role: schema.System,
Content: a.config.SystemPrompt,
})
}
// Add the previous interactions to the messages
interactions, err := session.Memory().GetInteractions(ctx)
if err != nil {
return Output{}, fmt.Errorf("failed to get session memory: %w", err)
}
// TODO: Add a limit to the number of interactions to avoid context bloat
messages = append(messages, interactions...)
// Add the current user query message to the messages
userQueryMsg := &schema.Message{
Role: schema.User,
Content: input.Query,
}
messages = append(messages, userQueryMsg)
// Execute the agent to produce a response
msg, err := agent.Generate(ctx, messages)
if err != nil {
return Output{}, fmt.Errorf("failed to generate response: %w", err)
}
// Add the user query message to the session memory
err = session.Memory().AddInteraction(ctx, userQueryMsg)
if err != nil {
return Output{}, fmt.Errorf("failed to add user query message to session memory: %w", err)
}
// Add the agent response message to the session memory
err = session.Memory().AddInteraction(ctx, msg)
if err != nil {
return Output{}, fmt.Errorf("failed to add response message to session memory: %w", err)
}
return Output{
Answer: a.schemaContent(msg),
}, nil
}
func (a *reactQueryAgent) wrapToolsForError(tools []tool.BaseTool) []tool.BaseTool {
wrappedTools := make([]tool.BaseTool, len(tools))
for i, tool := range tools {
wrappedTools[i] = einoutils.WrapToolWithErrorHandler(tool, func(_ context.Context, err error) string {
errorMessage := map[string]string{
"error": err.Error(),
"suggestion": "Tool call failed, Please try a different approach or check your input.",
}
encodedError, err := json.Marshal(errorMessage)
if err != nil {
return ""
}
return string(encodedError)
})
}
return wrappedTools
}
func (a *reactQueryAgent) schemaContent(msg *schema.Message) string {
content := msg.Content
if len(msg.MultiContent) > 0 {
content = ""
for _, part := range msg.MultiContent {
content += part.Text + "\n"
}
}
return content
}

29
agent/session.go Normal file
View File

@ -0,0 +1,29 @@
package agent
import "github.com/google/uuid"
type session struct {
sessionID string
memory Memory
}
var _ Session = (*session)(nil)
func NewSession(memory Memory) (*session, error) {
return newSessionWithID(uuid.New().String(), memory), nil
}
func newSessionWithID(id string, memory Memory) *session {
return &session{
sessionID: id,
memory: memory,
}
}
func (s *session) ID() string {
return s.sessionID
}
func (s *session) Memory() Memory {
return s.memory
}

651
agent/ui.go Normal file
View File

@ -0,0 +1,651 @@
package agent
import (
"context"
"fmt"
"strings"
"time"
"github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/lipgloss"
)
// Message types for Bubbletea updates
type statusUpdateMsg struct {
message string
}
type agentResponseMsg struct {
content string
}
type agentThinkingMsg struct {
thinking bool
}
type agentToolCallMsg struct {
toolName string
toolArgs string
}
type thinkingTickMsg struct{}
var (
headerStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("240")).
Padding(0, 1)
inputPromptStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("240"))
inputCursorStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("255"))
inputBorderStyle = lipgloss.NewStyle().
Border(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("240")).
Padding(0, 1)
thinkingStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("33")).
Bold(true)
)
// AgentUI represents the main TUI model
type agentUI struct {
viewport viewport.Model
textInput textarea.Model
width int
height int
statusMessage string
isThinking bool
messages []uiMessage
ready bool
agent Agent
session Session
config AgentUIConfig
thinkingFrame int
inputHistory []string
historyIndex int
currentInput string
}
// Message represents a chat message
type uiMessage struct {
Role string // "user", "agent", "system"
Content string
Timestamp time.Time
}
// AgentUIConfig defines the configuration for the UI
type AgentUIConfig struct {
Width int
Height int
InitialSystemMessage string
TextInputPlaceholder string
TitleText string
MaxHistory int
// Only for informational purposes.
ModelName string
ModelVendor string
ModelFast bool
}
// DefaultAgentUIConfig returns the opinionated default configuration for the UI
func DefaultAgentUIConfig() AgentUIConfig {
return AgentUIConfig{
Width: 80,
Height: 20,
MaxHistory: 50,
InitialSystemMessage: "Security Agent initialized",
TextInputPlaceholder: "Ask me anything...",
TitleText: "Security Agent",
}
}
// NewAgentUI creates a new agent UI instance
func NewAgentUI(agent Agent, session Session, config AgentUIConfig) *agentUI {
vp := viewport.New(config.Width, config.Height)
ta := textarea.New()
ta.Placeholder = ""
ta.Focus()
ta.SetHeight(1)
ta.SetWidth(80)
ta.CharLimit = 1000
ta.ShowLineNumbers = false
ui := &agentUI{
viewport: vp,
textInput: ta,
statusMessage: "",
messages: []uiMessage{},
agent: agent,
session: session,
config: config,
thinkingFrame: 0,
inputHistory: []string{},
historyIndex: -1,
currentInput: "",
}
ui.addSystemMessage(config.InitialSystemMessage)
return ui
}
// Init implements the tea.Model interface
func (m *agentUI) Init() tea.Cmd {
return tea.Batch(
textarea.Blink,
m.tickThinking(),
)
}
// Update implements the tea.Model interface
func (m *agentUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyCtrlC, tea.KeyEsc:
return m, tea.Quit
case tea.KeyEnter:
if m.textInput.Focused() && !m.isThinking {
// Handle user input only if agent is not in thinking mode
userInput := strings.TrimSpace(m.textInput.Value())
if userInput != "" {
// Add to history and reset navigation
m.addToHistory(userInput)
// Add the input to the message list and reset user input field
m.addUserMessage(userInput)
m.resetInputField()
// Check if it's a slash command
if strings.HasPrefix(userInput, "/") {
// Handle slash command
cmd := m.handleSlashCommand(userInput)
if cmd != nil {
cmds = append(cmds, cmd)
}
} else {
// Execute agent query
cmds = append(cmds,
m.setThinking(true),
m.executeAgentQuery(userInput),
)
}
}
}
case tea.KeyTab:
// Switch focus between input and viewport, but not while agent is thinking
if !m.isThinking {
if m.textInput.Focused() {
m.textInput.Blur()
} else {
m.textInput.Focus()
cmds = append(cmds, textarea.Blink)
}
}
case tea.KeyUp, tea.KeyDown:
if m.textInput.Focused() && !m.isThinking {
// Navigate input history when text input is focused
var direction int
if msg.Type == tea.KeyUp {
direction = 1 // Go back in history
} else {
direction = -1 // Go forward in history
}
historyEntry := m.navigateHistory(direction)
m.textInput.SetValue(historyEntry)
m.textInput.CursorEnd()
} else if !m.textInput.Focused() {
// Allow scrolling in viewport when not focused on text input
m.viewport, cmd = m.viewport.Update(msg)
cmds = append(cmds, cmd)
}
case tea.KeyPgUp, tea.KeyPgDown:
// Allow scrolling in viewport when not focused on text input
if !m.textInput.Focused() {
m.viewport, cmd = m.viewport.Update(msg)
cmds = append(cmds, cmd)
}
case tea.KeyHome:
if !m.textInput.Focused() {
m.viewport.GotoTop()
}
case tea.KeyEnd:
if !m.textInput.Focused() {
m.viewport.GotoBottom()
}
}
case tea.WindowSizeMsg:
// Handle window resize
m.width = msg.Width
m.height = msg.Height
// Calculate dimensions for minimal UI
headerHeight := 2 // Header + blank line
inputHeight := 2 // Input area + status
spacing := 1 // Bottom spacing
// Calculate viewport dimensions to maximize output area
viewportHeight := m.height - headerHeight - inputHeight - spacing
// Ensure minimum height
if viewportHeight < 10 {
viewportHeight = 10
}
// Full width utilization
viewportWidth := m.width
// Ensure minimum width
if viewportWidth < 50 {
viewportWidth = 50
}
m.viewport.Width = viewportWidth
m.viewport.Height = viewportHeight
m.textInput.SetWidth(m.width - 3)
// Update content when dimensions change
m.viewport.SetContent(m.renderMessages())
if !m.ready {
m.ready = true
}
case statusUpdateMsg:
m.statusMessage = msg.message
case agentThinkingMsg:
m.isThinking = msg.thinking
// When agent starts thinking, blur the input
if m.isThinking {
m.resetInputField()
m.textInput.Blur()
m.thinkingFrame = 0
cmds = append(cmds, m.tickThinking())
} else {
// Re-focus input when thinking stops
m.textInput.Focus()
cmds = append(cmds, textarea.Blink)
}
case agentResponseMsg:
m.addAgentMessage(msg.content)
cmds = append(cmds, m.setThinking(false))
case agentToolCallMsg:
m.addToolCallMessage(fmt.Sprintf("🔧 %s", msg.toolName), msg.toolArgs)
case thinkingTickMsg:
if m.isThinking {
m.thinkingFrame = (m.thinkingFrame + 1) % 4
cmds = append(cmds, m.tickThinking())
}
}
// Update child components
m.viewport, cmd = m.viewport.Update(msg)
cmds = append(cmds, cmd)
// Only update text input if not thinking
if !m.isThinking {
m.textInput, cmd = m.textInput.Update(msg)
cmds = append(cmds, cmd)
}
return m, tea.Batch(cmds...)
}
// View implements the tea.Model interface
func (m *agentUI) View() string {
if !m.ready {
return "Loading..."
}
if m.width == 0 || m.height == 0 {
return "Initializing..."
}
modelAbility := "fast"
if !m.config.ModelFast {
modelAbility = "slow"
}
modelStatusLine := fmt.Sprintf("%s/%s (%s)", m.config.ModelVendor, m.config.ModelName, modelAbility)
header := headerStyle.Render(fmt.Sprintf("%s %s", m.config.TitleText, modelStatusLine))
content := m.viewport.View()
var thinkingIndicator string
if m.isThinking {
thinkingFrames := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
spinner := thinkingFrames[m.thinkingFrame%len(thinkingFrames)]
thinkingIndicator = thinkingStyle.Render(fmt.Sprintf("%s thinking...", spinner))
}
var inputArea string
userInput := m.textInput.Value()
cursor := ""
if m.textInput.Focused() && !m.isThinking {
cursor = inputCursorStyle.Render("▊")
}
inputContent := fmt.Sprintf("%s%s%s", inputPromptStyle.Render("> "), userInput, cursor)
inputArea = inputBorderStyle.Width(m.width - 2).Render(inputContent)
statusLine := inputPromptStyle.Render(fmt.Sprintf("** %s | ctrl+c to exit", modelStatusLine))
var components []string
components = append(components, header, "", content, "")
if thinkingIndicator != "" {
components = append(components, thinkingIndicator)
}
components = append(components, inputArea, statusLine)
return lipgloss.JoinVertical(lipgloss.Left, components...)
}
func (m *agentUI) resetInputField() {
m.textInput.Reset()
m.textInput.SetValue("")
m.textInput.CursorStart()
}
func (m *agentUI) addUserMessage(content string) {
m.messages = append(m.messages, uiMessage{
Role: "user",
Content: content,
Timestamp: time.Now(),
})
m.viewport.SetContent(m.renderMessages())
m.viewport.GotoBottom()
}
func (m *agentUI) addAgentMessage(content string) {
m.messages = append(m.messages, uiMessage{
Role: "agent",
Content: content,
Timestamp: time.Now(),
})
m.viewport.SetContent(m.renderMessages())
m.viewport.GotoBottom()
}
func (m *agentUI) addSystemMessage(content string) {
m.messages = append(m.messages, uiMessage{
Role: "system",
Content: content,
Timestamp: time.Now(),
})
m.viewport.SetContent(m.renderMessages())
m.viewport.GotoBottom()
}
func (m *agentUI) addToolCallMessage(toolName string, toolArgs string) {
content := fmt.Sprintf(" %s", toolName)
if toolArgs != "" && toolArgs != "{}" {
content += fmt.Sprintf("\n └─ %s", toolArgs)
}
m.messages = append(m.messages, uiMessage{
Role: "tool",
Content: content,
Timestamp: time.Now(),
})
m.viewport.SetContent(m.renderMessages())
m.viewport.GotoBottom()
}
// renderMessages formats all messages for display
func (m *agentUI) renderMessages() string {
var rendered []string
rendered = append(rendered, "", "")
contentWidth := m.viewport.Width - 2 // Account for internal padding
if contentWidth < 40 {
contentWidth = 40
}
r, err := glamour.NewTermRenderer(
glamour.WithStandardStyle("notty"),
glamour.WithWordWrap(contentWidth),
)
if err != nil {
r = nil
}
for _, msg := range m.messages {
timestamp := msg.Timestamp.Format("15:04:05")
switch msg.Role {
case "user":
userHeaderStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("86")).
Bold(true).
Border(lipgloss.NormalBorder(), false, false, false, true).
BorderForeground(lipgloss.Color("86")).
Padding(0, 1)
userContentStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("255")).
Padding(0, 2)
rendered = append(rendered,
userHeaderStyle.Render(fmt.Sprintf("[%s] → You:", timestamp)),
userContentStyle.Render(msg.Content),
"",
)
case "agent":
var content string
if r != nil {
renderedMarkdown, err := r.Render(msg.Content)
if err == nil {
content = strings.TrimSpace(renderedMarkdown)
} else {
content = msg.Content // Fallback to plain text
}
} else {
content = msg.Content
}
agentHeaderStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("39")).
Bold(true).
Border(lipgloss.NormalBorder(), false, false, false, true).
BorderForeground(lipgloss.Color("39")).
Padding(0, 1)
agentContentStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("255")).
Padding(0, 2)
rendered = append(rendered,
agentHeaderStyle.Render(fmt.Sprintf("[%s] ← Agent:", timestamp)),
agentContentStyle.Render(content),
"",
)
case "system":
systemStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("241")).
Italic(true).
Border(lipgloss.NormalBorder(), false, false, false, true).
BorderForeground(lipgloss.Color("241")).
Padding(0, 1)
rendered = append(rendered,
systemStyle.Render(fmt.Sprintf("[%s] %s", timestamp, msg.Content)),
"",
)
case "tool":
toolStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("245")).
Italic(true).
Faint(true).
Border(lipgloss.NormalBorder(), false, false, false, true).
BorderForeground(lipgloss.Color("245")).
Padding(0, 1)
rendered = append(rendered,
toolStyle.Render(fmt.Sprintf("[%s] %s", timestamp, msg.Content)),
"",
)
}
}
rendered = append(rendered, "", "")
return strings.Join(rendered, "\n")
}
func (m *agentUI) updateStatus(message string) tea.Cmd {
return func() tea.Msg {
return statusUpdateMsg{message: message}
}
}
func (m *agentUI) setThinking(thinking bool) tea.Cmd {
return func() tea.Msg {
return agentThinkingMsg{thinking: thinking}
}
}
func (m *agentUI) executeAgentQuery(userInput string) tea.Cmd {
return func() tea.Msg {
ctx := context.Background()
input := Input{
Query: userInput,
}
toolCallHook := func(_ context.Context, _ Session, _ Input, toolName string, toolArgs string) error {
m.Update(agentToolCallMsg{toolName: toolName, toolArgs: toolArgs})
return nil
}
output, err := m.agent.Execute(ctx, m.session, input, WithToolCallHook(toolCallHook))
if err != nil {
return agentResponseMsg{
content: fmt.Sprintf("❌ **Error**\n\nSorry, I encountered an error while processing your query:\n\n%s", err.Error()),
}
}
return agentResponseMsg{content: output.Answer}
}
}
// StartUI starts the TUI application with the default configuration
func StartUI(agent Agent, session Session) error {
config := DefaultAgentUIConfig()
config.InitialSystemMessage = ""
return StartUIWithConfig(agent, session, config)
}
// StartUIWithConfig starts the TUI application with the provided configuration
func StartUIWithConfig(agent Agent, session Session, config AgentUIConfig) error {
ui := NewAgentUI(agent, session, config)
p := tea.NewProgram(
ui,
tea.WithAltScreen(),
tea.WithMouseCellMotion(),
)
_, err := p.Run()
return err
}
func (m *agentUI) tickThinking() tea.Cmd {
return tea.Tick(150*time.Millisecond, func(time.Time) tea.Msg {
return thinkingTickMsg{}
})
}
// handleSlashCommand processes commands that start with '/'
func (m *agentUI) handleSlashCommand(command string) tea.Cmd {
switch command {
case "/exit":
m.addSystemMessage("Goodbye! Exiting gracefully...")
return tea.Quit
default:
m.addSystemMessage(fmt.Sprintf("Unknown command: %s", command))
return nil
}
}
// addToHistory adds input to history buffer with a maximum of 50 entries
func (m *agentUI) addToHistory(input string) {
// Don't add empty strings or duplicates of the last entry
if input == "" || (len(m.inputHistory) > 0 && m.inputHistory[len(m.inputHistory)-1] == input) {
return
}
m.inputHistory = append(m.inputHistory, input)
// Keep only the last maxHistory entries
if len(m.inputHistory) > m.config.MaxHistory {
m.inputHistory = m.inputHistory[len(m.inputHistory)-m.config.MaxHistory:]
}
// Reset history navigation
m.historyIndex = -1
m.currentInput = ""
}
// navigateHistory moves through input history and returns the selected entry
func (m *agentUI) navigateHistory(direction int) string {
if len(m.inputHistory) == 0 {
return ""
}
// Save current input when starting navigation
if m.historyIndex == -1 {
m.currentInput = m.textInput.Value()
}
// Calculate new index
newIndex := m.historyIndex + direction
// Handle boundaries
if newIndex < -1 {
newIndex = -1
} else if newIndex >= len(m.inputHistory) {
newIndex = len(m.inputHistory) - 1
}
m.historyIndex = newIndex
// Return the appropriate entry
if m.historyIndex == -1 {
return m.currentInput
}
return m.inputHistory[len(m.inputHistory)-1-m.historyIndex]
}

280
agent/ui_test.go Normal file
View File

@ -0,0 +1,280 @@
package agent
import (
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/stretchr/testify/assert"
)
func TestAgentUICreation(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
assert.NotNil(t, ui, "Failed to create AgentUI")
assert.Empty(t, ui.statusMessage, "Expected empty status message")
assert.False(t, ui.isThinking, "UI should not be thinking initially")
assert.Equal(t, 0, ui.thinkingFrame, "Thinking frame should be 0 initially")
// Check that system message was added if config has one
if config.InitialSystemMessage != "" {
assert.NotEmpty(t, ui.messages, "Expected system message to be added if InitialSystemMessage is set")
}
}
func TestDefaultAgentUIConfig(t *testing.T) {
config := DefaultAgentUIConfig()
assert.Equal(t, 80, config.Width, "Expected default width 80")
assert.Equal(t, 20, config.Height, "Expected default height 20")
assert.Equal(t, "Security Agent", config.TitleText, "Expected title 'Security Agent'")
assert.Equal(t, "Ask me anything...", config.TextInputPlaceholder, "Expected placeholder 'Ask me anything...'")
}
func TestMessageManagement(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
initialCount := len(ui.messages)
// Test adding user message
ui.addUserMessage("Test user message")
assert.Equal(t, initialCount+1, len(ui.messages), "Expected message count to increase")
lastMessage := ui.messages[len(ui.messages)-1]
assert.Equal(t, "user", lastMessage.Role, "Expected last message role to be 'user'")
assert.Equal(t, "Test user message", lastMessage.Content, "Expected last message content to be 'Test user message'")
// Test adding agent message
ui.addAgentMessage("Test agent response")
assert.Equal(t, initialCount+2, len(ui.messages), "Expected message count to increase")
lastMessage = ui.messages[len(ui.messages)-1]
assert.Equal(t, "agent", lastMessage.Role, "Expected last message role to be 'agent'")
// Test adding system message
ui.addSystemMessage("System notification")
assert.Equal(t, initialCount+3, len(ui.messages), "Expected message count to increase")
lastMessage = ui.messages[len(ui.messages)-1]
assert.Equal(t, "system", lastMessage.Role, "Expected last message role to be 'system'")
// Test adding tool call message
ui.addToolCallMessage("ScanVulnerabilities", `{"path": "/app"}`)
assert.Equal(t, initialCount+4, len(ui.messages), "Expected message count to increase")
lastMessage = ui.messages[len(ui.messages)-1]
assert.Equal(t, "tool", lastMessage.Role, "Expected last message role to be 'tool'")
}
func TestMessageRendering(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
// Set up viewport dimensions for rendering
ui.viewport.Width = 80
ui.viewport.Height = 20
ui.addUserMessage("How many vulnerabilities?")
ui.addAgentMessage("Found 5 critical vulnerabilities")
rendered := ui.renderMessages()
assert.NotEmpty(t, rendered, "Expected non-empty rendered output")
assert.Contains(t, rendered, "How many vulnerabilities?", "Rendered output should contain user message")
assert.Contains(t, rendered, "Found 5 critical vulnerabilities", "Rendered output should contain agent message")
assert.Contains(t, rendered, "You:", "Rendered output should contain user label")
assert.Contains(t, rendered, "Agent:", "Rendered output should contain agent label")
}
func TestViewportDimensions(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
// Test window resize handling
resizeMsg := tea.WindowSizeMsg{Width: 100, Height: 30}
ui.Update(resizeMsg)
assert.Equal(t, 100, ui.width, "Expected width 100")
assert.Equal(t, 30, ui.height, "Expected height 30")
// Test minimum dimensions enforcement
resizeMsg = tea.WindowSizeMsg{Width: 10, Height: 5}
ui.Update(resizeMsg)
assert.GreaterOrEqual(t, ui.viewport.Width, 50, "Viewport width should be enforced to minimum 50")
assert.GreaterOrEqual(t, ui.viewport.Height, 10, "Viewport height should be enforced to minimum 10")
}
func TestViewRendering(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
config.ModelName = "gpt-4"
config.ModelVendor = "openai"
config.ModelFast = false
ui := NewAgentUI(mockAgent, mockSession, config)
ui.width = 80
ui.height = 24
ui.ready = true
view := ui.View()
assert.Contains(t, view, "Security Agent", "View should contain title")
assert.Contains(t, view, "openai/gpt-4", "View should contain model information")
assert.Contains(t, view, ">", "View should contain input prompt")
assert.Contains(t, view, "ctrl+c to exit", "View should contain exit instruction")
}
func TestThinkingState(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
ui.width = 80
ui.height = 24
ui.ready = true
// Initially not thinking
assert.False(t, ui.isThinking, "UI should not be thinking initially")
// Set thinking state
thinkingMsg := agentThinkingMsg{thinking: true}
ui.Update(thinkingMsg)
assert.True(t, ui.isThinking, "UI should be thinking after agentThinkingMsg")
// Check view contains thinking indicator
view := ui.View()
assert.Contains(t, view, "thinking...", "View should contain thinking indicator when thinking")
// Stop thinking
thinkingMsg = agentThinkingMsg{thinking: false}
ui.Update(thinkingMsg)
assert.False(t, ui.isThinking, "UI should not be thinking after agentThinkingMsg with false")
}
func TestKeyboardHandling(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
ui.width = 80
ui.height = 24
ui.ready = true
var keyMsg tea.KeyMsg
var model tea.Model
var cmd tea.Cmd
// Test Ctrl+C exits immediately
keyMsg = tea.KeyMsg{Type: tea.KeyCtrlC}
_, cmd = ui.Update(keyMsg)
assert.NotNil(t, cmd, "Ctrl+C should return quit command")
// Test Tab key for focus switching when not thinking
ui.textInput.Focus()
keyMsg = tea.KeyMsg{Type: tea.KeyTab}
model, _ = ui.Update(keyMsg)
ui = model.(*agentUI)
assert.False(t, ui.textInput.Focused(), "Tab should blur text input when it's focused")
// Test Enter key handling when not thinking
ui.textInput.Focus()
ui.textInput.SetValue("test message")
initialMessageCount := len(ui.messages)
keyMsg = tea.KeyMsg{Type: tea.KeyEnter}
model, _ = ui.Update(keyMsg)
ui = model.(*agentUI)
assert.Equal(t, initialMessageCount+1, len(ui.messages), "Enter should add user message when input is not empty")
// Note: Input field reset happens when thinking starts, not immediately
// The resetInputField() is called, but the UI state may not reflect it immediately in tests
}
func TestInputFieldReset(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
// Set some input text
ui.textInput.SetValue("test input")
assert.Equal(t, "test input", ui.textInput.Value(), "Input should contain test text")
// Reset input field
ui.resetInputField()
assert.Empty(t, ui.textInput.Value(), "Input should be empty after reset")
}
func TestCommandCreation(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
// Test status update command
cmd := ui.updateStatus("Testing status")
assert.NotNil(t, cmd, "updateStatus should return a non-nil command")
// Test thinking command
cmd = ui.setThinking(true)
assert.NotNil(t, cmd, "setThinking should return a non-nil command")
// Test execute agent query command
cmd = ui.executeAgentQuery("test query")
assert.NotNil(t, cmd, "executeAgentQuery should return a non-nil command")
}
func TestMessageTimestamps(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
before := time.Now()
ui.addUserMessage("Test message")
after := time.Now()
message := ui.messages[len(ui.messages)-1]
assert.True(t, message.Timestamp.After(before) || message.Timestamp.Equal(before), "Message timestamp should be after or equal to before time")
assert.True(t, message.Timestamp.Before(after) || message.Timestamp.Equal(after), "Message timestamp should be before or equal to after time")
}
func TestUIInitialization(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
// Test Init command
cmd := ui.Init()
assert.NotNil(t, cmd, "Init should return a non-nil command")
// Test initial state before ready
view := ui.View()
assert.Equal(t, "Loading...", view, "View should show loading before ready")
// Test with zero dimensions
ui.ready = true
ui.width = 0
ui.height = 0
view = ui.View()
assert.Equal(t, "Initializing...", view, "View should show initializing with zero dimensions")
}

17
api/checks.proto Normal file
View File

@ -0,0 +1,17 @@
syntax = "proto3";
option go_package = "github.com/safedep/vet/gen/checks";
enum CheckType {
CheckTypeUnknown = 0;
CheckTypeVulnerability = 1;
CheckTypeMalware = 2;
CheckTypePopularity = 3;
CheckTypeMaintenance = 4;
CheckTypeSecurityScorecard = 5;
CheckTypeLicense = 6;
reserved 7 to 99;
CheckTypeOther = 100;
}

View File

@ -1,123 +0,0 @@
openapi: 3.0.2
info:
title: SafeDep Control Plane API for Trials Registration
contact:
name: SafeDep API
url: 'https://safedep.io'
description: |
Trials API provide a way for obtaining an API Key for data plane service access
using an Email Address. Trials is different from Registrations as the later
allows full access to the control plane while Trials is meant to allow access
only to a time bounded (expirable) API key for quick evaluation of tools.
version: 0.0.1
servers:
- url: 'https://{apiHost}/{apiBase}'
variables:
apiHost:
default: api.safedep.io
apiBase:
default: control-plane/v1
tags:
- name: Control Plane
description: Control Plane API
paths:
/trials:
post:
description: |
Register a trial user to obtain an expirable API Key. The API key will
be generated and sent to the user over Email to ensure validity and access
to the email by the requester. System defined limits will be applied to
maximum number of trial API keys that can be generated for an email.
operationId: registerTrialUser
tags:
- Control Plane
requestBody:
description: Trial registration request
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TrialRequest'
responses:
'201':
description: Successfully created an API key request
content:
application/json:
schema:
$ref: '#/components/schemas/TrialResponse'
'403':
description: Access to the API is denied
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'429':
description: Rate limit block
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'500':
description: Failed due to internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
components:
schemas:
ApiError:
type: object
properties:
message:
type: string
description: A descriptive message about the error meant for developer consumption
type:
type: string
description: An optional service or domain specific error group
enum:
- invalid_request
- operation_failed
- internal_error
code:
type: string
description: An error code identifying the error
enum:
- api_guard_invalid_credentials
- api_guard_rate_limit_exceeded
- api_guard_unauthorized
- api_guard_error
- app_generic_error
- app_security_error
- app_insufficient_parameters
- app_feature_not_enabled
- app_package_version_not_found
params:
type: object
description: Optional error specific attributes
additionalProperties:
type: object
properties:
key:
type: string
value:
type: string
TrialRequest:
type: object
properties:
email:
type: string
format: email
required:
- email
TrialResponse:
type: object
properties:
id:
type: string
minLength: 6
maxLength: 512
description: The ID of the trial registration request created in the system
expires_at:
type: string
format: date-time
description: The expiry time of the API key

18
api/exceptions_spec.proto Normal file
View File

@ -0,0 +1,18 @@
syntax = "proto3";
option go_package = "github.com/safedep/vet/gen/exceptionsapi";
message Exception {
string id = 1;
string ecosystem = 2;
string name = 3;
string version = 4;
string expires = 5;
string pattern = 6; // To be used for special cases
}
message ExceptionSuite {
string name = 1;
string description = 2;
repeated Exception exceptions = 3;
}

View File

@ -3,48 +3,27 @@ syntax = "proto3";
/* Specifcations for filter input that can be used for query by CEL */
option go_package = "github.com/safedep/vet/gen/filterinput";
message Vulnerability {
string id = 1; // OSV ID
string cve = 2; // CVE ID
}
import "insights_models.proto";
// Only hold vulnerability IDs
message Vulnerabilities {
repeated Vulnerability all = 1;
repeated Vulnerability critical = 2;
repeated Vulnerability high = 3;
repeated Vulnerability medium = 4;
repeated Vulnerability low = 5;
message FilterInputVulnerabilities {
repeated InsightVulnerability all = 1;
repeated InsightVulnerability critical = 2;
repeated InsightVulnerability high = 3;
repeated InsightVulnerability medium = 4;
repeated InsightVulnerability low = 5;
}
// OpenSSF Scorecard
message Scorecard {
map<string, float> scores = 1;
}
enum ProjectType {
UNKNOWN = 0;
GITHUB = 1;
}
message ProjectInfo {
string name = 1;
ProjectType type = 2;
int32 stars = 3;
int32 forks = 4;
int32 issues = 5;
}
message PackageVersion {
message FilterInputPackageVersion {
string ecosystem = 1;
string name = 2;
string version = 3;
}
message FilterInput {
PackageVersion pkg = 1;
Vulnerabilities vulns = 2;
Scorecard scorecard = 3;
repeated ProjectInfo projects = 4;
FilterInputPackageVersion pkg = 1;
FilterInputVulnerabilities vulns = 2;
InsightScorecard scorecard = 3;
repeated InsightProjectInfo projects = 4;
repeated string licenses = 5;
}

View File

@ -0,0 +1,22 @@
syntax = "proto3";
option go_package = "github.com/safedep/vet/gen/filtersuite";
import "checks.proto";
message Filter {
string name = 1;
string value = 2;
CheckType check_type = 3;
string summary = 4;
string description = 5;
repeated string references = 6;
repeated string tags = 7;
}
message FilterSuite {
string name = 1;
string description = 2;
repeated Filter filters = 3;
repeated string tags = 4;
}

56
api/insights_models.proto Normal file
View File

@ -0,0 +1,56 @@
syntax = "proto3";
option go_package = "github.com/safedep/vet/gen/models";
message InsightVulnerabilitySeverity {
enum Type {
UNKNOWN_TYPE = 0;
CVSSV2 = 1;
CVSSV3 = 2;
}
enum Risk {
UNKNOWN_RISK = 0;
LOW = 1;
MEDIUM = 2;
HIGH = 3;
CRITICAL = 4;
}
Type type = 1;
string score = 2; // Score based on type (usually the CVSS metric)
Risk risk = 3;
}
message InsightVulnerability {
string id = 1; // OSV ID
string cve = 2; // CVE ID. DO NOT USE THIS outside vet. Its used for internal legacy reason
string title = 3;
repeated string aliases = 4; // Other IDs for same vuln in different databases
repeated InsightVulnerabilitySeverity severities = 5;
}
message InsightLicenseInfo {
string id = 1; // SPDX license ID
}
message InsightScorecard {
map<string, float> scores = 1;
float score = 2;
}
message InsightProjectInfo {
enum Type {
UNKNOWN = 0;
GITHUB = 1;
}
string name = 1;
Type type = 2;
int32 stars = 3;
int32 forks = 4;
int32 issues = 5;
string url = 6;
}

111
api/json_report_spec.proto Normal file
View File

@ -0,0 +1,111 @@
syntax = "proto3";
option go_package = "github.com/safedep/vet/gen/jsonreportspec";
import "models.proto";
import "insights_models.proto";
import "violations.proto";
enum RemediationAdviceType {
UnknownAdviceType = 0;
UpgradePackage = 1;
AlternatePopularPackage = 2;
AlternateSecurePackage = 3;
}
message RemediationAdvice {
RemediationAdviceType type = 1;
Package package = 2;
string target_package_name = 3;
string target_package_version = 4;
string target_alternate_package_name = 5;
string target_alternate_package_version = 6;
}
// We are introducing the concept of Threat as a reporting entity so
// that we can report threats like lockfile poisoning using a standard schema.
// But why do we need threats? Why not just use vet's paradigm of policy over
// enriched packages? The reason is, there are threats that are applicable in
// an environment, against a manifest or other entities or even group of entities.
// Hence it is required to introduce a threat as a reporting entity so that external
// tools can consume vet's reports and take actions based on the threats.
message ReportThreat {
enum Confidence {
UnknownConfidence = 0;
High = 1;
Medium = 2;
Low = 3;
}
enum Source {
UnknownSource = 0;
CWE = 1;
}
enum SubjectType {
UnknownSubject = 0;
Package = 1;
Manifest = 2;
}
enum ReportThreatId {
UnknownReportThreatId = 0;
LockfilePoisoning = 1;
}
ReportThreatId id = 1;
string instanceId = 2; // Unique threat instance ID per (ID, SubjectType, Subject) tuple
string message = 3;
SubjectType subject_type = 4;
string subject = 5;
Confidence confidence = 6;
Source source = 7;
string source_id = 8;
}
message PackageManifestReport {
string id = 1;
Ecosystem ecosystem = 2;
string path = 3;
repeated ReportThreat threats = 4;
string display_path = 5;
string source_type = 6;
string namespace = 7;
}
// PackageReport represents the first class entity for which we have different type
// of reporting information
message PackageReport {
Package package = 1;
// The manifests identified by IDs where this package belongs to
repeated string manifests = 2;
repeated Violation violations = 3;
repeated RemediationAdvice advices = 4;
// Insights data
repeated InsightVulnerability vulnerabilities = 5;
repeated InsightLicenseInfo licenses = 6;
repeated InsightProjectInfo projects = 8;
// Threats
repeated ReportThreat threats = 7;
}
message ReportMeta {
string tool_name = 1;
string tool_version = 2;
string created_at = 3;
}
message Report {
ReportMeta meta = 1;
repeated PackageManifestReport manifests = 2;
repeated PackageReport packages = 3;
}

35
api/models.proto Normal file
View File

@ -0,0 +1,35 @@
syntax = "proto3";
option go_package = "github.com/safedep/vet/gen/models";
// Core data models on which `vet` operations. This should eventually
// become source of truth and we should remove the model definitions in
// Go code `models.go` and instead generate code from here
enum Ecosystem {
UNKNOWN_ECOSYSTEM = 0;
Maven = 1;
RubyGems = 2;
Go = 3;
Npm = 4;
PyPI = 5;
Cargo = 6;
NuGet = 7;
Packagist = 8;
Hex = 9;
Pub = 10;
CycloneDxSBOM = 11;
SpdxSBOM = 12;
}
message Package {
Ecosystem ecosystem = 1;
string name = 2;
string version = 3;
}
message PackageManifest {
Ecosystem ecosystem = 1;
string path = 2;
repeated Package packages = 3;
}

15
api/violations.proto Normal file
View File

@ -0,0 +1,15 @@
syntax = "proto3";
option go_package = "github.com/safedep/vet/gen/violations";
import "models.proto";
import "checks.proto";
import "filter_suite_spec.proto";
message Violation {
CheckType check_type = 1;
Package package = 2;
Filter filter = 3;
map<string, string> extra = 4;
}

107
auth.go
View File

@ -1,36 +1,31 @@
package main
import (
"fmt"
"errors"
"os"
"syscall"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"golang.org/x/term"
"github.com/safedep/vet/internal/auth"
"github.com/safedep/vet/internal/command"
"github.com/safedep/vet/internal/ui"
"github.com/safedep/vet/pkg/common/logger"
)
var (
authInsightApiBaseUrl string
authControlPlaneApiBaseUrl string
authTrialEmail string
)
var authTenantDomain string
func newAuthCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "auth",
Short: "Configure and verify Insights API authentication",
Short: "Configure vet authentication",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("You must choose an appropriate command: configure, verify\n")
os.Exit(1)
return nil
return errors.New("a valid sub-command is required")
},
}
cmd.AddCommand(configureAuthCommand())
cmd.AddCommand(verifyAuthCommand())
cmd.AddCommand(trialsRegisterCommand())
return cmd
}
@ -39,73 +34,73 @@ func configureAuthCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "configure",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Print("Enter API Key: ")
key, err := term.ReadPassword(syscall.Stdin)
var key string
var err error
err = survey.AskOne(&survey.Password{
Message: "Enter the API key",
}, &key)
if err != nil {
panic(err)
logger.Fatalf("Failed to setup auth: %v", err)
}
err = auth.Configure(auth.Config{
ApiUrl: authInsightApiBaseUrl,
ApiKey: string(key),
})
if auth.TenantDomain() != "" && auth.TenantDomain() != authTenantDomain {
ui.PrintWarning("Tenant domain mismatch. Existing: %s, New: %s, continue? ",
auth.TenantDomain(), authTenantDomain)
if err != nil {
panic(err)
var confirm bool
err = survey.AskOne(&survey.Confirm{
Message: "Do you want to continue?",
}, &confirm)
if err != nil {
logger.Fatalf("Failed to setup auth: %v", err)
}
if !confirm {
return nil
}
}
os.Exit(1)
auth.SetRuntimeCloudTenant(authTenantDomain)
auth.SetRuntimeApiKey(key)
err = auth.Verify()
if err != nil {
logger.Fatalf("Failed to verify auth: %v", err)
}
err = auth.PersistApiKey(key, authTenantDomain)
if err != nil {
logger.Fatalf("Failed to configure auth: %v", err)
}
os.Exit(0)
return nil
},
}
cmd.Flags().StringVarP(&authInsightApiBaseUrl, "api", "", auth.DefaultApiUrl(),
"Base URL of Insights API")
cmd.Flags().StringVarP(&authTenantDomain, "tenant", "", "",
"Tenant domain for SafeDep Cloud")
_ = cmd.MarkFlagRequired("tenant")
return cmd
}
func verifyAuthCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "verify",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("Verify auth command is currently work in progress\n")
os.Exit(1)
return nil
},
}
return cmd
}
func trialsRegisterCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "trial",
RunE: func(cmd *cobra.Command, args []string) error {
client := auth.NewTrialRegistrationClient(auth.TrialConfig{
Email: authTrialEmail,
ControlPlaneApiUrl: authControlPlaneApiBaseUrl,
})
res, err := client.Execute()
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
if auth.CommunityMode() {
ui.PrintSuccess("Running in Community Mode")
}
fmt.Printf("Trial registration successful with Id:%s\n", res.Id)
fmt.Printf("Check your email (%s) for API key and usage instructions\n", authTrialEmail)
fmt.Printf("The trial API key will expire on %s\n", res.ExpiresAt.String())
command.FailOnError("auth/verify", auth.Verify())
ui.PrintSuccess("Authentication key is valid!")
return nil
},
}
cmd.Flags().StringVarP(&authTrialEmail, "email", "", "",
"Email address to use for sending trial API key")
cmd.Flags().StringVarP(&authControlPlaneApiBaseUrl, "control-plane", "",
auth.DefaultControlPlaneApiUrl(), "Base URL of Control Plane API for registrations")
return cmd
}

53
cmd/agent/common.go Normal file
View File

@ -0,0 +1,53 @@
package agent
import (
"context"
"fmt"
"os"
"github.com/charmbracelet/glamour"
"github.com/safedep/vet/agent"
)
func buildModelFromEnvironment() (*agent.Model, error) {
model, err := agent.BuildModelFromEnvironment(fastMode)
if err != nil {
return nil, fmt.Errorf("failed to build LLM model adapter using environment configuration: %w", err)
}
return model, nil
}
func executeAgentPrompt(agentExecutor agent.Agent, session agent.Session, prompt string) error {
output, err := agentExecutor.Execute(context.Background(), session, agent.Input{
Query: prompt,
}, agent.WithToolCallHook(func(ctx context.Context, session agent.Session, input agent.Input, toolName string, toolArgs string) error {
os.Stderr.WriteString(fmt.Sprintf("Tool called: %s with args: %s\n", toolName, toolArgs))
return nil
}))
if err != nil {
return fmt.Errorf("failed to execute agent: %w", err)
}
terminalRenderer, err := glamour.NewTermRenderer(
glamour.WithAutoStyle(),
glamour.WithWordWrap(80),
glamour.WithEmoji(),
)
if err != nil {
return fmt.Errorf("failed to create glamour renderer: %w", err)
}
rendered, err := terminalRenderer.Render(output.Answer)
if err != nil {
return fmt.Errorf("failed to render answer: %w", err)
}
_, err = os.Stdout.WriteString(rendered)
if err != nil {
return fmt.Errorf("failed to write answer: %w", err)
}
return nil
}

34
cmd/agent/main.go Normal file
View File

@ -0,0 +1,34 @@
// Package agent provides a CLI for running agents.
package agent
import "github.com/spf13/cobra"
var (
maxAgentSteps int
// Use a fast model when available. Opinionated. Can be overridden by the
// setting environment variables.
fastMode bool
// User wants the agent to answer a single question and not start the
// interactive agent. Not all agents may support this.
singlePrompt string
)
func NewAgentCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "agent",
Short: "Run an available AI agent",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
cmd.PersistentFlags().IntVar(&maxAgentSteps, "max-steps", 30, "The maximum number of steps for the agent executor")
cmd.PersistentFlags().StringVarP(&singlePrompt, "prompt", "p", "", "A single prompt to run the agent with")
cmd.PersistentFlags().BoolVar(&fastMode, "fast", false, "Prefer a fast model when available (compromises on advanced reasoning)")
cmd.AddCommand(newQueryAgentCommand())
return cmd
}

107
cmd/agent/query.go Normal file
View File

@ -0,0 +1,107 @@
package agent
import (
"context"
_ "embed"
"fmt"
"github.com/spf13/cobra"
"github.com/safedep/vet/agent"
"github.com/safedep/vet/internal/analytics"
"github.com/safedep/vet/internal/command"
"github.com/safedep/vet/pkg/common/logger"
)
//go:embed query_prompt.md
var querySystemPrompt string
var queryAgentDBPath string
func newQueryAgentCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "query",
Short: "Query agent allows analysis and querying the vet sqlite3 report database",
RunE: func(cmd *cobra.Command, args []string) error {
err := executeQueryAgent()
if err != nil {
logger.Errorf("failed to execute query agent: %v", err)
}
return nil
},
}
cmd.Flags().StringVar(&queryAgentDBPath, "db", "", "The path to the vet sqlite3 report database")
_ = cmd.MarkFlagRequired("db")
return cmd
}
func executeQueryAgent() error {
analytics.TrackAgentQuery()
toolBuilder, err := agent.NewMcpClientToolBuilder(agent.McpClientToolBuilderConfig{
ClientName: "vet-query-agent",
ClientVersion: command.GetVersion(),
SkipDefaultTools: true,
SQLQueryToolEnabled: true,
SQLQueryToolDBPath: queryAgentDBPath,
PackageRegistryToolEnabled: true,
})
if err != nil {
return fmt.Errorf("failed to create MCP client tool builder: %w", err)
}
tools, err := toolBuilder.Build(context.Background())
if err != nil {
return fmt.Errorf("failed to build tools: %w", err)
}
model, err := buildModelFromEnvironment()
if err != nil {
return fmt.Errorf("failed to build LLM model adapter using environment configuration: %w", err)
}
agentExecutor, err := agent.NewReactQueryAgent(model.Client, agent.ReactQueryAgentConfig{
MaxSteps: maxAgentSteps,
SystemPrompt: querySystemPrompt,
}, agent.WithTools(tools))
if err != nil {
return fmt.Errorf("failed to create agent: %w", err)
}
memory, err := agent.NewSimpleMemory()
if err != nil {
return fmt.Errorf("failed to create memory: %w", err)
}
session, err := agent.NewSession(memory)
if err != nil {
return fmt.Errorf("failed to create session: %w", err)
}
if singlePrompt != "" {
err = executeAgentPrompt(agentExecutor, session, singlePrompt)
if err != nil {
return fmt.Errorf("failed to execute agent prompt: %w", err)
}
} else {
uiConfig := agent.DefaultAgentUIConfig()
uiConfig.TitleText = "🔍 Query Agent - Interactive Query Mode"
uiConfig.TextInputPlaceholder = "Ask me anything about your scan data..."
uiConfig.InitialSystemMessage = "🤖 Query Agent initialized. Ask me anything about your dependencies, vulnerabilities and other supply chain risks."
uiConfig.ModelName = model.Name
uiConfig.ModelVendor = model.Vendor
uiConfig.ModelFast = model.Fast
err = agent.StartUIWithConfig(agentExecutor, session, uiConfig)
if err != nil {
return fmt.Errorf("failed to start agent interaction UI: %w", err)
}
}
return nil
}

57
cmd/agent/query_prompt.md Normal file
View File

@ -0,0 +1,57 @@
Your task is to assist the user in finding useful information from vet scan results
available in an sqlite3 database.
To answer user's query, you MUST do the following:
1. **Schema Discovery**: Use the database schema introspection tool to understand the available tables, columns, and relationships
2. **Query Planning**: Analyze the user's question and plan your approach:
- Identify which tables contain the relevant data
- Determine the relationships between tables needed
- Plan the query structure before writing SQL
3. **Query Execution**: Execute your planned query using the database query tool
4. **Result Validation**: Verify the results make sense and answer the user's question
5. **Response Formatting**: Present findings in clear markdown format
GUIDELINES:
* **Query Best Practices**:
- Always use `COUNT(*)` instead of `SELECT *` when determining table sizes
- Use `LIMIT` and `OFFSET` for pagination with large result sets
- Prefer JOINs over subqueries for better performance
- Use aggregate functions (COUNT, SUM, AVG) for statistical queries
* **Data Integrity**:
- NEVER make assumptions about data that you haven't verified through queries
- If a query returns unexpected results, re-examine your approach
- Always check for NULL values and handle them appropriately
- Validate that your query logic matches the user's intent
* **Error Handling**:
- If a query fails, explain the error and try an alternative approach
- If no data is found, clearly state this rather than making assumptions
- When data seems incomplete, acknowledge limitations in your response
IMPORTANT CONSTRAINTS:
* **Prevent Hallucinations**:
- Only report data that you have actually queried from the database
- NEVER invent or assume data points that weren't returned by your queries
- If you're unsure about a result, query the data again to confirm
- Always distinguish between actual data and your interpretation of it
* **User Interaction**:
- Ask for clarification if the user's query is ambiguous
- Provide context about what the data represents (e.g., "This shows vulnerabilities found in your dependencies")
- If you cannot answer with available data, explain what information is missing
* **Response Format**:
- Present tabular data as markdown tables with appropriate headers
- Include summary statistics when relevant (e.g., "Found 15 vulnerabilities across 8 packages")
- Use clear headings to organize complex responses
- Always explain what the data means in the context of security scanning
* **Domain Context**:
- Remember that vet scans analyze software dependencies for security issues
- Common entities include: packages, vulnerabilities, licenses, malware, scorecards
- Explain technical terms that may be unfamiliar to users

191
cmd/cloud/key.go Normal file
View File

@ -0,0 +1,191 @@
package cloud
import (
"time"
"github.com/spf13/cobra"
"github.com/safedep/vet/internal/auth"
"github.com/safedep/vet/internal/ui"
"github.com/safedep/vet/pkg/cloud"
"github.com/safedep/vet/pkg/common/logger"
)
var (
keyName string
keyDescription string
keyExpiresIn int
listKeysName string
listKeysIncludeExpired bool
listKeysOnlyMine bool
deleteKeyId string
)
func newKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "key",
Short: "Manage API keys",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
cmd.AddCommand(newKeyCreateCommand())
cmd.AddCommand(newListKeyCommand())
cmd.AddCommand(newDeleteKeyCommand())
return cmd
}
func newDeleteKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "delete",
Short: "Delete an API key",
RunE: func(cmd *cobra.Command, args []string) error {
err := executeDeleteKey()
if err != nil {
logger.Errorf("Failed to delete API key: %v", err)
}
return nil
},
}
cmd.Flags().StringVar(&deleteKeyId, "id", "", "ID of the API key to delete")
_ = cmd.MarkFlagRequired("id")
return cmd
}
func executeDeleteKey() error {
client, err := auth.ControlPlaneClientConnection("vet-cloud-key-delete")
if err != nil {
return err
}
keyService, err := cloud.NewApiKeyService(client)
if err != nil {
return err
}
err = keyService.DeleteKey(deleteKeyId)
if err != nil {
return err
}
ui.PrintSuccess("API key deleted successfully.")
return nil
}
func newListKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List API keys",
RunE: func(cmd *cobra.Command, args []string) error {
err := executeListKeys()
if err != nil {
logger.Errorf("Failed to list API keys: %v", err)
}
return nil
},
}
cmd.Flags().StringVar(&listKeysName, "name", "",
"List keys with partial match on the name")
cmd.Flags().BoolVar(&listKeysIncludeExpired, "include-expired", false,
"Include expired keys in the list")
cmd.Flags().BoolVar(&listKeysOnlyMine, "only-mine", false,
"List only keys created by the current user")
return cmd
}
func executeListKeys() error {
client, err := auth.ControlPlaneClientConnection("vet-cloud-key-list")
if err != nil {
return err
}
keyService, err := cloud.NewApiKeyService(client)
if err != nil {
return err
}
keys, err := keyService.ListKeys(&cloud.ListApiKeyRequest{
Name: listKeysName,
IncludeExpired: listKeysIncludeExpired,
OnlyMine: listKeysOnlyMine,
})
if err != nil {
return err
}
if len(keys.Keys) == 0 {
ui.PrintSuccess("No API keys found.")
return nil
}
tbl := ui.NewTabler(ui.TablerConfig{})
tbl.AddHeader("ID", "Name", "Expires At", "Description")
for _, key := range keys.Keys {
expiresAt := key.ExpiresAt.In(time.Local).Format(time.RFC822)
tbl.AddRow(key.ID, key.Name, expiresAt, key.Desc)
}
return tbl.Finish()
}
func newKeyCreateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "Create a new API key",
RunE: func(cmd *cobra.Command, args []string) error {
err := executeCreateKey()
if err != nil {
logger.Errorf("Failed to create API key: %v", err)
}
return nil
},
}
cmd.Flags().StringVar(&keyName, "name", "", "Name of the API key")
cmd.Flags().StringVar(&keyDescription, "description", "", "Description of the API key")
cmd.Flags().IntVar(&keyExpiresIn, "expires-in", 30,
"Number of days after which the API key will expire")
_ = cmd.MarkFlagRequired("name")
return cmd
}
func executeCreateKey() error {
client, err := auth.ControlPlaneClientConnection("vet-cloud-key-create")
if err != nil {
return err
}
keyService, err := cloud.NewApiKeyService(client)
if err != nil {
return err
}
key, err := keyService.CreateApiKey(&cloud.CreateApiKeyRequest{
Name: keyName,
Desc: keyDescription,
ExpiryInDays: keyExpiresIn,
})
if err != nil {
return err
}
ui.PrintSuccess("API key created successfully.")
ui.PrintSuccess("Key: %s", key.Key)
ui.PrintSuccess("Expires at: %s", key.ExpiresAt.Format(time.RFC3339))
return nil
}

68
cmd/cloud/login.go Normal file
View File

@ -0,0 +1,68 @@
package cloud
import (
"context"
"fmt"
"net/http"
"github.com/cli/oauth/api"
"github.com/cli/oauth/device"
"github.com/spf13/cobra"
"github.com/safedep/vet/internal/auth"
"github.com/safedep/vet/internal/ui"
"github.com/safedep/vet/pkg/common/logger"
)
func newCloudLoginCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "login",
Short: "Login to SafeDep cloud for management tasks",
RunE: func(cmd *cobra.Command, args []string) error {
err := executeCloudLogin()
if err != nil {
logger.Errorf("Failed to login to the SafeDep cloud: %v", err)
}
return nil
},
}
return cmd
}
func executeCloudLogin() error {
token, err := executeDeviceAuthFlow()
if err != nil {
return fmt.Errorf("failed to execute device auth flow: %w", err)
}
return auth.PersistCloudTokens(token.Token,
token.RefreshToken, tenantDomain)
}
func executeDeviceAuthFlow() (*api.AccessToken, error) {
code, err := device.RequestCode(http.DefaultClient,
auth.CloudIdentityServiceDeviceCodeUrl(),
auth.CloudIdentityServiceClientId(),
[]string{"offline_access", "openid", "profile", "email"},
device.WithAudience(auth.CloudIdentityServiceAudience()))
if err != nil {
return nil, fmt.Errorf("failed to request device code: %w", err)
}
ui.PrintSuccess("Please visit %s and enter the code %s to authenticate",
code.VerificationURIComplete, code.UserCode)
token, err := device.Wait(context.TODO(),
http.DefaultClient, auth.CloudIdentityServiceTokenUrl(),
device.WaitOptions{
ClientID: auth.CloudIdentityServiceClientId(),
DeviceCode: code,
})
if err != nil {
return nil, fmt.Errorf("failed to authenticate: %w", err)
}
return token, nil
}

87
cmd/cloud/main.go Normal file
View File

@ -0,0 +1,87 @@
package cloud
import (
"fmt"
"github.com/spf13/cobra"
"github.com/safedep/vet/internal/auth"
"github.com/safedep/vet/internal/ui"
)
var (
tenantDomain string
outputCSV string
outputMarkdown string
)
func NewCloudCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "cloud",
Short: "Manage and query cloud resources (control plane)",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
cmd.PersistentFlags().StringVar(&tenantDomain, "tenant", "",
"Tenant domain to use for the command")
cmd.PersistentFlags().StringVar(&outputCSV, "csv", "",
"Output table views to a CSV file")
cmd.PersistentFlags().StringVar(&outputMarkdown, "markdown", "",
"Output table views to a Markdown file")
cmd.AddCommand(newCloudLoginCommand())
cmd.AddCommand(newRegisterCommand())
cmd.AddCommand(newCloudQuickstartCommand())
queryCmd := newQueryCommand()
queryCmd.PreRunE = requireAccessTokenCheck
pingCmd := newPingCommand()
pingCmd.PreRunE = requireAccessTokenCheck
whoamiCmd := newWhoamiCommand()
whoamiCmd.PreRunE = requireAccessTokenCheck
keyCmd := newKeyCommand()
keyCmd.PreRunE = requireAccessTokenCheck
cmd.AddCommand(queryCmd)
cmd.AddCommand(pingCmd)
cmd.AddCommand(whoamiCmd)
cmd.AddCommand(keyCmd)
cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
if tenantDomain != "" {
auth.SetRuntimeCloudTenant(tenantDomain)
}
}
return cmd
}
func requireAccessTokenCheck(cmd *cobra.Command, args []string) error {
// Check if token was obtained/refreshed 5 mins ago
// If > 5 mins, check the access token expiry
// else return
if auth.ShouldCheckAccessTokenExpiry() {
// Check if access token is expired
// If expired (ok), refresh the session
if ok, err := auth.IsAccessTokenExpired(); err != nil {
tenantDomainPlaceholder := auth.TenantDomain()
if tenantDomainPlaceholder == "" {
tenantDomainPlaceholder = "<your-tenant-domain>"
}
ui.PrintError("Automatic token refresh failed, please re-login using `vet cloud login --tenant %s`", tenantDomainPlaceholder)
return fmt.Errorf("failed to check access token expiry: %w", err)
} else if ok {
ui.PrintMsg("Refreshing Access Token")
return auth.RefreshCloudSession()
}
}
return nil
}

50
cmd/cloud/ping.go Normal file
View File

@ -0,0 +1,50 @@
package cloud
import (
"time"
"github.com/spf13/cobra"
"github.com/safedep/vet/internal/auth"
"github.com/safedep/vet/internal/ui"
"github.com/safedep/vet/pkg/cloud"
"github.com/safedep/vet/pkg/common/logger"
)
func newPingCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "ping",
Short: "Ping the control plane to check authentication and connectivity",
RunE: func(cmd *cobra.Command, args []string) error {
err := pingControlPlane()
if err != nil {
logger.Errorf("Failed to ping control plane: %v", err)
}
return nil
},
}
return cmd
}
func pingControlPlane() error {
conn, err := auth.ControlPlaneClientConnection("vet-cloud-ping")
if err != nil {
return err
}
pingService, err := cloud.NewPingService(conn)
if err != nil {
return err
}
pr, err := pingService.Ping()
if err != nil {
return err
}
ui.PrintSuccess("Ping successful. Started at %s, finished at %s",
pr.StartedAt.Format(time.RFC3339), pr.FinishedAt.Format(time.RFC3339))
return nil
}

178
cmd/cloud/query.go Normal file
View File

@ -0,0 +1,178 @@
package cloud
import (
"errors"
"sort"
"github.com/spf13/cobra"
"github.com/safedep/vet/internal/auth"
"github.com/safedep/vet/internal/ui"
"github.com/safedep/vet/pkg/cloud/query"
"github.com/safedep/vet/pkg/common/logger"
)
var (
querySql string
queryPageSize int
)
func newQueryCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "query",
Short: "Query risks by executing SQL queries",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
cmd.AddCommand(newQuerySchemaCommand())
cmd.AddCommand(newQueryExecuteCommand())
return cmd
}
func newQuerySchemaCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "schema",
Short: "Get the schema for the query service",
RunE: func(cmd *cobra.Command, args []string) error {
err := getQuerySchema()
if err != nil {
logger.Errorf("Failed to get query schema: %v", err)
}
return nil
},
}
return cmd
}
func newQueryExecuteCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "execute",
Short: "Execute a query",
RunE: func(cmd *cobra.Command, args []string) error {
err := executeQuery()
if err != nil {
logger.Errorf("Failed to execute query: %v", err)
}
return nil
},
}
cmd.Flags().StringVarP(&querySql, "sql", "s", "", "SQL query to execute")
cmd.Flags().IntVarP(&queryPageSize, "limit", "", 100, "Limit the number of results returned")
return cmd
}
func getQuerySchema() error {
client, err := auth.ControlPlaneClientConnection("vet-cloud-query")
if err != nil {
return err
}
queryService, err := query.NewQueryService(client)
if err != nil {
return err
}
response, err := queryService.GetSchema()
if err != nil {
return err
}
tbl := ui.NewTabler(ui.TablerConfig{
CsvPath: outputCSV,
MarkdownPath: outputMarkdown,
})
tbl.AddHeader("Name", "Column Name", "Selectable", "Filterable", "Reference")
schemas := response.GetSchemas()
for _, schema := range schemas {
schemaName := schema.GetName()
columns := schema.GetColumns()
sort.Slice(columns, func(i, j int) bool {
return columns[i].GetName() < columns[j].GetName()
})
for _, column := range columns {
tbl.AddRow(schemaName,
column.GetName(),
column.GetSelectable(),
column.GetFilterable(),
column.GetReferenceUrl())
}
}
return tbl.Finish()
}
func executeQuery() error {
if querySql == "" {
return errors.New("SQL string is required")
}
client, err := auth.ControlPlaneClientConnection("vet-cloud-query")
if err != nil {
return err
}
queryService, err := query.NewQueryService(client)
if err != nil {
return err
}
response, err := queryService.ExecuteSql(querySql, queryPageSize)
if err != nil {
return err
}
return renderQueryResponseAsTable(response)
}
func renderQueryResponseAsTable(response *query.QueryResponse) error {
tbl := ui.NewTabler(ui.TablerConfig{
CsvPath: outputCSV,
MarkdownPath: outputMarkdown,
})
if response.Count() == 0 {
logger.Infof("No results found")
return nil
}
ui.PrintSuccess("Query returned %d results", response.Count())
// Header
headers := []string{}
response.GetRow(0).ForEachField(func(key string, _ interface{}) {
headers = append(headers, key)
})
sort.Strings(headers)
headerRow := []interface{}{}
for _, header := range headers {
headerRow = append(headerRow, header)
}
tbl.AddHeader(headerRow...)
// Ensure we have a consistent order of columns
response.ForEachRow(func(row *query.QueryRow) {
rowValues := []interface{}{}
for _, header := range headers {
rowValues = append(rowValues, row.GetField(header))
}
tbl.AddRow(rowValues...)
})
return tbl.Finish()
}

314
cmd/cloud/quickstart.go Normal file
View File

@ -0,0 +1,314 @@
package cloud
import (
"fmt"
"os"
"time"
controltowerv1pb "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/controltower/v1"
controltowerv1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/services/controltower/v1"
"github.com/AlecAivazis/survey/v2"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"github.com/safedep/vet/internal/auth"
"github.com/safedep/vet/internal/ui"
"github.com/safedep/vet/pkg/cloud"
)
func newCloudQuickstartCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "quickstart",
Short: "Quick onboarding to SafeDep Cloud and cli setup",
RunE: func(cmd *cobra.Command, args []string) error {
err := executeCloudQuickstart()
if err != nil {
os.Exit(1)
}
return nil
},
}
return cmd
}
// executeCloudQuickstart executes an opinionated quick start flow for the user
// with the goal of least friction on-boarding to SafeDep Cloud and configuring
// the cli with everything required to start using SafeDep Cloud services.
func executeCloudQuickstart() error {
ui.PrintMsg("🚀 Starting SafeDep Cloud Quickstart...")
ui.PrintMsg("👋 Hello! Let's get you onboarded..")
// This will execute cloud authentication flow and persist the cloud tokens
// in the local config file.
if err := quickStartAuthentication(); err != nil {
return err
}
// Here we create a connection to the control plane with cloud token. This
// connection may not be multi-tenant because user may not have any tenants
// yet.
conn, err := quickStartCreateConnection()
if err != nil {
return err
}
// Here we check if the user has any tenants. If not, we create a new one.
userInfo, err := quickStartTenantSetup(conn)
if err != nil {
return err
}
// Here we get the tenant from the user info. The tenant domain is stored
// in the local config file.
tenant, err := quickStartSetupTenantFromAccess(userInfo)
if err != nil {
return err
}
ui.PrintMsg("✅ Your tenant is set to: %s", tenant.GetDomain())
// Close the previous connection
if err := conn.Close(); err != nil {
ui.PrintError("❌ Oops! Something went wrong while closing cloud connection: %s", err.Error())
return err
}
// Here we re-create the connection because we need a multi-tenant connection
conn, err = quickStartCreateConnection()
if err != nil {
return err
}
if err := quickStartAPIKeyCreation(conn, tenant); err != nil {
return err
}
ui.PrintMsg("✅ All done!")
ui.PrintMsg("")
ui.PrintMsg("🎉 You are all set! You can now start using SafeDep Cloud")
// TODO: We need the ability to auto-detect the project name and version
// and then use that to sync the results to SafeDep Cloud
ui.PrintMsg("✨ Run `vet scan -D /path/to/code --report-sync` to scan your code and sync the results to SafeDep Cloud")
return nil
}
func quickStartSetupTenantFromAccess(userInfo *controltowerv1.GetUserInfoResponse) (*controltowerv1pb.Tenant, error) {
if len(userInfo.GetAccess()) == 0 {
ui.PrintError("❌ Oops! This is weird, you should have access to at least one tenant. Please contact support.")
return nil, fmt.Errorf("no tenant access")
}
// If user has access to multiple tenants
// Ask user about which tenant they want to use if they have more than one
var tenant *controltowerv1pb.Tenant
if len(userInfo.GetAccess()) > 1 {
// Print all tenants with index
var tenantOptions []string
ui.PrintMsg("🔍 You have access to the following tenants:")
for idx, tenant := range userInfo.GetAccess() {
ui.PrintMsg("%s", fmt.Sprintf(" - [%d] %s", idx, tenant.GetTenant().GetDomain()))
tenantOptions = append(tenantOptions, tenant.GetTenant().GetDomain())
}
// Ask user which tenant they want to use
var tenantIndex int
err := survey.AskOne(&survey.Select{
Message: "🔍 Which tenant do you want to use?",
Options: tenantOptions,
}, &tenantIndex)
if err != nil {
ui.PrintError("❌ Oops! Something went wrong while asking which tenant to use: %s", err.Error())
return nil, err
}
tenant = userInfo.GetAccess()[tenantIndex].GetTenant()
} else {
tenant = userInfo.GetAccess()[0].GetTenant()
}
if err := auth.PersistTenantDomain(tenant.GetDomain()); err != nil {
ui.PrintError("❌ Oops! Something went wrong while persisting your tenant domain: %s", err.Error())
return nil, err
}
return tenant, nil
}
func quickStartAuthentication() error {
ui.PrintMsg("🔑 Start by creating an account or sign-in to your existing account")
token, err := executeDeviceAuthFlow()
if err != nil {
ui.PrintError("❌ Oops! Something went wrong while authenticating you: %s", err.Error())
ui.PrintMsg(" If you are using email and password, ensure your email is verified.")
return err
}
ui.PrintSuccess("✅ Successfully authenticated you!")
ui.PrintMsg("🔑 Saving your cloud credentials in your local config...")
if err := auth.PersistCloudTokens(token.Token, token.RefreshToken, ""); err != nil {
ui.PrintError("❌ Oops! Something went wrong while saving your cloud credentials: %s", err.Error())
return err
}
ui.PrintSuccess("✅ Successfully saved your cloud credentials!")
return nil
}
func quickStartCreateConnection() (*grpc.ClientConn, error) {
conn, err := auth.ControlPlaneClientConnection("vet-cloud-quickstart")
if err != nil {
ui.PrintError("❌ Oops! Something went wrong while creating cloud connection: %s", err.Error())
return nil, err
}
return conn, nil
}
func quickStartTenantSetup(conn *grpc.ClientConn) (*controltowerv1.GetUserInfoResponse, error) {
ui.PrintMsg("🔍 Checking if you have an existing tenant...")
userService, err := cloud.NewUserService(conn)
if err != nil {
ui.PrintError("❌ Oops! Something went wrong while creating user service: %s", err.Error())
return nil, err
}
userInfo, err := userService.CurrentUserInfo()
if err != nil {
return quickStartCreateNewTenant(conn)
}
ui.PrintMsg("✅ You are already registered with SafeDep Cloud")
return userInfo, nil
}
func quickStartCreateNewTenant(conn *grpc.ClientConn) (*controltowerv1.GetUserInfoResponse, error) {
ui.PrintMsg("📝 Looks like you don't have an existing tenant. Let's create one for you...")
userName, domain, err := quickStartGetTenantInputs()
if err != nil {
return nil, err
}
onboardingService, err := cloud.NewOnboardingService(conn)
if err != nil {
ui.PrintError("❌ Oops! Something went wrong while creating onboarding service: %s", err.Error())
return nil, err
}
_, err = onboardingService.Register(&cloud.RegisterRequest{
Name: userName,
Email: registerEmail,
OrgName: "Quickstart Organization",
OrgDomain: domain,
})
if err != nil {
ui.PrintError("❌ Oops! Something went wrong while registering your tenant: %s", err.Error())
return nil, err
}
ui.PrintSuccess("✅ Successfully created a new tenant!")
ui.PrintMsg("🔑 Please wait while we get you onboarded...")
userService, err := cloud.NewUserService(conn)
if err != nil {
ui.PrintError("❌ Oops! Something went wrong while creating user service: %s", err.Error())
return nil, err
}
return userService.CurrentUserInfo()
}
func quickStartGetTenantInputs() (string, string, error) {
var userName string
err := survey.AskOne(&survey.Input{
Message: "👤 What should we call you?",
Default: "John Doe",
}, &userName)
if err != nil {
ui.PrintError("❌ Oops! Something went wrong while asking for your name: %s", err.Error())
return "", "", err
}
autoDomain := fmt.Sprintf("quickstart-%s", time.Now().Format("20060102150405"))
var domain string
err = survey.AskOne(&survey.Input{
Message: "📝 We have automatically generated a domain for you. Here is your chance to update",
Default: autoDomain,
}, &domain)
if err != nil {
ui.PrintError("❌ Oops! Something went wrong while asking for your domain: %s", err.Error())
return "", "", err
}
if domain == "" {
domain = autoDomain
}
return userName, domain, nil
}
func quickStartAPIKeyCreation(conn *grpc.ClientConn, tenant *controltowerv1pb.Tenant) error {
var createAPIKey bool
err := survey.AskOne(&survey.Confirm{
Message: "🔑 Do you want to create a new API key for this tenant?",
Default: true,
}, &createAPIKey)
if err != nil {
ui.PrintError("❌ Oops! Something went wrong while asking if you want to create an API key: %s", err.Error())
return err
}
if !createAPIKey {
return nil
}
var showAPIKey bool
err = survey.AskOne(&survey.Confirm{
Message: "Would you like to see the API key in addition to configuring it?",
Default: true,
}, &showAPIKey)
if err != nil {
ui.PrintError("❌ Oops! Something went wrong while asking about showing the API key: %s", err.Error())
return err
}
createApiKeyService, err := cloud.NewApiKeyService(conn)
if err != nil {
ui.PrintError("❌ Oops! Something went wrong while creating the API key service: %s", err.Error())
return err
}
apiKey, err := createApiKeyService.CreateApiKey(&cloud.CreateApiKeyRequest{
Name: fmt.Sprintf("Quick Start API Key: %s", time.Now().Format("20060102150405")),
Desc: "This is a quick start API key created for you by vet",
ExpiryInDays: 30,
})
if err != nil {
ui.PrintError("❌ Oops! Something went wrong while creating the API key: %s", err.Error())
return err
}
if err := auth.PersistApiKey(apiKey.Key, tenant.GetDomain()); err != nil {
ui.PrintError("❌ Oops! Something went wrong while persisting the API key: %s", err.Error())
return err
}
if showAPIKey {
ui.PrintMsg("✅ Here is your API key: %s", text.BgGreen.Sprint(apiKey.Key))
ui.PrintMsg("🔒 Your key will expire on: %s", apiKey.ExpiresAt.Format(time.RFC3339))
}
ui.PrintMsg(" Your tenant domain is: %s", text.BgGreen.Sprint(tenant.GetDomain()))
ui.PrintMsg("🔑 Please save this API key in a secure location, it will not be shown again.")
return nil
}

77
cmd/cloud/register.go Normal file
View File

@ -0,0 +1,77 @@
package cloud
import (
"fmt"
"github.com/spf13/cobra"
"github.com/safedep/vet/internal/auth"
"github.com/safedep/vet/internal/ui"
"github.com/safedep/vet/pkg/cloud"
"github.com/safedep/vet/pkg/common/logger"
)
var (
registerEmail string
registerName string
registerOrgName string
registerOrgDomain string
)
func newRegisterCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "register",
Short: "Register a new user and tenant",
RunE: func(cmd *cobra.Command, args []string) error {
err := registerUserTenant()
if err != nil {
logger.Errorf("Failed to register user: %v", err)
}
return nil
},
}
cmd.Flags().StringVar(&registerEmail, "email", "cloud@safedep.io", "Email of the user (not required for SafeDep cloud)")
cmd.Flags().StringVar(&registerName, "name", "", "Name of the user")
cmd.Flags().StringVar(&registerOrgName, "org-name", "", "Name of the organization")
cmd.Flags().StringVar(&registerOrgDomain, "org-domain", "", "Domain of the organization")
_ = cmd.MarkFlagRequired("name")
_ = cmd.MarkFlagRequired("org-name")
_ = cmd.MarkFlagRequired("org-domain")
return cmd
}
func registerUserTenant() error {
conn, err := auth.ControlPlaneClientConnection("vet-cloud-register")
if err != nil {
return err
}
onboardingService, err := cloud.NewOnboardingService(conn)
if err != nil {
return err
}
res, err := onboardingService.Register(&cloud.RegisterRequest{
Name: registerName,
Email: registerEmail,
OrgName: registerOrgName,
OrgDomain: registerOrgDomain,
})
if err != nil {
return err
}
ui.PrintSuccess("Registered user and tenant.")
ui.PrintSuccess("Tenant domain: %s", res.TenantDomain)
err = auth.PersistTenantDomain(res.TenantDomain)
if err != nil {
return fmt.Errorf("failed to persist tenant domain: %w", err)
}
return nil
}

63
cmd/cloud/whoami.go Normal file
View File

@ -0,0 +1,63 @@
package cloud
import (
"fmt"
controltowerv1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/controltower/v1"
"github.com/spf13/cobra"
"github.com/safedep/vet/internal/auth"
"github.com/safedep/vet/internal/ui"
"github.com/safedep/vet/pkg/cloud"
"github.com/safedep/vet/pkg/common/logger"
)
func newWhoamiCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "whoami",
Short: "Print information about the current user",
RunE: func(cmd *cobra.Command, args []string) error {
err := executeWhoami()
if err != nil {
logger.Errorf("Failed to execute whoami: %v", err)
}
return nil
},
}
return cmd
}
func executeWhoami() error {
conn, err := auth.ControlPlaneClientConnection("vet-cloud-whoami")
if err != nil {
return err
}
userService, err := cloud.NewUserService(conn)
if err != nil {
return err
}
res, err := userService.CurrentUserInfo()
if err != nil {
return err
}
tbl := ui.NewTabler(ui.TablerConfig{})
tbl.AddHeader("Email", "Tenant", "Access Level")
for _, access := range res.GetAccess() {
accessName := "UNSPECIFIED"
if name, ok := controltowerv1.AccessLevel_name[int32(access.GetLevel())]; ok {
accessName = name
}
tbl.AddRow(res.GetUser().GetEmail(),
access.GetTenant().GetDomain(),
fmt.Sprintf("%s (%d)", accessName, access.GetRole()))
}
return tbl.Finish()
}

33
cmd/code/lang.go Normal file
View File

@ -0,0 +1,33 @@
package code
import (
"github.com/safedep/code/core"
"github.com/safedep/code/lang"
"github.com/safedep/vet/pkg/common/logger"
)
func getAllLanguageCodeStrings() ([]string, error) {
langs, err := lang.AllLanguages()
if err != nil {
return nil, err
}
var languageCodes []string
for _, lang := range langs {
languageCodes = append(languageCodes, string(lang.Meta().Code))
}
return languageCodes, nil
}
func getLanguagesFromCodes(languageCodes []string) ([]core.Language, error) {
var languages []core.Language
for _, languageCode := range languageCodes {
language, err := lang.GetLanguage(languageCode)
if err != nil {
logger.Fatalf("failed to get language for code %s: %v", languageCode, err)
return nil, err
}
languages = append(languages, language)
}
return languages, nil
}

28
cmd/code/main.go Normal file
View File

@ -0,0 +1,28 @@
package code
import (
"github.com/spf13/cobra"
"github.com/safedep/vet/internal/command"
)
var languageCodes []string
func NewCodeCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "code",
Short: "Analyze source code",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
defaultAllLanguageCodes, err := getAllLanguageCodeStrings()
command.FailOnError("setup-default-languages", err)
cmd.PersistentFlags().StringArrayVar(&languageCodes, "lang", defaultAllLanguageCodes, "Source code languages to analyze")
cmd.AddCommand(newScanCommand())
return cmd
}

96
cmd/code/scan.go Normal file
View File

@ -0,0 +1,96 @@
package code
import (
"context"
"regexp"
"github.com/spf13/cobra"
"github.com/safedep/vet/internal/command"
"github.com/safedep/vet/internal/ui"
"github.com/safedep/vet/pkg/code"
"github.com/safedep/vet/pkg/common/logger"
"github.com/safedep/vet/pkg/storage"
)
var (
dbPath string
appDirs []string
importDirs []string
excludePatterns []string
skipDependencyUsagePlugin bool
)
func newScanCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "scan",
Short: "Scan source code",
RunE: func(cmd *cobra.Command, args []string) error {
startScan()
return nil
},
}
cmd.Flags().StringVar(&dbPath, "db", "", "Path to create the sqlite database")
cmd.Flags().StringArrayVar(&appDirs, "app", []string{"."}, "Directories to scan for application code files")
cmd.Flags().StringArrayVar(&importDirs, "import-dir", []string{}, "Directories to scan for import files")
cmd.Flags().StringArrayVarP(&excludePatterns, "exclude", "", []string{},
"Name patterns to ignore while scanning a codebase")
cmd.Flags().BoolVar(&skipDependencyUsagePlugin, "skip-dependency-usage-plugin", false, "Skip dependency usage plugin analysis")
_ = cmd.MarkFlagRequired("db")
return cmd
}
func startScan() {
command.FailOnError("scan", internalStartScan())
}
func internalStartScan() error {
allowedLanguages, err := getLanguagesFromCodes(languageCodes)
if err != nil {
logger.Fatalf("failed to get languages from codes: %v", err)
return err
}
entSqliteStorage, err := storage.NewEntSqliteStorage(storage.EntSqliteClientConfig{
Path: dbPath,
ReadOnly: false,
SkipSchemaCreation: false,
})
if err != nil {
logger.Fatalf("failed to create ent sqlite storage: %v", err)
return err
}
excludePatternsRegexps := []*regexp.Regexp{}
for _, pattern := range excludePatterns {
excludePatternsRegexps = append(excludePatternsRegexps, regexp.MustCompile(pattern))
}
codeScanner, err := code.NewScanner(code.ScannerConfig{
AppDirectories: appDirs,
ImportDirectories: importDirs,
ExcludePatterns: excludePatternsRegexps,
Languages: allowedLanguages,
SkipDependencyUsagePlugin: skipDependencyUsagePlugin,
Callbacks: &code.ScannerCallbackRegistry{
OnScanStart: func() error {
ui.StartSpinner("Scanning code")
return nil
},
OnScanEnd: func() error {
ui.StopSpinner()
ui.PrintSuccess("🚀 Code scanning completed. Run vet scan with code context using --code flag")
return nil
},
},
}, entSqliteStorage)
if err != nil {
logger.Fatalf("failed to create code scanner: %v", err)
return err
}
return codeScanner.Scan(context.Background())
}

72
cmd/doc/generate.go Normal file
View File

@ -0,0 +1,72 @@
package doc
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
var (
// markdownOutDir is the output directory for markdown doc files
markdownOutDir string
// manOutDir is the output directory for troff (man markup) doc files
manOutDir string
)
func newGenerateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "generate",
Short: "Generate docs / manual artifacts",
RunE: func(cmd *cobra.Command, args []string) error {
// we specify the root (see, not parent) command since its the starting point for docs
return runGenerateCommand(cmd.Root())
},
}
cmd.PersistentFlags().StringVar(&markdownOutDir, "markdown", "", "The output directory for markdown doc files")
cmd.PersistentFlags().StringVar(&manOutDir, "man", "", "The output directory for troff (man markup) doc files")
cmd.PreRunE = func(cmd *cobra.Command, args []string) error {
// At least one of the output directory is required
if markdownOutDir == "" && manOutDir == "" {
return errors.New("no output directory specified, at least one of the output directory is required")
}
return nil
}
return cmd
}
func runGenerateCommand(rootCmd *cobra.Command) error {
// If markdown directory is specified
if markdownOutDir != "" {
// Create Markdown Manual
if err := doc.GenMarkdownTree(rootCmd, markdownOutDir); err != nil {
return errors.Wrap(err, "failed to generate markdown manual")
}
fmt.Println("Markdown manual doc created in: ", markdownOutDir)
}
// If troff (man markup) directory is specified
if manOutDir != "" {
// Create Troff (man markup) Manual
manHeader := &doc.GenManHeader{
Title: "VET",
Source: "SafeDep",
Manual: "VET Manual",
}
if err := doc.GenManTree(rootCmd, manHeader, manOutDir); err != nil {
return errors.Wrap(err, "failed to generate man (troff) manual")
}
fmt.Println("Troff (man markup) manual doc created in: ", manOutDir)
}
return nil
}

17
cmd/doc/main.go Normal file
View File

@ -0,0 +1,17 @@
package doc
import (
"github.com/spf13/cobra"
)
func NewDocCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "doc",
Short: "Documentation generation internal utilities",
Hidden: true, // Hide from vet public commands and docs itself, since its only build utility
}
cmd.AddCommand(newGenerateCommand())
return cmd
}

20
cmd/inspect/main.go Normal file
View File

@ -0,0 +1,20 @@
package inspect
import (
"github.com/spf13/cobra"
)
func NewPackageInspectCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "inspect",
Short: "Inspect an OSS package",
Long: `Inspect an OSS package using deep inspection and analysis.
This command will integrate with local and remote analysis services.`,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
cmd.AddCommand(newPackageMalwareInspectCommand())
return cmd
}

310
cmd/inspect/malware.go Normal file
View File

@ -0,0 +1,310 @@
package inspect
import (
"context"
"fmt"
"os"
"strings"
"time"
"buf.build/gen/go/safedep/api/grpc/go/safedep/services/malysis/v1/malysisv1grpc"
malysisv1pb "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/malysis/v1"
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
malysisv1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/services/malysis/v1"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/safedep/dry/adapters"
"github.com/safedep/dry/api/pb"
"github.com/safedep/dry/utils"
"github.com/spf13/cobra"
"github.com/safedep/vet/internal/analytics"
"github.com/safedep/vet/internal/auth"
"github.com/safedep/vet/internal/ui"
"github.com/safedep/vet/pkg/common/registry"
vetutils "github.com/safedep/vet/pkg/common/utils"
"github.com/safedep/vet/pkg/malysis"
"github.com/safedep/vet/pkg/reporter"
)
var (
malwareAnalysisPackageUrl string
malwareAnalysisTimeout time.Duration
malwareAnalysisReportJSON string
malwareAnalysisReportOSV string
malwareAnalysisNoWait bool
malwareReportOSVFinderName string
malwareReportOSVContacts []string
malwareReportOSVReferenceURL string
malwareReportOSVUseRange bool
)
func newPackageMalwareInspectCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "malware",
Short: "Inspect an OSS package for malware",
Long: `Inspect an OSS package for malware using SafeDep Malware Analysis API`,
RunE: func(cmd *cobra.Command, args []string) error {
err := executeMalwareAnalysis()
if err != nil {
ui.PrintError("Failed: %v", err)
}
return nil
},
}
cmd.Flags().StringVar(&malwareAnalysisPackageUrl, "purl", "",
"Package URL to inspect for malware")
cmd.Flags().DurationVar(&malwareAnalysisTimeout, "timeout", 5*time.Minute,
"Timeout for malware analysis")
cmd.Flags().StringVar(&malwareAnalysisReportJSON, "report-json", "",
"Path to save malware analysis report in JSON format")
cmd.Flags().StringVar(&malwareAnalysisReportOSV, "report-osv", "",
"Dir path to save malware analysis report in OSV format and ossf/malicious-packages format")
cmd.Flags().BoolVar(&malwareAnalysisNoWait, "no-wait", false,
"Do not wait for malware analysis to complete")
cmd.Flags().StringVar(&malwareReportOSVFinderName, "report-osv-finder-name", "",
"Finder name for malware analysis report in OSV format")
cmd.Flags().StringSliceVar(&malwareReportOSVContacts, "report-osv-contacts", []string{},
"Contacts for malware analysis report in OSV format (URL, email, etc.)")
cmd.Flags().StringVar(&malwareReportOSVReferenceURL, "report-osv-reference-url", "",
"Custom reference URL for malware analysis report (defaults to app.safedep.io)")
cmd.Flags().BoolVar(&malwareReportOSVUseRange, "report-osv-with-ranges", false,
"Use range-based versioning in OSV report (default: use explicit versions)")
_ = cmd.MarkFlagRequired("purl")
return cmd
}
func executeMalwareAnalysis() error {
analytics.TrackCommandInspectMalwareAnalysis()
err := auth.Verify()
if err != nil {
return fmt.Errorf("access to Malicious Package Analysis requires an API key. " +
"For more details: https://docs.safedep.io/cloud/quickstart/")
}
cc, err := auth.MalwareAnalysisClientConnection("malware-analysis")
if err != nil {
return err
}
service := malysisv1grpc.NewMalwareAnalysisServiceClient(cc)
purl, err := pb.NewPurlPackageVersion(malwareAnalysisPackageUrl)
if err != nil {
return err
}
githubClient, err := adapters.NewGithubClient(adapters.DefaultGitHubClientConfig())
if err != nil {
return fmt.Errorf("failed to create GitHub client: %v", err)
}
versionResolver, err := registry.NewPackageVersionResolver(githubClient)
if err != nil {
return fmt.Errorf("failed to create package version resolver: %v", err)
}
packageVersion := purl.PackageVersion()
// If package version is empty or latest replace it with actual literal latest version
// Reference: https://github.com/safedep/vet/issues/446
if packageVersion.GetVersion() == "" || packageVersion.GetVersion() == "latest" {
ui.PrintMsg("Resolving package version")
version, err := versionResolver.ResolvePackageLatestVersion(purl.Ecosystem(), purl.Name())
if err != nil {
return fmt.Errorf("failed to resolve package latest version: %v", err)
}
ui.PrintSuccess("Resolved package version: %s", version)
packageVersion.Version = version
}
ctx := context.Background()
ctx, cancelFun := context.WithTimeout(ctx, malwareAnalysisTimeout)
defer cancelFun()
// For GitHub Actions packages, we need to resolve the commit hash
if packageVersion.GetPackage().GetEcosystem() == packagev1.Ecosystem_ECOSYSTEM_GITHUB_ACTIONS {
ui.PrintMsg("Resolving commit hash for GitHub Actions package")
commitHash, err := resolveGitHubActionsCommitHash(ctx, packageVersion)
if err != nil {
return fmt.Errorf("failed to resolve commit hash for GitHub Actions package: %v", err)
}
ui.PrintSuccess("Resolved commit hash for GitHub Actions package: %s", commitHash)
packageVersion.Version = commitHash
}
analyzePackageResponse, err := service.AnalyzePackage(ctx, &malysisv1.AnalyzePackageRequest{
Target: &malysisv1pb.PackageAnalysisTarget{
PackageVersion: packageVersion,
},
})
if err != nil {
return fmt.Errorf("failed to submit package for malware analysis: %v", err)
}
ui.PrintMsg("Submitted package for malware analysis with ID: %s",
analyzePackageResponse.GetAnalysisId())
if malwareAnalysisNoWait {
return nil
}
ui.StartSpinner("Waiting for malware analysis to complete")
var report *malysisv1pb.Report
var verificationRecord *malysisv1pb.VerificationRecord
for {
reportResponse, err := service.GetAnalysisReport(ctx, &malysisv1.GetAnalysisReportRequest{
AnalysisId: analyzePackageResponse.GetAnalysisId(),
})
if err != nil {
return fmt.Errorf("failed to get malware analysis report: %v", err)
}
if reportResponse.GetStatus() == malysisv1.AnalysisStatus_ANALYSIS_STATUS_FAILED {
return fmt.Errorf("malware analysis failed: %s", reportResponse.GetErrorMessage())
}
if reportResponse.GetStatus() == malysisv1.AnalysisStatus_ANALYSIS_STATUS_COMPLETED {
report = reportResponse.GetReport()
verificationRecord = reportResponse.GetVerificationRecord()
break
}
time.Sleep(5 * time.Second)
}
ui.StopSpinner()
if report == nil {
return fmt.Errorf("malware analysis report is empty")
}
ui.PrintSuccess("Malware analysis completed successfully")
if malwareAnalysisReportJSON != "" {
ui.PrintMsg("Generating JSON report")
err = writeJSONReport(report)
if err != nil {
ui.PrintError("Failed to render malware analysis report in JSON format: %v", err)
}
}
if malwareAnalysisReportOSV != "" {
if !report.GetInference().GetIsMalware() {
ui.PrintWarning("Report is not malware, skipping OSV report generation")
return nil
} else {
ui.PrintMsg("Generating OSV report in: %s", malwareAnalysisReportOSV)
err = writeOSVReport(report)
if err != nil {
ui.PrintError("Failed to render malware analysis report in OSV format: %v", err)
}
}
}
return renderMalwareAnalysisReport(malwareAnalysisPackageUrl,
analyzePackageResponse.GetAnalysisId(), report, verificationRecord)
}
func writeOSVReport(report *malysisv1pb.Report) error {
err := os.MkdirAll(malwareAnalysisReportOSV, 0o755)
if err != nil {
return fmt.Errorf("failed to create directory: %v", err)
}
generator, err := malysis.NewOpenSSFMaliciousPackageReportGenerator(malysis.OpenSSFMaliciousPackageReportGeneratorConfig{
Dir: malwareAnalysisReportOSV,
})
if err != nil {
return fmt.Errorf("failed to create OpenSSF malicious package report generator: %v", err)
}
err = generator.GenerateReport(context.Background(), report, malysis.OpenSSFMaliciousPackageReportParams{
FinderName: malwareReportOSVFinderName,
Contacts: malwareReportOSVContacts,
ReferenceURL: malwareReportOSVReferenceURL,
UseRange: malwareReportOSVUseRange,
})
if err != nil {
return fmt.Errorf("failed to generate OpenSSF malicious package report: %v", err)
}
return nil
}
func writeJSONReport(report *malysisv1pb.Report) error {
data, err := utils.ToPbJson(report, " ")
if err != nil {
return err
}
return os.WriteFile(malwareAnalysisReportJSON, []byte(data), 0o644)
}
func renderMalwareAnalysisReport(purl string, analysisId string,
report *malysisv1pb.Report, vr *malysisv1pb.VerificationRecord,
) error {
ui.PrintMsg("Malware analysis report for package: %s", purl)
tbl := table.NewWriter()
tbl.SetOutputMirror(os.Stdout)
tbl.SetStyle(table.StyleLight)
tbl.AppendHeader(table.Row{"Package URL", "Status", "Confidence"})
status := reporter.InfoBgText(" SAFE ")
if report.GetInference().GetIsMalware() {
if vr != nil && vr.IsMalware {
status = reporter.CriticalBgText(" MALICIOUS ")
} else {
status = reporter.WarningBgText(" SUSPICIOUS ")
}
}
confidence := report.GetInference().GetConfidence().String()
confidence = strings.TrimPrefix(confidence, "CONFIDENCE_")
tbl.AppendRow(table.Row{purl, status, confidence})
tbl.Render()
fmt.Println()
fmt.Println(reporter.WarningText(fmt.Sprintf("** The full report is available at: %s",
reportVisualizationUrl(analysisId))))
fmt.Println()
return nil
}
func reportVisualizationUrl(analysisId string) string {
return malysis.ReportURL(analysisId)
}
func resolveGitHubActionsCommitHash(ctx context.Context, packageVersion *packagev1.PackageVersion) (string, error) {
gha, err := adapters.NewGithubClient(adapters.DefaultGitHubClientConfig())
if err != nil {
return "", fmt.Errorf("failed to create GitHub client: %v", err)
}
parts := strings.Split(packageVersion.GetPackage().GetName(), "/")
if len(parts) != 2 {
return "", fmt.Errorf("invalid repository name: %s - should be in the format <owner>/<repo>", packageVersion.GetPackage().GetName())
}
owner := parts[0]
repo := parts[1]
return vetutils.ResolveGitHubRepositoryCommitSHA(ctx,
gha, owner, repo, packageVersion.GetVersion())
}

17
cmd/server/main.go Normal file
View File

@ -0,0 +1,17 @@
package server
import "github.com/spf13/cobra"
func NewServerCommand() *cobra.Command {
cmd := cobra.Command{
Use: "server",
Short: "Start available servers",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
cmd.AddCommand(newMcpServerCommand())
return &cmd
}

195
cmd/server/mcp.go Normal file
View File

@ -0,0 +1,195 @@
package server
import (
"fmt"
"os"
"buf.build/gen/go/safedep/api/grpc/go/safedep/services/insights/v2/insightsv2grpc"
"buf.build/gen/go/safedep/api/grpc/go/safedep/services/malysis/v1/malysisv1grpc"
"github.com/safedep/dry/adapters"
"github.com/spf13/cobra"
"github.com/safedep/vet/internal/auth"
"github.com/safedep/vet/mcp"
"github.com/safedep/vet/mcp/server"
"github.com/safedep/vet/mcp/tools"
"github.com/safedep/vet/pkg/common/logger"
)
var (
mcpServerSseServerAddr string
mcpServerServerType string
skipDefaultTools bool
registerVetSQLQueryTool bool
vetSQLQueryToolDBPath string
registerPackageRegistryTool bool
sseServerAllowedOrigins []string
sseServerAllowedHosts []string
)
func newMcpServerCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "mcp",
Short: "Start the MCP server",
RunE: func(cmd *cobra.Command, args []string) error {
err := startMcpServer()
if err != nil {
logger.Errorf("Failed to start server: %v", err)
os.Exit(1)
}
return nil
},
}
cmd.Flags().StringVar(&mcpServerSseServerAddr, "sse-server-addr", "localhost:9988", "The address to listen for SSE connections")
cmd.Flags().StringVar(&mcpServerServerType, "server-type", "stdio", "The type of server to start (stdio, sse)")
cmd.Flags().StringSliceVar(
&sseServerAllowedOrigins,
"sse-allowed-origins",
nil,
"List of allowed origin prefixes for SSE connections. By default, we allow http://localhost:, http://127.0.0.1: and https://localhost:.",
)
cmd.Flags().StringSliceVar(
&sseServerAllowedHosts,
"sse-allowed-hosts",
nil,
"List of allowed hosts for SSE connections. By default, we allow localhost:9988, 127.0.0.1:9988 and [::1]:9988.",
)
// We allow skipping default tools to allow for custom tools to be registered when the server starts.
// This is useful for agents to avoid unnecessary tool registration.
cmd.Flags().BoolVar(&skipDefaultTools, "skip-default-tools", false, "Skip registering default tools")
// Options to register sqlite3 query tool
cmd.Flags().BoolVar(&registerVetSQLQueryTool, "sql-query-tool", false, "Register the vet report query by SQL tool (requires database path)")
cmd.Flags().StringVar(&vetSQLQueryToolDBPath, "sql-query-tool-db-path", "", "The path to the vet SQLite3 database file")
// Options to register package registry tool
cmd.Flags().BoolVar(&registerPackageRegistryTool, "package-registry-tool", false, "Register the package registry tool")
cmd.PreRunE = func(cmd *cobra.Command, args []string) error {
if registerVetSQLQueryTool && vetSQLQueryToolDBPath == "" {
return fmt.Errorf("database path is required for SQL query tool")
}
return nil
}
return cmd
}
func startMcpServer() error {
driver, err := buildMcpDriver()
if err != nil {
return fmt.Errorf("failed to build MCP driver: %w", err)
}
var mcpSrv server.McpServer
switch mcpServerServerType {
case "stdio":
mcpSrv, err = server.NewMcpServerWithStdioTransport(server.DefaultMcpServerConfig())
case "sse":
config := server.DefaultMcpServerConfig()
// Override with user supplied config
config.SseServerAddr = mcpServerSseServerAddr
// override origins and hosts defaults only if user explicitly set them.
// When explicitly passed as cmd line args, cobra parses
// --sse-allowed-hosts='' as empty slice. Otherwise if not provided,
// sse-allowed-hosts will be nil.
if sseServerAllowedOrigins != nil {
config.SseServerAllowedOriginsPrefix = sseServerAllowedOrigins
}
if sseServerAllowedHosts != nil {
config.SseServerAllowedHosts = sseServerAllowedHosts
}
mcpSrv, err = server.NewMcpServerWithSseTransport(config)
default:
return fmt.Errorf("invalid server type: %s", mcpServerServerType)
}
if err != nil {
return fmt.Errorf("failed to create MCP server: %w", err)
}
if !skipDefaultTools {
err = doRegisterDefaultTools(mcpSrv, driver)
if err != nil {
return fmt.Errorf("failed to register default tools: %w", err)
}
}
if registerVetSQLQueryTool {
err = doRegisterVetSQLQueryTool(mcpSrv)
if err != nil {
return fmt.Errorf("failed to register vet SQL query tool: %w", err)
}
}
if registerPackageRegistryTool {
err = doRegisterPackageRegistryTool(mcpSrv, driver)
if err != nil {
return fmt.Errorf("failed to register package registry tool: %w", err)
}
}
err = mcpSrv.Start()
if err != nil {
return fmt.Errorf("failed to start MCP server: %w", err)
}
return nil
}
func doRegisterDefaultTools(mcpSrv server.McpServer, driver mcp.Driver) error {
return tools.RegisterAll(mcpSrv, driver)
}
func doRegisterVetSQLQueryTool(mcpSrv server.McpServer) error {
tool, err := tools.NewVetSQLQueryTool(vetSQLQueryToolDBPath)
if err != nil {
return fmt.Errorf("failed to create vet SQL query tool: %w", err)
}
return mcpSrv.RegisterTool(tool)
}
func doRegisterPackageRegistryTool(mcpSrv server.McpServer, driver mcp.Driver) error {
err := mcpSrv.RegisterTool(tools.NewPackageRegistryTool(driver))
if err != nil {
return fmt.Errorf("failed to register package registry tool: %w", err)
}
return nil
}
func buildMcpDriver() (mcp.Driver, error) {
insightsConn, err := auth.InsightsV2CommunityClientConnection("vet-mcp-insights")
if err != nil {
return nil, fmt.Errorf("failed to create insights client: %w", err)
}
communityConn, err := auth.MalwareAnalysisCommunityClientConnection("vet-mcp-malware")
if err != nil {
return nil, fmt.Errorf("failed to create community client: %w", err)
}
insightsClient := insightsv2grpc.NewInsightServiceClient(insightsConn)
malysisClient := malysisv1grpc.NewMalwareAnalysisServiceClient(communityConn)
githubAdapter, err := adapters.NewGithubClient(adapters.DefaultGitHubClientConfig())
if err != nil {
return nil, fmt.Errorf("failed to create github client: %w", err)
}
driver, err := mcp.NewDefaultDriver(insightsClient, malysisClient, githubAdapter)
if err != nil {
return nil, fmt.Errorf("failed to create MCP driver: %w", err)
}
return driver, nil
}

60
cmd/server/mcp_test.go Normal file
View File

@ -0,0 +1,60 @@
package server
import (
"context"
"testing"
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
"github.com/stretchr/testify/assert"
"github.com/safedep/vet/test"
)
func TestMcpDriver(t *testing.T) {
test.EnsureEndToEndTestIsEnabled(t)
driver, err := buildMcpDriver()
if err != nil {
t.Fatalf("failed to build MCP driver: %v", err)
}
t.Run("malysis community service is accessible", func(t *testing.T) {
report, err := driver.GetPackageVersionMalwareReport(context.Background(), &packagev1.PackageVersion{
Package: &packagev1.Package{
Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM,
Name: "express",
},
Version: "4.17.1",
})
assert.NoError(t, err)
assert.NotNil(t, report)
})
t.Run("insights community service is accessible", func(t *testing.T) {
vulns, err := driver.GetPackageVersionVulnerabilities(context.Background(), &packagev1.PackageVersion{
Package: &packagev1.Package{
Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM,
Name: "express",
},
Version: "4.17.1",
})
assert.NoError(t, err)
assert.NotNil(t, vulns)
assert.NotEmpty(t, vulns)
})
t.Run("package registry adapter is accessible", func(t *testing.T) {
res, err := driver.GetPackageLatestVersion(context.Background(), &packagev1.Package{
Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM,
Name: "express",
})
assert.NoError(t, err)
assert.NotNil(t, res)
assert.Equal(t, "express", res.GetPackage().GetName())
assert.Equal(t, packagev1.Ecosystem_ECOSYSTEM_NPM, res.GetPackage().GetEcosystem())
assert.NotEmpty(t, res.GetPackage().GetName())
})
}

152
connect.go Normal file
View File

@ -0,0 +1,152 @@
package main
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"github.com/AlecAivazis/survey/v2"
"github.com/cli/oauth/device"
"github.com/spf13/cobra"
"github.com/safedep/vet/internal/connect"
"github.com/safedep/vet/internal/ui"
"github.com/safedep/vet/pkg/common/logger"
)
func newConnectCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "connect",
Short: "Connect with 3rd party apps",
RunE: func(cmd *cobra.Command, args []string) error {
return errors.New("a valid sub-command is required")
},
}
cmd.AddCommand(connectGithubCommand())
return cmd
}
func connectGithubCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "github",
RunE: func(cmd *cobra.Command, args []string) error {
githubAccessToken, err := getAccessTokenFromUser()
if err != nil {
githubAccessToken, err = getAccessTokenViaDeviceFlow()
}
if err != nil {
logger.Fatalf("Failed to connect with Github API: %s", err.Error())
}
err = connect.PersistGithubAccessToken(githubAccessToken)
if err != nil {
logger.Fatalf("Failed to persist Github connection token: %s", err.Error())
}
ui.PrintSuccess("Github Access Token configured and saved at '%s' for your convenience.", connect.GetConfigFileHint())
ui.PrintSuccess("You can use vet to scan your github repositories")
ui.PrintSuccess("Run the command to scan your github repository")
ui.PrintSuccess("\tvet scan --github https://github.com/<Org|User>/<Repo>")
os.Exit(1)
return nil
},
}
return cmd
}
func getAccessTokenFromUser() (string, error) {
var by_github_acces_token string
prompt := &survey.Select{
Message: "Do you have access token ready?",
Options: []string{"Y", "N"},
Default: "Y",
}
err := survey.AskOne(prompt, &by_github_acces_token)
if err != nil {
return "", err
}
if by_github_acces_token != "Y" {
return "", fmt.Errorf("user refused to provide access token")
}
password := &survey.Password{
Message: "Provide your access token: ",
}
var accessToken string
err = survey.AskOne(password, &accessToken)
if err != nil {
return "", err
}
return accessToken, nil
}
func getAccessTokenViaDeviceFlow() (string, error) {
var by_web_flow string
prompt := &survey.Select{
Message: "Do you want to connect with your Github account to continue?",
Options: []string{"Y", "N"},
Default: "Y",
}
err := survey.AskOne(prompt, &by_web_flow)
if err != nil {
return "", err
}
if by_web_flow != "Y" {
return "", fmt.Errorf("user cancelled device flow")
}
ui.PrintMsg("Starting Github authentication using oauth2 device flow")
token, err := connectGithubWithDeviceFlow()
if err != nil {
return "", err
}
return token, nil
}
func connectGithubWithDeviceFlow() (string, error) {
clientID := connect.GetGithubOAuth2ClientId()
scopes := []string{"repo", "read:org"}
httpClient := http.DefaultClient
logger.Debugf("Initiating Github device flow auth using clientId: %s", clientID)
// TODO: We are coupling with Github cloud API here. Self-hosted Github enterprise won't work
code, err := device.RequestCode(httpClient, "https://github.com/login/device/code", clientID, scopes)
if err != nil {
ui.PrintError("Error while requesting code from github: %s", err.Error())
return "", err
}
ui.PrintMsg("Copy the code: %s", code.UserCode)
ui.PrintMsg("Navigate to the URL and paste the code: %s", code.VerificationURI)
// TODO: We are coupling with Github cloud API here. Self-hosted Github enterprise won't work
accessToken, err := device.Wait(context.TODO(), httpClient,
"https://github.com/login/oauth/access_token",
device.WaitOptions{
ClientID: clientID,
DeviceCode: code,
})
if err != nil {
return "", err
}
logger.Debugf("Completed device flow with Github successfully")
return accessToken.Token, nil
}

20
docs/.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

1
docs/.node-version Normal file
View File

@ -0,0 +1 @@
16.15.0

9
docs/README.md Normal file
View File

@ -0,0 +1,9 @@
# vet Documentation
## Usage
`vet` user documentation is available at [https://docs.safedep.io/](https://docs.safedep.io/)
## Development
- [Storage](./storage.md)

40
docs/agent.md Normal file
View File

@ -0,0 +1,40 @@
# Agents
`vet` natively supports AI agents with MCP based integration for tools.
To get started, set an API key for the LLM you want to use. Example:
```bash
export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-...
export GEMINI_API_KEY=AIza...
```
> **Note:** You can also set the model to use with `OPENAI_MODEL_OVERRIDE`, `ANTHROPIC_MODEL_OVERRIDE` and `GEMINI_MODEL_OVERRIDE` environment variables to override the default model used by the agent.
## Fast Mode
All agents support a `--fast` flag to use a faster LLM model instead of a slower but more powerful reasoning model. This is only for influencing the default choice of model. It can be overridden by setting model provider specific environment variables such as `OPENAI_MODEL_OVERRIDE`, `ANTHROPIC_MODEL_OVERRIDE` and `GEMINI_MODEL_OVERRIDE`.
## Available Agents
### Query Agent
The query agent helps run query and analysis over vet's sqlite3 reporting database. To use it:
* Run a `vet` scan and generate report in sqlite3 format
```bash
vet scan --insights-v2 -M package-lock.json --report-sqlite3 report.db
```
**Note:** Agents only work with `--insights-v2`
* Start the query agent
```bash
vet agent query --db report.db
```
* Thats it! Start asking questions about the scan results.

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

BIN
docs/assets/vet-demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

BIN
docs/assets/vet-docs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/assets/vet-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 KiB

35
docs/doc-generate.md Normal file
View File

@ -0,0 +1,35 @@
# Doc Generate
Docs for `cmd/doc/` command
> [!NOTE]
> This command is `HIDDEN` and not listed in the help output of `vet`. It is used to generate the documentation / manual for the `vet` command line application.
## doc
Documentation generation internal utilities
### Options
```
-h, --help help for doc
```
## doc generate
Generate docs / manual artifacts
```
doc generate [flags]
```
### Options
```
-h, --help help for generate
--man string The output directory for troff (man markup) doc files
--markdown string The output directory for markdown doc files
```
> [!IMPORTANT]
> At least one of the output directory is required

View File

@ -1,115 +0,0 @@
# Filtering
Filter command helps solve the problem of visibility for OSS dependencies in an
application. To support various requirements, we adopt a generic [expressions
language](https://github.com/google/cel-spec) for flexible filtering.
## Input
Filter expressions work on packages (aka. dependencies) and evaluates to
a boolean result. The package is included in the results table if the
expression evaluates to `true`.
Filter expressions get the following input data to work with
| Variable | Content |
|-------------|-------------------------------------------------------------|
| `_` | The root variable, holding other variables |
| `vulns` | Holds a map of vulnerabiliteis by severity |
| `scorecard` | Holds OpenSSF scorecard |
| `projects` | Holds a list of source projects associated with the package |
| `licenses` | Holds a list of liceses in SPDX license code format |
## Expressions
Expressions are [CEL](https://github.com/google/cel-spec) statements. While
CEL internals are not required, an [introductory](https://github.com/google/cel-spec/blob/master/doc/intro.md)
knowledge of CEL will help formulating queries.
### Example Queries
| Description | Query |
|----------------------------------------------|---------------------------------------|
| Find packages with a critical vulnerability | `vulns.critical.exists(x, true)` |
| Find unmaintained packages as per OpenSSF SC | `scorecard.score["Maintenance"] == 0` |
| Find packages with low stars | `projects.exists(x, x.stars < 10)` |
| Find packages with GPL-2.0 license | `licenses.exists(x, x == "GPL-2.0")`
Refer to [scorecard checks](https://github.com/ossf/scorecard#checks-1) for
a list of checks available from OpenSSF Scorecards project.
## Query Workflow
Scanning a package manifest is a resource intensive process as it involves
enriching package metadata by queryin [Insights API](https://safedep.io/docs/concepts/raya-data-platform-overview).
However, for filtering and reporting may be done multiple times on the same
manifest. To speed up the process, we can dump the enriched data as JSON and
load the same for filtering and reporting.
Dump enriched JSON manifests to a directory (example)
```bash
vet scan --lockfile /path/to/package-lock.json --json-dump-dir /tmp/dump
vet scan -D /path/to/repository --json-dump-dir /tmp/dump-many
```
Load the enriched metadata for filtering and reporting
```bash
vet query --from /tmp/dump --report-console
vet query --from /tmp/dump --filter 'scorecard.score["Maintenance"] == 0'
```
## FAQ
### How does the filter input JSON look like?
```json
{
"pkg": {
"ecosystem": "npm",
"name": "lodash.camelcase",
"version": "4.3.0"
},
"vulns": {
"all": [],
"critical": [],
"high": [],
"medium": [],
"low": []
},
"scorecard": {
"scores": {
"Binary-Artifacts": 10,
"Branch-Protection": 0,
"CII-Best-Practices": 0,
"Code-Review": 8,
"Dangerous-Workflow": 10,
"Dependency-Update-Tool": 0,
"Fuzzing": 0,
"License": 10,
"Maintained": 0,
"Packaging": -1,
"Pinned-Dependencies": 9,
"SAST": 0,
"Security-Policy": 10,
"Signed-Releases": -1,
"Token-Permissions": 0,
"Vulnerabilities": 10
}
},
"projects": [
{
"name": "lodash/lodash",
"type": "GITHUB",
"stars": 55518,
"forks": 6787,
"issues": 464
}
],
"licenses": [
"MIT"
]
}
```

37
docs/manual/_config.yml Normal file
View File

@ -0,0 +1,37 @@
title: SafeDep/vet Manual
description: CLI reference manual for SafeDep/vet. Next-gen software composition analysis and malicious package protection tool.
remote_theme: just-the-docs/just-the-docs@v0.8.0
markdown: kramdown
# Custom Color Schema, defined in _sass/color_schemes/safedep.scss
color_scheme: safedep
# GitHub repo link (appears in top right)
repo_url: https://github.com/safedep/vet
# Aux links (top navigation bar)
aux_links:
"GitHub": https://github.com/safedep/vet
aux_links_new_tab: true
# Heading anchor links (h1, h2, h3...)
heading_anchors: true
# Search functionality
search_enabled: true
search.heading_level: 2
# Logo (if you have one)
logo: "/assets/logo.png"
favicon_ico: "/assets/favicon.png"
# Footer
footer_content: "Copyright &copy; 2025 SafeDep Inc."
# Back to top button
back_to_top: true
back_to_top_text: "Back to top"
plugins:
- jekyll-seo-tag
- jekyll-github-metadata
- jekyll-include-cache
- jekyll-sitemap

View File

@ -0,0 +1,19 @@
// Change Default Theme (purple's variable) to branding color
// SafeDep Brand Color is #0d9488
$safedep-brand: #0d9488;
// This will override the the default color schema
$purple-000: #0c8d75; // A little bit dark in that color for links (much better for links)
$purple-100: #0a7562; // A little more dark on that link color for hover effect
$link-color: $purple-000; // Set Link Color
// Override variables for the .btn-primary button
$btn-primary-color: $safedep-brand;
$btn-primary-bg: $safedep-brand; // Background color of the button
$btn-primary-border: $safedep-brand; // Border color of the button
$btn-hover-color: #10bcad;
$btn-primary-hover-bg: $btn-hover-color; // Background color on hover
$btn-primary-hover-border: $btn-hover-color; // Border color on hover

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
docs/manual/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

28
docs/manual/index.md Normal file
View File

@ -0,0 +1,28 @@
---
title: SafeDep/vet Manual
layout: home
nav_order: 1
---
# SafeDep `vet` CLI manual
[`vet`](https://github.com/safedep/vet) is a free and open source software supply chain security tool. It helps developers and security engineers protect against malicious open source packages and establish policy driven guardrails.
<br />
> _This CLI reference provides detailed documentation for all vet commands, flags, and options._
[Go to CLI Manual](vet.html){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 }
## `vet` Installation
```bash
brew install safedep/tap/vet
```
See [other installation options](https://github.com/safedep/vet?tab=readme-ov-file#-installation-options)
## Other Resources
Website: <https://safedep.io>
Docs: [https://docs.safedep.io](https://docs.safedep.io/introduction)

266
docs/mcp.md Normal file
View File

@ -0,0 +1,266 @@
# vet MCP Server
[![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=vet-mcp&config=eyJjb21tYW5kIjoiZG9ja2VyIHJ1biAtLXJtIC1pIGdoY3IuaW8vc2FmZWRlcC92ZXQ6bGF0ZXN0IC1zIC1sIC90bXAvdmV0LW1jcC5sb2cgc2VydmVyIG1jcCJ9)
The `vet` MCP server is designed to run locally using `stdio` or `sse` transports.
It provides tools for MCP clients such as Claude Code, Cursor and others to vet
open source packages before they are used in a project through AI generated code.
`vet` MCP server can protect against [Slopsquatting](https://en.wikipedia.org/wiki/Slopsquatting) attacks, malicious packages,
vulnerabilities and other security risks.
## Supported Ecosystems
`vet` MCP server currently supports the following ecosystems:
- npm
- PyPI
## Usage
Start the MCP server using SSE transport:
```bash
vet server mcp --server-type sse
```
Start the MCP server using stdio transport:
```bash
vet -s -l /tmp/vet-mcp.log server mcp --server-type stdio
```
> Avoid using `stdout` logging as it will interfere with the MCP server output.
### SSE Transport Features
The SSE (Server-Sent Events) transport supports:
- **GET requests**: For establishing SSE connections to receive real-time events
- **HEAD requests**: For endpoint health checks and capability probing (useful for tools like Langchain)
- **POST requests**: For sending messages to the MCP server via the message endpoint
The SSE endpoint returns appropriate headers for HEAD requests without a body, allowing tools to verify endpoint availability and capabilities.
### Security: Host and Origin Guards
For SSE, the server enforces simple, user-configurable guards to reduce the risk
of unauthorized cross-origin access and DNS rebinding attacks.
- **Host guard**: Only allows connections whose `Host` header matches an allowed
host list.
- **Origin guard**: For browser requests, only allows requests whose `Origin`
starts with an allowed prefix.
These checks are on by default with sensible localhost defaults, and you can
customize them with flags when starting the server.
#### Defaults
- **Allowed hosts**: `localhost:9988`, `127.0.0.1:9988`, `[::1]:9988`
- **Allowed origin prefixes**: `http://localhost:`, `http://127.0.0.1:`, `https://localhost:`
Requests that fail the host check are rejected with status `403`, and requests
that fail the origin check are rejected with status `403`.
#### Customize allowed hosts and origins
You can override the defaults using the following flags:
```bash
vet server mcp \
--server-type sse \
--sse-allowed-hosts "localhost:8080,127.0.0.1:8080" \
--sse-allowed-origins "http://localhost:,https://localhost:"
```
If you are running behind a proxy or using a different port, set both lists to
match your environment. For example, when exposing SSE on port 3001:
```bash
vet server mcp \
--server-type sse \
--sse-allowed-hosts "localhost:3001,127.0.0.1:3001" \
--sse-allowed-origins "http://localhost:,http://127.0.0.1:,https://localhost:"
```
With Docker, append the same flags to the container command:
```bash
docker run --rm -i ghcr.io/safedep/vet:latest \
server mcp \
--server-type sse \
--sse-allowed-hosts "localhost:9988,127.0.0.1:9988" \
--sse-allowed-origins "http://localhost:,http://127.0.0.1:,https://localhost:"
```
Tip: Non-browser clients may omit the `Origin` header. Those requests are
allowed as long as the host guard passes.
## Configure MCP Client
> **Note:** The example below uses pre-build docker image. You can build your own by running
> `docker build -t ghcr.io/safedep/vet:latest .`
Add following configuration to your MCP client configuration file:
```json
"vet-mcp": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"ghcr.io/safedep/vet:latest",
"-l",
"/tmp/vet-mcp.log",
"server",
"mcp"
]
}
```
`vet` uses `stdio` by default, if you wish to use SSE transport, add following args: `"--server-type", "sse"`
**Note:** While `docker` containers can help keep the MCP server process isolated, it needs to be periodically updated to the latest version.
Alternatively, you can use `vet` binary directly as an MCP server using `stdio` transport. Use the following command to start the MCP server:
```bash
vet -l /tmp/vet-mcp.log server mcp --server-type stdio
```
### Configure IDE
#### Cursor
Add `vet-mcp` server to either `.cursor/mcp.json` (project specific configuration) or `~/.cursor/mcp.json` (applies to all projects)
```json
{
"mcpServers": {
"vet-mcp": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"ghcr.io/safedep/vet:latest",
"-l",
"/tmp/vet-mcp.log",
"server",
"mcp"
]
}
}
}
```
Add the following to `.cursor/rules/vet-mcp.mdc` file:
```
---
description: vet Open Source Packages using vet MCP server
alwaysApply: true
---
Always use vet-mcp to vet any open source library package suggested by AI generated code before installation.
Follow the rules below to use vet-mcp:
- Try to find the latest version. If not found, it is likely a slopsquatting vulnerability
- Always check for critical vulnerabilities and malware
```
Example usage with Cursor:
![Cursor vet-mcp example](./assets/vet-mcp-cursor.png)
[📄 Cursor MCP server reference](https://docs.cursor.com/context/model-context-protocol#manual-configuration)
#### Visual Studio Code
Add `vet-mcp` server to `.vscode/mcp.json` (project specific configuration)
```json
{
"servers": {
"vet-mcp": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"ghcr.io/safedep/vet:latest",
"-l",
"/tmp/vet-mcp.log",
"server",
"mcp"
]
}
}
}
```
In order to use `vet-mcp` for all projects in Visual Studio Code, add following `mcp` setting in [Visual Studio Code User Settings](https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_add-an-mcp-server-to-your-user-settings) (`settings.json`)
```json
{
"mcp": {
"servers": {
"vet-mcp": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"ghcr.io/safedep/vet:latest",
"-l",
"/tmp/vet-mcp.log",
"server",
"mcp"
]
}
}
}
}
```
Add the following to `.github/copilot-instructions.md` file:
```
Always use vet-mcp to vet any open source library package suggested by AI generated code before installation.
Follow the rules below to use vet-mcp:
- Try to find the latest version. If not found, it is likely a slopsquatting vulnerability
- Always check for critical vulnerabilities and malware
```
Example usage with Visual Studio Code:
![Visual Studio Code vet-mcp example](./assets/vet-mcp-vscode.png)
[📄 Visual Studio Code MCP server reference](https://code.visualstudio.com/docs/copilot/chat/mcp-servers)
#### Claude Code
Add the following to `.mcp.json` in your Claude Code project:
```json
{
"mcpServers": {
"vet-mcp": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"ghcr.io/safedep/vet:latest",
"server",
"mcp"
]
}
}
}
```
**Note:** You can also use `vet` binary directly as an MCP server using `stdio` transport.

21
docs/osv-report.md Normal file
View File

@ -0,0 +1,21 @@
## OSV (OSSF) Report
Using `--report-osv` we can generate report for `OSSF` malicious package database.
Usage:
```bash
vet inspect malware --purl ... --report-osv .
```
The value of `--report-osv` is the root of [ossf/malicious-packages](https://github.com/ossf/malicious-packages/) repository,
it automatically places the JSON report in correct location, like `osv/malicious/npm/...`.
Flags:
| Flag | Usage | Default Value |
| -------------------------- | ----------------------------------- | --------------------------------------------- |
| `report-osv-finder-name` | Name of finder | `SafeDep` |
| `report-osv-contacts` | Contact Info, email, website etc | `https://safedep.io` |
| `report-osv-reference-url` | Report Reference URL, like blog etc | `https://app.safedep.io/community/malysis/ID` |
| `report-osv-with-ranges` | Use `ranges` affected property | discrete `versions` |

47
docs/policy-dev.md Normal file
View File

@ -0,0 +1,47 @@
# Policy Engine Development
The policy engine is implemented using [Common Expressions Languages](https://cel.dev).
This development document is ONLY for Policy v2, internally represented
as Filter V2 for naming consistency.
## Enum Constants
Protobuf enums are exposed as integer values in CEL. To improve policy readability, we generate enum constant maps that allow using symbolic names instead of integers.
**Example usage in policies:**
```cel
// Instead of: p.project.type == 1
p.project.type == ProjectSourceType.GITHUB
// Instead of: pkg.ecosystem == 2
pkg.ecosystem == Ecosystem.NPM
```
**How it works:**
- `pkg/analyzer/filterv2/enums.go` registers enums via `RegisteredEnums` by referencing protobuf-generated `Type_value` maps
- `pkg/analyzer/filterv2/enumgen/` generates `enums_generated.go` with constant maps
- Run `go generate ./pkg/analyzer/filterv2/` to regenerate after adding new enums
**Adding new enums:**
1. Add entry to `RegisteredEnums` in `pkg/analyzer/filterv2/enums.go`:
```go
{
Name: "SeverityRisk",
Prefix: "RISK_",
ValueMap: vulnerabilityv1.Severity_Risk_value,
}
```
2. Declare the enum variable in `pkg/analyzer/filterv2/eval.go` `NewEvaluator()`:
```go
cel.Variable("SeverityRisk", cel.MapType(cel.StringType, cel.IntType))
```
3. Run `go generate ./pkg/analyzer/filterv2/`
The generator automatically strips prefixes (e.g., `RISK_CRITICAL``CRITICAL`) and keeps enums synchronized with protobuf definitions.

File diff suppressed because it is too large Load Diff

29
docs/storage.md Normal file
View File

@ -0,0 +1,29 @@
# Storage
`vet` contains a storage engine defined in `pkg/storage`. We use `sqlite3` as
the database and [ent](https://entgo.io/) as the ORM.
## Usage
- Create new schema using the following command
```shell
go run -mod=mod entgo.io/ent/cmd/ent new CodeSourceFile
```
- Schemas are generated in `./ent/schema` directory
- Edit the generated schema file and add the necessary fields and edges
- Generate the models from the schema using the following command
```shell
make ent
```
- Make sure to commit any changes to `ent` directory including the generated
files
## Guidance
All schemas are stored in `./ent/schema` directory. To avoid naming conflicts,
prefer prefixing the schema name with the logical module name. Example: `CodeSourceFile` is
used as the schema for storing `SourceFile` within `Code` analysis module.

2373
ent/client.go Normal file

File diff suppressed because it is too large Load Diff

129
ent/codesourcefile.go Normal file
View File

@ -0,0 +1,129 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/safedep/vet/ent/codesourcefile"
)
// CodeSourceFile is the model entity for the CodeSourceFile schema.
type CodeSourceFile struct {
config `json:"-"`
// ID of the ent.
ID int `json:"id,omitempty"`
// Path holds the value of the "path" field.
Path string `json:"path,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the CodeSourceFileQuery when eager-loading is set.
Edges CodeSourceFileEdges `json:"edges"`
selectValues sql.SelectValues
}
// CodeSourceFileEdges holds the relations/edges for other nodes in the graph.
type CodeSourceFileEdges struct {
// DepsUsageEvidences holds the value of the deps_usage_evidences edge.
DepsUsageEvidences []*DepsUsageEvidence `json:"deps_usage_evidences,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// DepsUsageEvidencesOrErr returns the DepsUsageEvidences value or an error if the edge
// was not loaded in eager-loading.
func (e CodeSourceFileEdges) DepsUsageEvidencesOrErr() ([]*DepsUsageEvidence, error) {
if e.loadedTypes[0] {
return e.DepsUsageEvidences, nil
}
return nil, &NotLoadedError{edge: "deps_usage_evidences"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*CodeSourceFile) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case codesourcefile.FieldID:
values[i] = new(sql.NullInt64)
case codesourcefile.FieldPath:
values[i] = new(sql.NullString)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the CodeSourceFile fields.
func (csf *CodeSourceFile) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case codesourcefile.FieldID:
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
csf.ID = int(value.Int64)
case codesourcefile.FieldPath:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field path", values[i])
} else if value.Valid {
csf.Path = value.String
}
default:
csf.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the CodeSourceFile.
// This includes values selected through modifiers, order, etc.
func (csf *CodeSourceFile) Value(name string) (ent.Value, error) {
return csf.selectValues.Get(name)
}
// QueryDepsUsageEvidences queries the "deps_usage_evidences" edge of the CodeSourceFile entity.
func (csf *CodeSourceFile) QueryDepsUsageEvidences() *DepsUsageEvidenceQuery {
return NewCodeSourceFileClient(csf.config).QueryDepsUsageEvidences(csf)
}
// Update returns a builder for updating this CodeSourceFile.
// Note that you need to call CodeSourceFile.Unwrap() before calling this method if this CodeSourceFile
// was returned from a transaction, and the transaction was committed or rolled back.
func (csf *CodeSourceFile) Update() *CodeSourceFileUpdateOne {
return NewCodeSourceFileClient(csf.config).UpdateOne(csf)
}
// Unwrap unwraps the CodeSourceFile entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (csf *CodeSourceFile) Unwrap() *CodeSourceFile {
_tx, ok := csf.config.driver.(*txDriver)
if !ok {
panic("ent: CodeSourceFile is not a transactional entity")
}
csf.config.driver = _tx.drv
return csf
}
// String implements the fmt.Stringer.
func (csf *CodeSourceFile) String() string {
var builder strings.Builder
builder.WriteString("CodeSourceFile(")
builder.WriteString(fmt.Sprintf("id=%v, ", csf.ID))
builder.WriteString("path=")
builder.WriteString(csf.Path)
builder.WriteByte(')')
return builder.String()
}
// CodeSourceFiles is a parsable slice of CodeSourceFile.
type CodeSourceFiles []*CodeSourceFile

View File

@ -0,0 +1,83 @@
// Code generated by ent, DO NOT EDIT.
package codesourcefile
import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the codesourcefile type in the database.
Label = "code_source_file"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldPath holds the string denoting the path field in the database.
FieldPath = "path"
// EdgeDepsUsageEvidences holds the string denoting the deps_usage_evidences edge name in mutations.
EdgeDepsUsageEvidences = "deps_usage_evidences"
// Table holds the table name of the codesourcefile in the database.
Table = "code_source_files"
// DepsUsageEvidencesTable is the table that holds the deps_usage_evidences relation/edge.
DepsUsageEvidencesTable = "deps_usage_evidences"
// DepsUsageEvidencesInverseTable is the table name for the DepsUsageEvidence entity.
// It exists in this package in order to avoid circular dependency with the "depsusageevidence" package.
DepsUsageEvidencesInverseTable = "deps_usage_evidences"
// DepsUsageEvidencesColumn is the table column denoting the deps_usage_evidences relation/edge.
DepsUsageEvidencesColumn = "deps_usage_evidence_used_in"
)
// Columns holds all SQL columns for codesourcefile fields.
var Columns = []string{
FieldID,
FieldPath,
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
return false
}
var (
// PathValidator is a validator for the "path" field. It is called by the builders before save.
PathValidator func(string) error
)
// OrderOption defines the ordering options for the CodeSourceFile queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByPath orders the results by the path field.
func ByPath(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPath, opts...).ToFunc()
}
// ByDepsUsageEvidencesCount orders the results by deps_usage_evidences count.
func ByDepsUsageEvidencesCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newDepsUsageEvidencesStep(), opts...)
}
}
// ByDepsUsageEvidences orders the results by deps_usage_evidences terms.
func ByDepsUsageEvidences(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newDepsUsageEvidencesStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newDepsUsageEvidencesStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(DepsUsageEvidencesInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, true, DepsUsageEvidencesTable, DepsUsageEvidencesColumn),
)
}

Some files were not shown because too many files have changed in this diff Show More