├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── README.md └── workflows │ ├── build_and_release.yml │ ├── publish_docker_images.yml │ ├── run_opensca_scan.yml │ └── update_package_managers.yml ├── .goreleaser.yml ├── CONTRIBUTING-zh_CN.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── cmd ├── config │ └── config.go ├── detail │ ├── client.go │ ├── cmp.go │ ├── detail.go │ ├── login.go │ └── origin.go ├── format │ ├── csv.go │ ├── cyclonedx.go │ ├── dpsbom.go │ ├── dsdx.go │ ├── html.go │ ├── html_tpl │ ├── json.go │ ├── saas.go │ ├── sarif.go │ ├── save.go │ ├── spdx.go │ ├── sqlite.go │ ├── sqlite_init.sql │ ├── statis.go │ ├── swid.go │ └── xml.go └── ui │ └── ui.go ├── config.json ├── db-demo.json ├── docker └── Dockerfile ├── docs ├── About_OpenSCA-zh_CN.md ├── About_OpenSCA.md ├── CODE_OF_CONDUCT.md ├── Code_Standard-zh_CN.md ├── Code_standard.md ├── Contributing_Guideline-v1.0-zh_CN.md ├── Contributing_Guideline-v1.0.md ├── Integrations │ ├── CICD-zh_CN.md │ ├── CICD.md │ ├── IDE_Plugins-zh_CN.md │ └── IDE_Plugins.md ├── Quick_Start-zh_CN.md ├── Quick_Start.md ├── README-zh-CN.md ├── README.md ├── Troubleshooting-zh_CN.md ├── Troubleshooting.md └── User_Guide │ ├── Configuration-and-Parameters-zh_CN.md │ ├── Configuration-and-Parameters.md │ ├── Docker-zh_CN.md │ ├── Docker.md │ ├── Generating_Reports │ ├── Reports-zh_CN.md │ ├── Reports.md │ ├── SBOM-zh_CN.md │ └── SBOM.md │ ├── Installation-zh_CN.md │ ├── Installation.md │ ├── Scanning │ ├── Dependency_Analysis-zh_CN.md │ ├── Dependency_Analysis.md │ ├── Vulnerability_Analysis-zh_CN.md │ └── Vulnerability_Analysis.md │ ├── Viewing_Results-zh_CN.md │ └── Viewing_Results.md ├── example ├── dep │ └── main.go ├── file │ ├── main.go │ └── test_file.txt ├── go.mod ├── go.sum ├── java │ └── main.go ├── javascript │ └── main.go ├── logs │ └── main.go ├── main.go └── php │ └── main.go ├── go.mod ├── go.sum ├── main.go ├── makefile ├── opensca ├── common │ ├── httpclient.go │ ├── repo.go │ └── temp.go ├── logs │ └── log.go ├── model │ ├── dep.go │ ├── dpsbom.go │ ├── dsdx.go │ ├── file.go │ ├── language.go │ └── spdx.go ├── run.go ├── sca │ ├── cache │ │ └── cache.go │ ├── erlang │ │ └── sca.go │ ├── filter │ │ └── filter.go │ ├── golang │ │ ├── gomod.go │ │ ├── gopkg.go │ │ └── sca.go │ ├── groovy │ │ ├── gradle.go │ │ ├── groovy.go │ │ ├── opensca.gradle │ │ ├── sca.go │ │ └── variable.go │ ├── java │ │ ├── mvn.go │ │ ├── pom.go │ │ ├── sca.go │ │ └── xml │ │ │ ├── marshal.go │ │ │ ├── read.go │ │ │ ├── typeinfo.go │ │ │ └── xml.go │ ├── javascript │ │ ├── npm.go │ │ ├── sca.go │ │ └── yarn.go │ ├── php │ │ ├── composer.go │ │ └── sca.go │ ├── python │ │ ├── env.go │ │ ├── oss.py │ │ ├── pip.go │ │ ├── sca.go │ │ └── setup.go │ ├── ruby │ │ ├── gem.go │ │ └── sca.go │ ├── rust │ │ ├── cargo.go │ │ └── sca.go │ ├── sbom │ │ ├── cdx.go │ │ ├── dpsbom.go │ │ ├── dsdx.go │ │ ├── sca.go │ │ └── spdx.go │ └── sca.go └── walk │ ├── download.go │ ├── magic.go │ ├── rar.go │ ├── tar.go │ ├── walk.go │ └── zip.go ├── resources ├── DetectionProcess-zh_CN.png ├── DetectionProcess.png ├── jenkins-freestyle.png ├── jenkins-postbuild.png ├── jenkins-view-html-report.gif ├── logo.svg ├── wechat.png ├── xcheck_function.jpg ├── xcheck_marketplace.jpg └── xcheck_process.jpg ├── scripts ├── gitlab_scan.py ├── install.ps1 ├── install.sh └── json2excel.py └── test ├── java ├── 1 │ ├── mod │ │ └── pom.xml │ └── pom.xml ├── 2 │ ├── mod │ │ └── pom.xml │ └── pom.xml ├── 3 │ ├── mod │ │ └── pom.xml │ └── pom.xml ├── 4 │ ├── mod │ │ └── pom.xml │ └── pom.xml ├── 5 │ └── pom.xml ├── 6 │ └── pom.xml ├── 7 │ └── pom.xml ├── 8 │ ├── mod │ │ └── pom.xml │ └── pom.xml ├── 9 │ └── pom.xml ├── 10 │ ├── mod │ │ ├── mod2 │ │ │ └── pom.xml │ │ └── pom.xml │ └── pom.xml ├── 11 │ ├── b │ │ ├── c │ │ │ ├── d │ │ │ │ └── pom.xml │ │ │ └── pom.xml │ │ └── pom.xml │ └── pom.xml ├── 12 │ └── pom.xml ├── 13 │ └── pom.xml ├── 14 │ └── pom.xml ├── 15 │ └── pom.xml ├── 16 │ └── pom.xml ├── 17 │ └── pom.xml └── java_test.go ├── javascript ├── 1 │ ├── package-lock.json │ └── package.json ├── 2 │ ├── package-lock.json │ └── package.json ├── 3 │ ├── package.json │ └── yarn.lock ├── 4 │ ├── node_modules │ │ ├── ansi-regex │ │ │ └── package.json │ │ ├── ansi-styles │ │ │ └── package.json │ │ ├── cliui │ │ │ └── package.json │ │ ├── color-convert │ │ │ └── package.json │ │ ├── color-name │ │ │ └── package.json │ │ ├── emoji-regex │ │ │ └── package.json │ │ ├── is-fullwidth-code-point │ │ │ └── package.json │ │ ├── string-width │ │ │ └── package.json │ │ ├── strip-ansi │ │ │ └── package.json │ │ └── wrap-ansi │ │ │ └── package.json │ └── package.json ├── 5 │ └── package.json └── javascript_test.go ├── php ├── 1 │ ├── composer.json │ └── composer.lock ├── 2 │ └── composer.json └── php_test.go ├── python ├── 1 │ └── requirements.txt ├── 2 │ └── Pipfile ├── 3 │ └── Pipfile.lock └── python_test.go ├── ruby ├── 1 │ └── Gemfile.lock └── ruby_test.go ├── rust ├── 1 │ └── Cargo.lock └── rust_test.go └── tool └── tool.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[Bug] --a clear and concise title--" 5 | labels: bug 6 | assignees: luotianqi777, SuperChen-CC 7 | 8 | --- 9 | 10 | ## Title 11 | 12 | Aim for a clear and concise title that summarizes the specific issue. For instance: "OpenSCA-cli fails to detect dependencies in XYZ scenario" 13 | 14 | ## Description 15 | 16 | Provide a detailed description of the issue. Explain what you were doing when the bug occurred, the functions of OpenSCA-cli you were using, and the implications of the bug. 17 | 18 | ## Steps to Reproduce 19 | 20 | List the steps needed to reproduce the bug: 21 | 22 | 1. ... 23 | 1. ... 24 | 1. ... 25 | 1. ... 26 | 27 | Please include code snippets, error messages, or screenshots if they can help illustrate the issue. 28 | 29 | ## Expected Behavior 30 | 31 | Describe what you expected OpenSCA-cli to do when you executed the steps above. This could be about the detection of open source component dependencies, vulnerability identification, or any other features. 32 | 33 | ## Actual Behavior 34 | 35 | Describe what OpenSCA-cli actually did when you executed the steps above. 36 | 37 | ## Environment 38 | 39 | - OS: [e.g., iOS, Windows, Linux] 40 | - Version: [e.g., Windows 10, Ubuntu 20.04] 41 | - OpenSCA-cli version: [e.g., v1.0.0] 42 | 43 | ## Additional Context 44 | 45 | Provide any other information that you believe could help us understand the problem better. This could be about your project’s structure, the type of open source components you’re using, etc. 46 | 47 | ## Possible Fix (optional) 48 | 49 | If you have any ideas about what might be causing this bug or how to fix it, please share that information here. This could help speed up the process of resolving the issue. 50 | 51 | Please remember to maintain a respectful and professional tone in your report. We appreciate your contribution to improving OpenSCA! 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature request]" 5 | labels: enhancement 6 | assignees: SuperChen-CC 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/build_and_release.yml: -------------------------------------------------------------------------------- 1 | name: Build Binaries And Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v*.*.* 7 | workflow_dispatch: 8 | inputs: 9 | tag: 10 | description: 'Tag to build' 11 | required: true 12 | log_level: 13 | description: 'Log level' 14 | required: false 15 | default: 'info' 16 | 17 | jobs: 18 | createRelease: 19 | name: Create Release 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Build Changelog 23 | id: changelog 24 | uses: mikepenz/release-changelog-builder-action@v3 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | - name: Create Release 28 | uses: softprops/action-gh-release@v1 29 | with: 30 | body: ${{ steps.changelog.outputs.changelog }} 31 | 32 | releases-matrix: 33 | name: Release Binary 34 | runs-on: ubuntu-latest 35 | needs: [createRelease] 36 | strategy: 37 | matrix: 38 | goos: [linux, windows, darwin] 39 | goarch: [amd64, arm64, 386] 40 | # goarch: [amd64, arm64, 386, loong64] 41 | exclude: 42 | - goarch: arm64 43 | goos: windows 44 | - goarch: 386 45 | goos: darwin 46 | - goarch: 386 47 | goos: windows 48 | # - goarch: loong64 49 | # goos: windows 50 | # - goarch: loong64 51 | # goos: darwin 52 | steps: 53 | - name: Checkout 54 | uses: actions/checkout@v4 55 | - name: Set env 56 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 57 | - name: Create VERSION file 58 | run: echo ${{ env.RELEASE_VERSION }} > VERSION 59 | - name: Build And Release 60 | uses: wangyoucao577/go-release-action@v1 61 | env: 62 | CGO_ENABLED: 0 63 | with: 64 | github_token: ${{ secrets.GITHUB_TOKEN }} 65 | goos: ${{ matrix.goos }} 66 | goarch: ${{ matrix.goarch }} 67 | project_path: "./" 68 | binary_name: opensca-cli 69 | sha256sum: true 70 | md5sum: false 71 | extra_files: README.md config.json db-demo.json VERSION 72 | ldflags: "-s -w -X 'main.version=${{ env.RELEASE_VERSION }}' " 73 | -------------------------------------------------------------------------------- /.github/workflows/run_opensca_scan.yml: -------------------------------------------------------------------------------- 1 | name: OpenSCA Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | 12 | 13 | jobs: 14 | opensca_scan: 15 | name: OpenSCA Scan 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | with: 21 | sparse-checkout: | 22 | cmd/ 23 | opensca/ 24 | go.mod 25 | main.go 26 | - name: Run OpenSCA Scan 27 | uses: XmirrorSecurity/opensca-scan-action@v1 28 | with: 29 | token: ${{ secrets.OPENSCA_TOKEN }} 30 | proj: ${{ secrets.OPENSCA_PROJECT_ID }} 31 | need-artifact: true 32 | out: "outputs/results.json,outputs/result.html" 33 | -------------------------------------------------------------------------------- /.github/workflows/update_package_managers.yml: -------------------------------------------------------------------------------- 1 | name: Update Package Managers 2 | 3 | on: 4 | push: 5 | tags: 6 | - v*.*.* 7 | workflow_dispatch: 8 | inputs: 9 | tag-name: 10 | description: 'The version to upgrade' 11 | required: true 12 | 13 | jobs: 14 | homebrew: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: mislav/bump-homebrew-formula-action@v3 18 | with: 19 | formula-name: opensca-cli 20 | env: 21 | COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }} 22 | # winget: 23 | # runs-on: windows-latest 24 | # steps: 25 | # - uses: vedantmgoyal2009/winget-releaser@v2 26 | # with: 27 | # identifier: XmirrorSecurity.OpenSCA-cli 28 | # token: ${{ secrets.COMMITTER_TOKEN }} 29 | 30 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: opensca-cli 2 | builds: 3 | - env: 4 | - CGO_ENABLED=0 5 | goos: 6 | - linux 7 | - windows 8 | - darwin 9 | goarch: 10 | - 386 11 | - amd64 12 | - arm 13 | - arm64 14 | goarm: 15 | - 6 16 | - 7 17 | ignore: 18 | - goos: windows 19 | goarch: arm 20 | - goos: windows 21 | goarch: 386 22 | id: opensca-cli 23 | dir: . 24 | binary: opensca-cli 25 | main: ./ 26 | archives: 27 | - name_template: >- 28 | {{.ProjectName}}_{{.Tag}}_{{- title .Os}}_ 29 | {{- if eq .Arch "amd64" }}x86_64 30 | {{- else if eq .Arch "386" }}i386 31 | {{- else}}{{.Arch}}{{.Arm}}{{end}} 32 | files: 33 | - LICENSE 34 | - config.json 35 | - README.md 36 | format: zip 37 | checksum: 38 | name_template: checksums.txt 39 | snapshot: 40 | name_template: "{{.Tag}}" 41 | -------------------------------------------------------------------------------- /CONTRIBUTING-zh_CN.md: -------------------------------------------------------------------------------- 1 | [中文](./CONTRIBUTING-zh_CN.md) | [English](./CONTRIBUTING.md) 2 | 3 | # 贡献指南(中文版) 4 | 5 | ## OpenSCA项目贡献指南 v1.0 6 | 7 | OpenSCA项目是由悬镜安全团队首发的开源的软件成分分析工具。身处紧锣密鼓的研发节奏及开源代码和软件广泛应用的大环境中,面对日益严峻的软件供应链安全问题,我们的愿景是用开源的方式做开源风险治理。欢迎朋友们成为OpenSCA社区的一份子,与我们共同建设OpenSCA项目的未来,探索充满无限可能的开源解决方案。 8 | 9 | 我们珍视一切形式的贡献,包括但不限于: 10 | 11 | - 对已有代码的检查 12 | - 说明文档及部署案例 13 | - 通过社群渠道及Issue板块参与社区讨论 14 | - 提升OpenSCA项目功能的切实努力 15 | - 有益项目长远发展的经验分享、使用指导、专题交流等 16 | 17 | 为了帮助大家更有效地参与到各个方面的贡献中来,我们拟定了一份贡献指南,并会参照项目的发展情况定期更新完善。这份指南包括以下几个部分: 18 | 19 | - 社区行为守则及贡献体系 20 | - 如何提出BUG 21 | - BUG类型划分 22 | - 贡献代码 23 | - 代码规范 24 | 25 | ### 社区行为守则及贡献体系 26 | 27 | OpenSCA社区旨在营造贡献者友好的氛围,开放、包容、尊重他人的环境需要大家共同努力去创造和维持。不恰当的行为将会受到警告甚至惩罚。 28 | 29 | #### **行为守则** 30 | 31 | OpenSCA社区关心每位社区成员的体验;为了共同营造良好的社区氛围,请您提前阅读我们的[行为守则](https://www.contributor-covenant.org/zh-cn/version/2/0/code_of_conduct/),并在参与时多多留意。 32 | 33 | #### 社区贡献体系 34 | 35 | OpenSCA社区尚处于起步阶段,由悬镜安全OpenSCA项目团队管理。随着社区的发展,我们欢迎并期待更多的朋友们为社区做出贡献,成为项目团队的一员,和我们一起维护社区系统的运转。 36 | 37 | 暂行的贡献体系初步分代码和积分两个部分;**代码方面**,通过PR向我们提交代码并被采纳后即可成为我们的Contributor,将可作为OpenSCA项目的成员参与到进一步的项目功能规划和落实、BUG判定、PR审核和Contributor认证中来。 38 | 39 | **积分方面**,除贡献代码之外,积极参与社区的BUG报告、技术讨论、使用分享也可获得积分,积攒的积分可以定期兑换奖品。积分评定和奖品兑换的详细规则可能会随不同的活动时段发生变化。目前的详细规则请参考“播种计划”第一期的设置。 40 | 41 | ### 如何提出BUG 42 | 43 | 如果您在部署或使用OpenSCA时发现了BUG,可以在Issue板块提出;如果您对我们已经上传的公开文档中的内容有疑惑,可以直接发送邮件到 [Opensca@anpro-tech.com](mailto:Opensca@anpro-tech.com),也可通过OpenSCA社区微信公众号、微信群、QQ群等渠道联系我们。 44 | 45 | 在提出BUG之前,您需要先确认您部署的是OpenSCA的最新版本,然后浏览Issue板块,确认该BUG没有被其他人提出过。以上步骤确认无误后,您可以在Issue板块提出您发现的BUG,等待项目成员或其他社区成员参与讨论或进行处理;也可以提一个PR对它进行修复,然后等待项目成员的审核。 46 | 47 | *漏洞问题不适合公开提出或进行讨论,如果您发现了OpenSCA的安全漏洞,请发送相关信息到[Opensca@anpro-tech.com](mailto:Opensca@anpro-tech.com)与我们取得联系。 48 | 49 | ### BUG类型划分 50 | 51 | 不同的BUG对用户使用OpenSCA的影响是不同的。根据这种差别,我们暂时把BUG分为以下三类: 52 | 53 | **主要BUG**:影响主要功能(Java、PHP等语言的组件解析、漏洞识别) 54 | 55 | **一般BUG**:不影响功能,只影响使用体验 56 | 57 | **伪BUG**:由于操作或使用非最新版本导致的使用问题 58 | 59 | 主要BUG需要优先修复,项目成员会积极跟进;如果您有修复方案,也欢迎您提出PR,等待项目成员审核确认。项目成员会通过对某些部分的代码进行微调来处理一般BUG,对于伪BUG,我们也会及时进行回复,帮助您找出您操作中的疏漏,以便您更好地使用OpenSCA。 60 | 61 | ### 贡献代码 62 | 63 | OpenSCA项目需要通过您的代码贡献实现维护、完善和功能拓展;贡献代码的主要形式是提交PR。 64 | 65 | 您可以为BUG修复和功能提升提出PR,项目成员会对您的PR进行审核。为了提高审核的效率,我们希望您参照项目的代码规范来组织您的PR,每个PR专注一个问题,并尽量提升您代码的可读性,如果能附上一些说明更佳。 66 | 67 | **如果是关于BUG修复的PR**,在满足规范的基础上,审核标准将主要关注代码质量; 68 | 69 | **如果是关于功能提升的PR**,在满足规范的基础上,审核标准将围绕代码质量、功能创新性、功能实现度和算法潜力这几个方面。 70 | 71 | 项目成员审核完毕并决定采纳您的代码之后,我们会与您取得联系,并邀请您签署贡献者许可协议(CLA),将您的代码所有权授予OpenSCA项目。 72 | 73 | 协议签署完毕后,您的代码贡献会成为OpenSCA项目的一部分,您也会成为我们的Contributor,将可以作为项目成员参与进一步的项目功能规划和落实、BUG判定、PR审核和Contributor认证等过程。 74 | 75 | 76 | --- 77 | 78 | 79 | 我们感激每位贡献者对OpenSCA做出的贡献。 80 | 81 | 再次感谢您对OpenSCA的关注和对我们理念的认可。 82 | 83 | 贡献指南的英文版本请见 [Contributing Guideline-en v.1.0](./Contributing_Guideline-v1.0.md)。 84 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting security issue 2 | 3 | If you think you have found a security vulnerability, please send a report to [opensca@anpro-tech.com](mailto:opensca@anpro-tech.com). This address can be used for all of OpenSCA Community products (including but not limited to OpenSCA-cli, OpenSCA-IntelliJ-Plugins, OpenSCA-VSCode-Plugins and opensca.xmirror.cn) We Can accept only vulnerability reports at this address. 4 | 5 | OpenSCA Community will send you a response indicating the next steps in handing your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. 6 | 7 | **Important:** we ask you to not disclose the vulnerability before it have been fixed and announced, unless yor received a reponse from the OpenSCA Community security team that you can do so. 8 | 9 | We will post a summary, remediation, and mitigation details for any patch containing security fixes at [OpenSCA blog](https://opensca.xmirror.cn/resources). 10 | -------------------------------------------------------------------------------- /cmd/detail/login.go: -------------------------------------------------------------------------------- 1 | package detail 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/md5" 7 | "encoding/hex" 8 | "encoding/json" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "net/http" 13 | "os" 14 | "strings" 15 | 16 | "github.com/xmirrorsecurity/opensca-cli/v3/cmd/config" 17 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 18 | "golang.org/x/term" 19 | ) 20 | 21 | func Login() error { 22 | fmt.Println("Log in with your username to access cloud-based software supply-chain risk data from OpenSCA SaaS.") 23 | fmt.Println("If you don't have an account, please register at https://opensca.xmirror.cn/") 24 | 25 | fmt.Print("Enter username or email: ") 26 | username, err := bufio.NewReader(os.Stdin).ReadString('\n') 27 | username = strings.TrimRight(username, "\r\n") 28 | if err != nil { 29 | return err 30 | } 31 | 32 | fmt.Print("Enter password: ") 33 | password, err := term.ReadPassword(int(os.Stdin.Fd())) 34 | password = bytes.TrimRight(password, "\r\n") 35 | if err != nil { 36 | return err 37 | } 38 | 39 | m := md5.New() 40 | m.Write(password) 41 | pswdmd5 := hex.EncodeToString(m.Sum(nil)) 42 | 43 | fmt.Printf("\n%s login ...\n", username) 44 | 45 | url := config.Conf().Origin.Url + "/oss-saas/api-v1/open-sca-client/token" 46 | url += fmt.Sprintf("?usernameOrEmail=%s&password=%s", username, pswdmd5) 47 | 48 | resp, err := http.DefaultClient.Get(url) 49 | if err != nil { 50 | return err 51 | } 52 | defer resp.Body.Close() 53 | 54 | data, err := io.ReadAll(resp.Body) 55 | if err != nil { 56 | return err 57 | } 58 | logs.Debugf("login response: %s", string(data)) 59 | 60 | loginResp := struct { 61 | Code int `json:"code"` 62 | Message string `json:"message"` 63 | Data string `json:"data"` 64 | }{} 65 | json.Unmarshal(data, &loginResp) 66 | if loginResp.Code == 0 && loginResp.Message == "success" { 67 | config.Conf().Origin.Token = loginResp.Data 68 | } else { 69 | return errors.New(loginResp.Message) 70 | } 71 | 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /cmd/detail/origin.go: -------------------------------------------------------------------------------- 1 | package detail 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "os" 8 | "strings" 9 | "sync" 10 | "time" 11 | 12 | "github.com/xmirrorsecurity/opensca-cli/v3/cmd/config" 13 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 14 | "gorm.io/driver/mysql" 15 | "gorm.io/driver/sqlite" 16 | "gorm.io/gorm" 17 | "gorm.io/gorm/logger" 18 | ) 19 | 20 | var ( 21 | _origin *BaseOrigin 22 | _once = sync.Once{} 23 | ) 24 | 25 | type VulnInfo struct { 26 | *Vuln 27 | Vendor string `json:"vendor" gorm:"column:vendor"` 28 | Product string `json:"product" gorm:"column:product"` 29 | Version string `json:"version" gorm:"column:version"` 30 | Language string `json:"language" gorm:"column:language"` 31 | } 32 | 33 | type BaseOrigin struct { 34 | // origin data 35 | // map[language]map[component_name][]VulnInfo 36 | data map[string]map[string][]VulnInfo 37 | idSet map[string]bool 38 | } 39 | 40 | func NewBaseOrigin() *BaseOrigin { 41 | return &BaseOrigin{ 42 | data: map[string]map[string][]VulnInfo{}, 43 | idSet: map[string]bool{}, 44 | } 45 | } 46 | 47 | func (o *BaseOrigin) LoadDataOrigin(data ...VulnInfo) { 48 | if o == nil { 49 | return 50 | } 51 | for _, info := range data { 52 | if info.Vuln == nil { 53 | continue 54 | } 55 | if o.idSet[info.Id] { 56 | continue 57 | } 58 | o.idSet[info.Id] = true 59 | name := strings.ToLower(info.Product) 60 | language := strings.ToLower(info.Language) 61 | if _, ok := o.data[language]; !ok { 62 | o.data[language] = map[string][]VulnInfo{} 63 | } 64 | vulns := o.data[language] 65 | vulns[name] = append(vulns[name], info) 66 | } 67 | } 68 | 69 | func GetOrigin() *BaseOrigin { 70 | _once.Do(func() { 71 | _origin = NewBaseOrigin() 72 | c := config.Conf().Origin 73 | _origin.LoadJsonOrigin(c.Json) 74 | _origin.LoadMysqlOrigin(c.Mysql) 75 | _origin.LoadSqliteOrigin(c.Sqlite) 76 | logs.Info(fmt.Sprintf("load %d vulnerability", len(_origin.idSet))) 77 | }) 78 | return _origin 79 | } 80 | 81 | func (o *BaseOrigin) LoadJsonOrigin(filepath string) { 82 | if filepath == "" { 83 | return 84 | } 85 | if jsonFile, err := os.Open(filepath); err != nil { 86 | logs.Error(err) 87 | } else { 88 | data := []VulnInfo{} 89 | err = json.NewDecoder(jsonFile).Decode(&data) 90 | if err != nil { 91 | logs.Error(err) 92 | } 93 | o.LoadDataOrigin(data...) 94 | } 95 | } 96 | 97 | func (o *BaseOrigin) LoadMysqlOrigin(cfg config.SqlOrigin) { 98 | o.LoadSqlOrigin(mysql.Open(cfg.Dsn), cfg) 99 | } 100 | 101 | func (o *BaseOrigin) LoadSqliteOrigin(cfg config.SqlOrigin) { 102 | o.LoadSqlOrigin(sqlite.Open(cfg.Dsn), cfg) 103 | } 104 | 105 | func (o *BaseOrigin) LoadSqlOrigin(dialector gorm.Dialector, cfg config.SqlOrigin) { 106 | if cfg.Dsn == "" { 107 | return 108 | } 109 | db, err := gorm.Open(dialector, &gorm.Config{ 110 | Logger: logger.New(log.Default(), logger.Config{ 111 | SlowThreshold: 1 * time.Second, 112 | LogLevel: logger.Info, 113 | }), 114 | }) 115 | if err != nil { 116 | logs.Error(err) 117 | return 118 | } 119 | data := []VulnInfo{} 120 | db.Table(cfg.Table).Find(&data) 121 | o.LoadDataOrigin(data...) 122 | } 123 | -------------------------------------------------------------------------------- /cmd/format/csv.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | 8 | "github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail" 9 | ) 10 | 11 | func Csv(report Report, out string) { 12 | 13 | table := "Name, Version, Vendor, License, Language, PURL\n" 14 | 15 | report.DepDetailGraph.ForEach(func(n *detail.DepDetailGraph) bool { 16 | 17 | licenseTxt := "" 18 | if len(n.Licenses) > 0 { 19 | licenseTxt = n.Licenses[0].ShortName 20 | } 21 | 22 | formatCsv := func(s string) string { 23 | if strings.Contains(s, `"`) { 24 | s = strings.ReplaceAll(s, `"`, `""`) 25 | } 26 | if strings.Contains(s, `,`) { 27 | s = fmt.Sprintf(`"%s"`, s) 28 | } 29 | return s 30 | } 31 | 32 | if n.Name != "" { 33 | table = table + fmt.Sprintf("%s,%s,%s,%s,%s,%s\n", 34 | formatCsv(n.Name), 35 | formatCsv(n.Version), 36 | formatCsv(n.Vendor), 37 | formatCsv(licenseTxt), 38 | formatCsv(n.Language), 39 | formatCsv(n.Purl()), 40 | ) 41 | } 42 | 43 | return true 44 | }) 45 | 46 | outWrite(out, func(w io.Writer) error { 47 | _, err := w.Write([]byte(table)) 48 | return err 49 | }) 50 | 51 | } 52 | -------------------------------------------------------------------------------- /cmd/format/cyclonedx.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | 7 | "github.com/CycloneDX/cyclonedx-go" 8 | "github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail" 9 | ) 10 | 11 | func cyclonedxbom(dep *detail.DepDetailGraph) *cyclonedx.BOM { 12 | 13 | metadata := cyclonedx.Metadata{} 14 | components := []cyclonedx.Component{} 15 | dependencies := []cyclonedx.Dependency{} 16 | 17 | dep.ForEach(func(n *detail.DepDetailGraph) bool { 18 | 19 | if n == dep { 20 | metadata.Component = &cyclonedx.Component{ 21 | BOMRef: n.Purl(), 22 | Type: cyclonedx.ComponentTypeApplication, 23 | Name: n.Name, 24 | Version: n.Version, 25 | PackageURL: n.Purl(), 26 | } 27 | return true 28 | } 29 | 30 | if n.Name != "" { 31 | components = append(components, cyclonedx.Component{ 32 | BOMRef: "ref-" + n.ID, 33 | Type: cyclonedx.ComponentTypeLibrary, 34 | Author: n.Vendor, 35 | Name: n.Name[strings.LastIndex(n.Name, "/")+1:], 36 | Version: n.Version, 37 | PackageURL: n.Purl(), 38 | }) 39 | var deps []string 40 | for _, child := range n.Children { 41 | deps = append(deps, child.Purl()) 42 | } 43 | dependencies = append(dependencies, cyclonedx.Dependency{ 44 | Ref: n.Purl(), 45 | Dependencies: &deps, 46 | }) 47 | } 48 | 49 | return true 50 | }) 51 | 52 | bom := cyclonedx.NewBOM() 53 | bom.Metadata = &metadata 54 | bom.Components = &components 55 | bom.Dependencies = &dependencies 56 | return bom 57 | } 58 | 59 | func CycloneDXJson(report Report, out string) { 60 | bom := cyclonedxbom(report.DepDetailGraph) 61 | outWrite(out, func(w io.Writer) error { 62 | return cyclonedx.NewBOMEncoder(w, cyclonedx.BOMFileFormatJSON).SetPretty(true).Encode(bom) 63 | }) 64 | } 65 | 66 | func CycloneDXXml(report Report, out string) { 67 | bom := cyclonedxbom(report.DepDetailGraph) 68 | outWrite(out, func(w io.Writer) error { 69 | return cyclonedx.NewBOMEncoder(w, cyclonedx.BOMFileFormatXML).SetPretty(true).Encode(bom) 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /cmd/format/dpsbom.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "archive/zip" 5 | "crypto/md5" 6 | "crypto/sha1" 7 | "crypto/sha256" 8 | "encoding/hex" 9 | "encoding/json" 10 | "errors" 11 | "fmt" 12 | "hash" 13 | "io" 14 | "path/filepath" 15 | "strings" 16 | 17 | "github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail" 18 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 19 | ) 20 | 21 | func DpSbomZip(report Report, out string) { 22 | zipFile := out 23 | if !strings.HasSuffix(out, ".zip") { 24 | zipFile = out + ".zip" 25 | } 26 | jsonName := filepath.Base(out) 27 | if !strings.HasSuffix(jsonName, ".json") { 28 | jsonName = jsonName + ".json" 29 | } 30 | outWrite(zipFile, func(w io.Writer) error { 31 | doc := pdSbomDoc(report) 32 | if doc.Hashes.HashFile == "" { 33 | return errors.New("hash file is required") 34 | } 35 | 36 | var h hash.Hash 37 | switch strings.ToLower(doc.Hashes.Algorithm) { 38 | case "sha-256": 39 | h = sha256.New() 40 | case "sha-1": 41 | h = sha1.New() 42 | case "md5": 43 | h = md5.New() 44 | case "": 45 | return errors.New("hash algorithm is required") 46 | default: 47 | return fmt.Errorf("unsupported hash algorithm: %s", doc.Hashes.Algorithm) 48 | } 49 | 50 | tojson := func(w io.Writer) error { 51 | encoder := json.NewEncoder(w) 52 | encoder.SetIndent("", " ") 53 | return encoder.Encode(doc) 54 | } 55 | 56 | zipfile := zip.NewWriter(w) 57 | defer zipfile.Close() 58 | 59 | sbomfile, err := zipfile.Create(jsonName) 60 | if err != nil { 61 | return err 62 | } 63 | err = tojson(sbomfile) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | hashfile, err := zipfile.Create(doc.Hashes.HashFile) 69 | if err != nil { 70 | return err 71 | } 72 | err = tojson(h) 73 | if err != nil { 74 | return err 75 | } 76 | hashstr := hex.EncodeToString(h.Sum(nil)[:]) 77 | hashfile.Write([]byte(hashstr)) 78 | 79 | return nil 80 | }) 81 | } 82 | 83 | func pdSbomDoc(report Report) *model.DpSbomDocument { 84 | 85 | doc := model.NewDpSbomDocument(report.TaskInfo.AppName, "opensca-cli") 86 | 87 | report.DepDetailGraph.ForEach(func(n *detail.DepDetailGraph) bool { 88 | 89 | if n.Name == "" { 90 | return true 91 | } 92 | 93 | lics := []string{} 94 | for _, lic := range n.Licenses { 95 | lics = append(lics, lic.ShortName) 96 | } 97 | doc.AppendComponents(func(dsp *model.DpSbomPackage) { 98 | dsp.Identifier.Purl = n.Purl() 99 | dsp.Name = n.Name 100 | dsp.Version = n.Version 101 | dsp.License = lics 102 | }) 103 | 104 | children := []string{} 105 | for _, c := range n.Children { 106 | if c.Name == "" { 107 | continue 108 | } 109 | children = append(children, c.Purl()) 110 | } 111 | doc.AppendDependencies(n.Purl(), children) 112 | 113 | return true 114 | }) 115 | 116 | return doc 117 | } 118 | -------------------------------------------------------------------------------- /cmd/format/dsdx.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "encoding/json" 5 | "encoding/xml" 6 | "io" 7 | 8 | "github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail" 9 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 10 | ) 11 | 12 | func Dsdx(report Report, out string) { 13 | outWrite(out, func(w io.Writer) error { 14 | return dsdxDoc(report).WriteDsdx(w) 15 | }) 16 | } 17 | 18 | func DsdxJson(report Report, out string) { 19 | outWrite(out, func(w io.Writer) error { 20 | return json.NewEncoder(w).Encode(dsdxDoc(report)) 21 | }) 22 | } 23 | 24 | func DsdxXml(report Report, out string) { 25 | outWrite(out, func(w io.Writer) error { 26 | return xml.NewEncoder(w).Encode(dsdxDoc(report)) 27 | }) 28 | } 29 | 30 | func dsdxDoc(report Report) *model.DsdxDocument { 31 | 32 | doc := model.NewDsdxDocument(report.TaskInfo.AppName, "opensca-cli") 33 | 34 | report.DepDetailGraph.ForEach(func(n *detail.DepDetailGraph) bool { 35 | 36 | if n.Name == "" { 37 | return true 38 | } 39 | 40 | lics := []string{} 41 | for _, lic := range n.Licenses { 42 | lics = append(lics, lic.ShortName) 43 | } 44 | doc.AppendComponents(n.ID, n.Vendor, n.Name, n.Version, n.Language, lics) 45 | 46 | childrenIds := []string{} 47 | for _, c := range n.Children { 48 | if c.Name == "" { 49 | continue 50 | } 51 | childrenIds = append(childrenIds, c.ID) 52 | } 53 | doc.AppendDependencies(n.ID, childrenIds) 54 | 55 | return true 56 | }) 57 | 58 | return doc 59 | } 60 | -------------------------------------------------------------------------------- /cmd/format/html.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "encoding/json" 7 | "io" 8 | 9 | "github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail" 10 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 11 | ) 12 | 13 | //go:embed html_tpl 14 | var index []byte 15 | 16 | // html组件字段 17 | type htmlDep struct { 18 | *detail.DepDetailGraph 19 | SecId int `json:"security_level_id,omitempty"` 20 | Statis map[int]int `json:"vuln_statis"` 21 | Children any `json:"children,omitempty"` 22 | } 23 | 24 | // html统计信息 25 | type htmlStatis struct { 26 | Component map[int]int `json:"component"` 27 | Vuln map[int]int `json:"vuln"` 28 | } 29 | 30 | func Html(report Report, out string) { 31 | 32 | deps := []htmlDep{} 33 | statis := htmlStatis{ 34 | Component: map[int]int{}, 35 | Vuln: map[int]int{}, 36 | } 37 | vulnMap := map[string]int{} 38 | 39 | // 遍历所有组件 40 | report.DepDetailGraph.ForEach(func(n *detail.DepDetailGraph) bool { 41 | 42 | // 组件风险 43 | secid := 5 44 | // 不同风险等级的漏洞数 45 | vuln_statis := map[int]int{} 46 | for _, v := range n.Vulnerabilities { 47 | vulnMap[v.Id] = v.SecurityLevelId 48 | vuln_statis[v.SecurityLevelId]++ 49 | if secid > v.SecurityLevelId { 50 | secid = v.SecurityLevelId 51 | } 52 | } 53 | 54 | if n.Name != "" { 55 | statis.Component[secid]++ 56 | deps = append(deps, htmlDep{ 57 | DepDetailGraph: n, 58 | Children: nil, 59 | SecId: secid, 60 | Statis: vuln_statis, 61 | }) 62 | } 63 | 64 | return true 65 | }) 66 | 67 | // 统计漏洞风险 68 | for _, secid := range vulnMap { 69 | statis.Vuln[secid]++ 70 | } 71 | 72 | // report依赖信息临时置空用于生成html报告 73 | graph := report.DepDetailGraph 74 | report.DepDetailGraph = nil 75 | defer func() { report.DepDetailGraph = graph }() 76 | 77 | // 生成html报告需要的json数据 78 | if data, err := json.Marshal(struct { 79 | TaskInfo TaskInfo `json:"task_info"` 80 | Statis htmlStatis `json:"statis"` 81 | Components []htmlDep `json:"components"` 82 | }{ 83 | TaskInfo: report.TaskInfo, 84 | Statis: statis, 85 | Components: deps, 86 | }); err != nil { 87 | logs.Warn(err) 88 | } else { 89 | outWrite(out, func(w io.Writer) error { 90 | _, err := w.Write(bytes.Replace(index, []byte(`"此处填充json数据"`), data, 1)) 91 | return err 92 | }) 93 | return 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /cmd/format/json.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | ) 7 | 8 | func Json(report Report, out string) { 9 | outWrite(out, func(w io.Writer) error { 10 | encoder := json.NewEncoder(w) 11 | encoder.SetIndent("", " ") 12 | return encoder.Encode(report) 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /cmd/format/saas.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "mime/multipart" 9 | "net/http" 10 | "os" 11 | "strconv" 12 | 13 | "github.com/google/uuid" 14 | "github.com/xmirrorsecurity/opensca-cli/v3/cmd/config" 15 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/common" 16 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 17 | ) 18 | 19 | // Saas 向saas平台发送检测报告 20 | func Saas(report Report) error { 21 | 22 | url := config.Conf().Origin.Url 23 | token := config.Conf().Origin.Token 24 | proj := config.Conf().Origin.Proj 25 | 26 | if url == "" || token == "" || proj == nil { 27 | return nil 28 | } 29 | 30 | body := &bytes.Buffer{} 31 | w := multipart.NewWriter(body) 32 | w.WriteField("token", token) 33 | w.WriteField("projectUid", *proj) 34 | w.WriteField("detectOrigin", strconv.Itoa(5)) 35 | 36 | uid, err := uuid.NewV6() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | // dsdx 42 | dsdxWriter, err := w.CreateFormFile("dsdxFile", uid.String()+".dsdx") 43 | if err != nil { 44 | return err 45 | } 46 | f := common.CreateTemp("dsdx") 47 | f.Close() 48 | defer os.Remove(f.Name()) 49 | Dsdx(report, f.Name()) 50 | dsdxFile, err := os.Open(f.Name()) 51 | if err != nil { 52 | return err 53 | } 54 | defer dsdxFile.Close() 55 | io.Copy(dsdxWriter, dsdxFile) 56 | 57 | // json 58 | jsonWriter, err := w.CreateFormFile("jsonFile", uid.String()+".json") 59 | if err != nil { 60 | return err 61 | } 62 | f = common.CreateTemp("json") 63 | f.Close() 64 | defer os.Remove(f.Name()) 65 | Json(report, f.Name()) 66 | jsonFile, err := os.Open(f.Name()) 67 | if err != nil { 68 | return err 69 | } 70 | defer jsonFile.Close() 71 | io.Copy(jsonWriter, jsonFile) 72 | 73 | w.Close() 74 | 75 | req, err := http.NewRequest("POST", url+"/oss-saas/api-v1/ide-plugin/sync/result", body) 76 | if err != nil { 77 | return err 78 | } 79 | req.Header.Set("Content-Type", w.FormDataContentType()) 80 | resp, err := common.HttpSaasClient.Do(req) 81 | if err != nil { 82 | return err 83 | } 84 | defer resp.Body.Close() 85 | 86 | data, err := io.ReadAll(resp.Body) 87 | if err != nil { 88 | return err 89 | } 90 | logs.Debugf("saas resp: %s", string(data)) 91 | saasResp := struct { 92 | Code int `json:"code"` 93 | Message string `json:"message"` 94 | Data string `json:"data"` 95 | }{} 96 | json.Unmarshal(data, &saasResp) 97 | if saasResp.Code == 0 && saasResp.Message == "success" { 98 | logs.Infof("saas url: %s/%s", url, saasResp.Data) 99 | fmt.Printf("saas url: %s/%s\n", url, saasResp.Data) 100 | } 101 | 102 | return nil 103 | } 104 | -------------------------------------------------------------------------------- /cmd/format/spdx.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "encoding/json" 5 | "encoding/xml" 6 | "io" 7 | 8 | "github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail" 9 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 10 | ) 11 | 12 | func Spdx(report Report, out string) { 13 | outWrite(out, func(w io.Writer) error { 14 | return spdxDoc(report).WriteSpdx(w) 15 | }) 16 | } 17 | 18 | func SpdxJson(report Report, out string) { 19 | outWrite(out, func(w io.Writer) error { 20 | return json.NewEncoder(w).Encode(spdxDoc(report)) 21 | }) 22 | } 23 | 24 | func SpdxXml(report Report, out string) { 25 | outWrite(out, func(w io.Writer) error { 26 | return xml.NewEncoder(w).Encode(spdxDoc(report)) 27 | }) 28 | } 29 | 30 | func spdxDoc(report Report) *model.SpdxDocument { 31 | 32 | doc := model.NewSpdxDocument(report.TaskInfo.AppName) 33 | 34 | report.DepDetailGraph.ForEach(func(n *detail.DepDetailGraph) bool { 35 | 36 | if n.Name == "" { 37 | return true 38 | } 39 | 40 | lics := []string{} 41 | for _, lic := range n.Licenses { 42 | lics = append(lics, lic.ShortName) 43 | } 44 | doc.AddPackage(n.ID, n.Vendor, n.Name, n.Version, model.Language(n.Language), lics) 45 | 46 | for _, c := range n.Children { 47 | if c.Name == "" { 48 | continue 49 | } 50 | doc.AddRelation(n.ID, c.ID) 51 | } 52 | 53 | return true 54 | }) 55 | 56 | return doc 57 | } 58 | -------------------------------------------------------------------------------- /cmd/format/sqlite.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "database/sql" 5 | _ "embed" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail" 11 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 12 | 13 | _ "github.com/glebarez/go-sqlite" 14 | ) 15 | 16 | //go:embed sqlite_init.sql 17 | var initSql string 18 | 19 | func Sqlite(report Report, out string) { 20 | 21 | dbFile := out 22 | db, err := sql.Open("sqlite", dbFile) 23 | if err != nil { 24 | logs.Error(err) 25 | } 26 | defer db.Close() 27 | 28 | if _, err := os.Stat(dbFile); err != nil { 29 | logs.Info("initing database: " + dbFile) 30 | _, err = db.Exec(initSql) 31 | if err != nil { 32 | logs.Warn(err) 33 | } 34 | } 35 | 36 | moduleName := filepath.Base(report.TaskInfo.AppName) 37 | 38 | if report.DepDetailGraph != nil && report.DepDetailGraph.Name != "" { 39 | moduleName = report.DepDetailGraph.Name 40 | } 41 | 42 | logs.Debugf("sql report of %s", moduleName) 43 | insertFmt := "insert or ignore into component (name, version, vendor, language, purl) values ('%s','%s','%s','%s','%s');\n" 44 | insertRef := "insert or ignore into reference (module_name, purl) values ('%s','%s');\n" 45 | 46 | report.DepDetailGraph.ForEach(func(n *detail.DepDetailGraph) bool { 47 | if n.Name == "" { 48 | return true 49 | } 50 | _, err := db.Exec("insert or ignore into component (name, version, vendor, language, purl) values (?,?,?,?,?)", n.Name, n.Version, n.Vendor, n.Language, n.Purl()) 51 | if err != nil { 52 | logs.Debugf(insertFmt, quoteEscape(n.Name), quoteEscape(n.Version), quoteEscape(n.Vendor), quoteEscape(n.Language), quoteEscape(n.Purl())) 53 | logs.Warn(err) 54 | } 55 | return true 56 | }) 57 | 58 | report.DepDetailGraph.ForEach(func(n *detail.DepDetailGraph) bool { 59 | if n.Name == "" { 60 | return true 61 | } 62 | _, err := db.Exec("insert or ignore into reference (module_name, purl) values (?, ?)", moduleName, n.Purl()) 63 | if err != nil { 64 | logs.Debugf(insertRef, moduleName, quoteEscape(n.Purl())) 65 | logs.Warn(err) 66 | } 67 | return true 68 | }) 69 | 70 | } 71 | 72 | // quoteEscape 转义单引号 73 | func quoteEscape(src string) (out string) { 74 | return strings.ReplaceAll(src, `'`, "\\'") 75 | } 76 | -------------------------------------------------------------------------------- /cmd/format/sqlite_init.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- File generated with SQLiteStudio v3.4.3 on 22:18:37 2023-07-28 3 | -- 4 | -- Text encoding used: System 5 | -- 6 | PRAGMA foreign_keys = off; 7 | BEGIN TRANSACTION; 8 | 9 | -- Table: component 10 | CREATE TABLE IF NOT EXISTS component ( 11 | id INTEGER PRIMARY KEY AUTOINCREMENT, 12 | name VARCHAR (50) NOT NULL, 13 | version VARCHAR (50) NOT NULL, 14 | vendor VARCHAR (50) 15 | DEFAULT 'N.A.', 16 | language VARCHAR (50) NOT NULL, 17 | purl VARCHAR (256) NOT NULL 18 | UNIQUE 19 | ); 20 | 21 | 22 | -- Table: reference 23 | CREATE TABLE IF NOT EXISTS reference ( 24 | id INTEGER PRIMARY KEY AUTOINCREMENT, 25 | module_name VARCHAR (100) NOT NULL, 26 | purl VARCHAR (256) NOT NULL 27 | ); 28 | 29 | 30 | -- Index: uk_ref 31 | CREATE UNIQUE INDEX IF NOT EXISTS uk_ref ON reference ( 32 | module_name, 33 | purl 34 | ); 35 | 36 | 37 | COMMIT TRANSACTION; 38 | PRAGMA foreign_keys = on; 39 | -------------------------------------------------------------------------------- /cmd/format/statis.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail" 7 | ) 8 | 9 | // Statis 统计概览信息 10 | func Statis(report Report) (string, string) { 11 | 12 | // 组件风险统计 key:0代表组件总数 13 | depStatic := map[int]int{ 14 | 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 15 | } 16 | 17 | // 漏洞风险统计 18 | vulStatic := map[int]int{ 19 | 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 20 | } 21 | // 记录统计过的漏洞 22 | vulSet := map[string]bool{} 23 | 24 | report.DepDetailGraph.ForEach(func(n *detail.DepDetailGraph) bool { 25 | 26 | if n.Name == "" { 27 | return true 28 | } 29 | 30 | // 当前组件风险 31 | risk := 5 32 | for _, v := range n.Vulnerabilities { 33 | if !vulSet[v.Id] { 34 | vulSet[v.Id] = true 35 | if v.SecurityLevelId > 0 { 36 | vulStatic[v.SecurityLevelId]++ 37 | } 38 | vulStatic[0]++ 39 | } 40 | if v.SecurityLevelId < risk { 41 | // 组件风险取最高漏洞风险 42 | risk = v.SecurityLevelId 43 | } 44 | } 45 | 46 | if risk > 0 { 47 | depStatic[risk]++ 48 | } 49 | depStatic[0]++ 50 | 51 | return true 52 | }) 53 | if vulStatic[0] != 0 { 54 | return fmt.Sprintf("Components:%d C:%d H:%d M:%d L:%d", 55 | depStatic[0], depStatic[1], depStatic[2], depStatic[3], depStatic[4]), 56 | fmt.Sprintf("\nVulnerabilities:%d C:%d H:%d M:%d L:%d", 57 | vulStatic[0], vulStatic[1], vulStatic[2], vulStatic[3], vulStatic[4]) 58 | } 59 | return fmt.Sprintf("Components: %d", depStatic[0]), "" 60 | } 61 | -------------------------------------------------------------------------------- /cmd/format/swid.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "archive/zip" 5 | "encoding/json" 6 | "encoding/xml" 7 | "errors" 8 | "io" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail" 13 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 14 | 15 | "github.com/veraison/swid" 16 | ) 17 | 18 | func swidZip(out string, report Report, writeFunc func(tag *swid.SoftwareIdentity, w io.Writer) error) { 19 | outWrite(out+".zip", func(writer io.Writer) error { 20 | 21 | zf := zip.NewWriter(writer) 22 | defer zf.Close() 23 | 24 | var werr error 25 | 26 | report.DepDetailGraph.ForEach(func(n *detail.DepDetailGraph) bool { 27 | 28 | if n.Name == "" { 29 | return true 30 | } 31 | 32 | tag, err := swid.NewTag(n.Dep.Key(), n.Name, n.Version) 33 | if err != nil { 34 | logs.Warn(err) 35 | return true 36 | } 37 | 38 | tag.TagVersion = 1 39 | tag.SoftwareName = n.Name 40 | tag.SoftwareVersion = n.Version 41 | tag.VersionScheme = &swid.VersionScheme{} 42 | tag.VersionScheme.SetCode(1) 43 | 44 | if n.Vendor != "" { 45 | e := swid.Entity{ 46 | RegID: n.Vendor, 47 | EntityName: "The vendor of component", 48 | Roles: swid.Roles{}, 49 | } 50 | e.Roles.Set("softwareCreator") 51 | tag.AddEntity(e) 52 | } 53 | 54 | name := []string{} 55 | if n.Vendor != "" { 56 | name = append(name, n.Vendor) 57 | } 58 | name = append(name, n.Name) 59 | if n.Version != "" { 60 | name = append(name, n.Version) 61 | } 62 | 63 | w, err := zf.Create(strings.Join(name, "-") + filepath.Ext(out)) 64 | if err != nil { 65 | logs.Warn(err) 66 | return true 67 | } 68 | 69 | werr = errors.Join(werr, writeFunc(tag, w)) 70 | 71 | return true 72 | }) 73 | 74 | return werr 75 | }) 76 | } 77 | 78 | func SwidJson(report Report, out string) { 79 | swidZip(out, report, func(tag *swid.SoftwareIdentity, w io.Writer) error { 80 | return json.NewEncoder(w).Encode(tag) 81 | }) 82 | } 83 | 84 | func SwidXml(report Report, out string) { 85 | swidZip(out, report, func(tag *swid.SoftwareIdentity, w io.Writer) error { 86 | return xml.NewEncoder(w).Encode(tag) 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /cmd/format/xml.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "encoding/xml" 5 | "io" 6 | ) 7 | 8 | func Xml(report Report, out string) { 9 | outWrite(out, func(w io.Writer) error { 10 | return xml.NewEncoder(w).Encode(report) 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | // 检测项目路径 4 | // project path 5 | // support http(s)/ftp/file protocol 6 | "path": "", 7 | 8 | // 导出报告路径 9 | // report path 10 | // suport ext: html/json/xml/csv/sqlite/cdx/spdx/swid/dsdx 11 | "out": "output.json,output.html", 12 | 13 | // 检测日志路径 14 | // log path 15 | "log": "opensca.log", 16 | 17 | // opensca 配置项 18 | // opensca optional 19 | "optional": { 20 | 21 | // 开启ui 22 | // open ui 23 | "ui": false, 24 | 25 | // 相同组件仅保留一条数据 检出路径合并 26 | // delete duplicate components and merge path 27 | "dedup": false, 28 | 29 | // 仅检测目录(跳过压缩包) 30 | // only detect directory (skip compress file) 31 | "dir": false, 32 | 33 | // 仅保留漏洞组件 34 | // only save components with vulnerability 35 | "vuln": false, 36 | 37 | // 开启进度条 38 | // open progress bar 39 | "progress": true, 40 | 41 | // 保留开发组件 42 | // save develop components 43 | "dev": true, 44 | 45 | // 开启 TLS 验证 46 | // use tls verify, default: false 47 | "tls": false, 48 | 49 | // 全局http代理 50 | // global proxy for http requests, eg: http://127.0.0.1:7890 51 | "proxy": "" 52 | 53 | }, 54 | 55 | // 组件仓库配置 56 | // repo config 57 | "repo": { 58 | 59 | // maven repo 60 | "maven": [ 61 | { 62 | "url": "https://maven.aliyun.com/repository/public", 63 | // 认证信息 没有可不填 64 | // auth info, not required 65 | "username": "", 66 | "password": "" 67 | }, 68 | { 69 | "url": "https://repo.maven.apache.org/maven2/" 70 | } 71 | ], 72 | 73 | // npm repo 74 | "npm": [ 75 | { 76 | "url": "https://registry.npmmirror.com" 77 | } 78 | ], 79 | 80 | // composer repo 81 | "composer": [ 82 | { 83 | "url":"https://mirrors.aliyun.com/composer/p2" 84 | } 85 | ] 86 | 87 | }, 88 | 89 | // 数据库源 90 | // database origin 91 | "origin": { 92 | 93 | // opensca web service url 94 | "url": "https://opensca.xmirror.cn", 95 | // opensca web service token 96 | "token": "", 97 | // opensca saas project id, not required 98 | // "proj": "", 99 | 100 | // json dbfile 101 | "json": "", 102 | 103 | // mysql origin 104 | "mysql": { 105 | // user:password@tcp(ip:port)/dbname 106 | "dsn": "", 107 | "table": "" 108 | }, 109 | 110 | // sqlite origin 111 | "sqlite": { 112 | // sqlite dbfile 113 | "dsn": "", 114 | "table": "" 115 | } 116 | 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG VERSION 2 | ARG TAG 3 | ARG BASEIMAGE 4 | 5 | FROM golang:latest AS builder 6 | 7 | ARG VERSION 8 | ARG TAG 9 | 10 | COPY ./ /app 11 | WORKDIR /app 12 | 13 | RUN CGO_ENABLED=0 GOOS=linux \ 14 | go build -ldflags "-s -w -X 'main.version=${VERSION}-${TAG}' " \ 15 | -o /app/opensca-cli main.go 16 | 17 | FROM ${BASEIMAGE} 18 | LABEL authors="OpenSCA Team " 19 | 20 | COPY --from=builder /app/opensca-cli /app/opensca-cli 21 | COPY --from=builder /app/opensca-cli /app/config.json 22 | WORKDIR /src 23 | ENTRYPOINT [ "/app/opensca-cli", "-path", "/src"] 24 | CMD ["-config", "/src/config.json"] 25 | -------------------------------------------------------------------------------- /docs/About_OpenSCA-zh_CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About OpenSCA 3 | author: Cyber Chen 4 | date: 2023-11-29T11:22:00+08:00 5 | --- 6 | 7 | [返回目录](/docs/README-zh-CN.md) | [English](./About_OpenSCA.md) 8 | 9 | # 关于 OpenSCA 10 | > 用开源的方式做开源风险治理 11 | 12 | OpenSCA 是 SCA 技术原理的开源实现。作为[悬镜安全](https://www.xmirror.cn)旗下[源鉴SCA开源威胁管控产品](https://oss.xmirror.cn/)的开源版本,OpenSCA继承了源鉴SCA的多源SCA开源应用安全缺陷检测等核心能力,通过软件成分分析、依赖分析、特征分析、引用识别、合规分析等方法,深度挖掘组件中潜藏的各类安全漏洞及开源协议风险,保障应用开源组件引入的安全。 13 | 14 | 不同于传统企业版SCA工具,OpenSCA为治理开源风险提供了充满可能性的开源解决方案。它轻量易用、能力完整,支持漏洞库、私服库等自主配置,覆盖IDE/命令行/云平台、离线/在线等多种使用场景,可灵活地接入开发流程,为企业、组织及个人用户输出透明化的组件资产及风险清单。 15 | 16 | 围绕OpenSCA,我们搭建起了聚集上万开源项目维护者和使用者的全球极客开源数字供应链安全社区,社区涵盖信息通信、泛互联网、车联网、金融、能源等众多行业用户,为万千中国数字安全实践者们构筑起交流的平台与创新的基地。 17 | 18 | # 支持语言 & 包管理器 19 | 20 | | 语言 | 包管理器 | 特征文件 | 21 | | :--:| :--: | :-- | 22 | | Java | Maven | `pom.xml` | 23 | | | Gradle | `.gradle`, `.gradle.kts` | 24 | | JavaScripts | NPM | `package-lock.json`, `package.json`, `yarn.lock` | 25 | | PHP | Composer | `composer.json`, `composer.lock` | 26 | | Ruby | gem | `gemfile.lock` | 27 | | Golang | Go mod | `go.mod`, `go.sum` | 28 | | Python | Pip | `Pipfile`, `Pipfile.lock`, `setup.py`, `requirements.txt`(依赖 pipenv, 需联网), `requirements.in`(依赖 pipenv, 需联网) | 29 | | Rust | cargo | `Cargo.lock` | 30 | | Erlang | Rebar | `rebar.lock` | 31 | 32 | # 检测流程 33 | 34 | ![DetectionProcess](/resources/DetectionProcess-zh_CN.png) 35 | -------------------------------------------------------------------------------- /docs/About_OpenSCA.md: -------------------------------------------------------------------------------- 1 | [Go Back](/docs/README.md) | [中文](./About_OpenSCA-zh_CN.md) 2 | 3 | # About 4 | > Manage Open Source Risks Through an Open Source Solution 5 | 6 | OpenSCA is the open source realization of SCA (Software Composition Analysis) technology. As the open source version of Xmirror SCA, it has been endowed with the core abilities of mixed-source application security detection. Aiming at guarding open source security, it is competent to dig out the hiding vulnerabilities and compliance risks in all components by dependency analysis, characteristic analysis, reference identification and compliance analysis. 7 | 8 | Unlike traditional commercial SCA tools, OpenSCA has offered an open source solution to the management of open source risks which is full of potential. Being both complete in ability and easy to use, it supports various scenarios including online/offline, IDE/CMD/SaaS, etc. while allows customized configuration such as local vulnerability databse and private repos. Generally speaking, OpenSCA is intended for outputting transparent component assets & risk list for companies, organizations and individual developers in a flexible way. 9 | 10 | Based on OpenSCA, we've built up a global community covering industries of telecom, internet, IoV, finance, energy and so on. We sincerely hope that our project can be a stage for communication and innovation of open source stakeholders. 11 | 12 | # Language & Package Manager 13 | 14 | | Language | Package Manager | File | 15 | | :--:| :--: | :-- | 16 | | Java | Maven | `pom.xml` | 17 | | | Gradle | `.gradle`, `.gradle.kts` | 18 | | JavaScripts | NPM | `package-lock.json`, `package.json`, `yarn.lock` | 19 | | PHP | Composer | `composer.json`, `composer.lock` | 20 | | Ruby | gem | `gemfile.lock` | 21 | | Golang | Go mod | `go.mod`, `go.sum` | 22 | | Python | Pip | `Pipfile`, `Pipfile.lock`, `setup.py`, `requirements.txt`(pipenv & internet needed), `requirements.in`(pipenv & internet needed) | 23 | | Rust | cargo | `Cargo.lock` | 24 | | Erlang | Rebar | `rebar.lock` | 25 | 26 | # Work Flow 27 | 28 | ![DetectionProcess](/resources/DetectionProcess.png) 29 | -------------------------------------------------------------------------------- /docs/Code_Standard-zh_CN.md: -------------------------------------------------------------------------------- 1 | # 代码规范 2 | 3 | ## 命名 4 | 5 | | 类型 | 格式 | 示例 | 6 | | ------ | ---------------------------------------------- | ------------------------ | 7 | | 常量 | 全大写 | `MAX_COUNT` | 8 | | 变量名 | 驼峰,可适当缩写 | `taskId, TaskId` | 9 | | 函数 | 驼峰 | `checkToken, CheckToken` | 10 | | 结构 | 驼峰 | `fileInfo, FileInfo` | 11 | | 接口 | 驼峰,一般`er`结尾 | `Analyzer` | 12 | | 文件名 | 全小写,下划线分割 | `db_test.go` | 13 | | 包名 | 全小写,尽可能使用简短的单词,避免下划线或驼峰 | `config` | 14 | 15 | ## 注释 16 | 17 | - 函数 使用单行注释(`//`),函数名开头。 18 | 19 | ```go 20 | // RunTask is run a task 21 | // taskId is task id 22 | func RunTask(taskId int64) 23 | ``` 24 | 25 | - 代码 无法直观了解逻辑的代码需要添加注释。 -------------------------------------------------------------------------------- /docs/Code_standard.md: -------------------------------------------------------------------------------- 1 | # Code Standard 2 | 3 | ## Naming 4 | 5 | | TYPE | FORMAT | EXAMPLE | 6 | | ------------ | ------------------------------------------------------------ | ------------------------ | 7 | | Constant | all in upper case | `MAX_COUNT` | 8 | | Variables | camel-case; proper abbreviation is acceptable | `taskId, TaskId` | 9 | | Function | camel-case | `checkToken, CheckToken` | 10 | | Structure | camel-case | `fileInfo, FileInfo` | 11 | | API | camel-case; end with `er` | `Analyzer` | 12 | | Doc name | all in lower case; use the underscore to show separation | `db_test.go` | 13 | | Package name | all in lower case; be brief and avoid using the underscore or camel-case if possible | `config` | 14 | 15 | ## Comments 16 | 17 | - Function: Please use single-line comments(`//`), starting with the name of the function. 18 | 19 | ```go 20 | // RunTask is run a task 21 | // taskId is task id 22 | func RunTask(taskId int64) 23 | ``` 24 | 25 | - Code: We recommend adding comments to your code so that we can grasp the underlying logic more effectively. -------------------------------------------------------------------------------- /docs/Contributing_Guideline-v1.0-zh_CN.md: -------------------------------------------------------------------------------- 1 | # 贡献指南(中文版) 2 | 3 | ## OpenSCA项目贡献指南 v1.0 4 | 5 | OpenSCA项目是由悬镜安全团队首发的开源的软件成分分析工具。身处紧锣密鼓的研发节奏及开源代码和软件广泛应用的大环境中,面对日益严峻的软件供应链安全问题,我们的愿景是用开源的方式做开源风险治理。欢迎朋友们成为OpenSCA社区的一份子,与我们共同建设OpenSCA项目的未来,探索充满无限可能的开源解决方案。 6 | 7 | 我们珍视一切形式的贡献,包括但不限于: 8 | 9 | - 对已有代码的检查 10 | - 说明文档及部署案例 11 | - 通过社群渠道及Issue板块参与社区讨论 12 | - 提升OpenSCA项目功能的切实努力 13 | - 有益项目长远发展的经验分享、使用指导、专题交流等 14 | 15 | 为了帮助大家更有效地参与到各个方面的贡献中来,我们拟定了一份贡献指南,并会参照项目的发展情况定期更新完善。这份指南包括以下几个部分: 16 | 17 | - 社区行为守则及贡献体系 18 | - 如何提出BUG 19 | - BUG类型划分 20 | - 贡献代码 21 | - 代码规范 22 | 23 | ### 社区行为守则及贡献体系 24 | 25 | OpenSCA社区旨在营造贡献者友好的氛围,开放、包容、尊重他人的环境需要大家共同努力去创造和维持。不恰当的行为将会受到警告甚至惩罚。 26 | 27 | #### **行为守则** 28 | 29 | OpenSCA社区关心每位社区成员的体验;为了共同营造良好的社区氛围,请您提前阅读我们的[行为守则](https://www.contributor-covenant.org/zh-cn/version/2/0/code_of_conduct/),并在参与时多多留意。 30 | 31 | #### 社区贡献体系 32 | 33 | OpenSCA社区尚处于起步阶段,由悬镜安全OpenSCA项目团队管理。随着社区的发展,我们欢迎并期待更多的朋友们为社区做出贡献,成为项目团队的一员,和我们一起维护社区系统的运转。 34 | 35 | 暂行的贡献体系初步分代码和积分两个部分;**代码方面**,通过PR向我们提交代码并被采纳后即可成为我们的Contributor,将可作为OpenSCA项目的成员参与到进一步的项目功能规划和落实、BUG判定、PR审核和Contributor认证中来。 36 | 37 | **积分方面**,除贡献代码之外,积极参与社区的BUG报告、技术讨论、使用分享也可获得积分,积攒的积分可以定期兑换奖品。积分评定和奖品兑换的详细规则可能会随不同的活动时段发生变化。目前的详细规则请参考“播种计划”第一期的设置。 38 | 39 | ### 如何提出BUG 40 | 41 | 如果您在部署或使用OpenSCA时发现了BUG,可以在Issue板块提出;如果您对我们已经上传的公开文档中的内容有疑惑,可以直接发送邮件到 [Opensca@anpro-tech.com](mailto:Opensca@anpro-tech.com),也可通过OpenSCA社区微信公众号、微信群、QQ群等渠道联系我们。 42 | 43 | 在提出BUG之前,您需要先确认您部署的是OpenSCA的最新版本,然后浏览Issue板块,确认该BUG没有被其他人提出过。以上步骤确认无误后,您可以在Issue板块提出您发现的BUG,等待项目成员或其他社区成员参与讨论或进行处理;也可以提一个PR对它进行修复,然后等待项目成员的审核。 44 | 45 | *漏洞问题不适合公开提出或进行讨论,如果您发现了OpenSCA的安全漏洞,请发送相关信息到[Opensca@anpro-tech.com](mailto:Opensca@anpro-tech.com)与我们取得联系。 46 | 47 | ### BUG类型划分 48 | 49 | 不同的BUG对用户使用OpenSCA的影响是不同的。根据这种差别,我们暂时把BUG分为以下三类: 50 | 51 | **主要BUG**:影响主要功能(Java、PHP等语言的组件解析、漏洞识别) 52 | 53 | **一般BUG**:不影响功能,只影响使用体验 54 | 55 | **伪BUG**:由于操作或使用非最新版本导致的使用问题 56 | 57 | 主要BUG需要优先修复,项目成员会积极跟进;如果您有修复方案,也欢迎您提出PR,等待项目成员审核确认。项目成员会通过对某些部分的代码进行微调来处理一般BUG,对于伪BUG,我们也会及时进行回复,帮助您找出您操作中的疏漏,以便您更好地使用OpenSCA。 58 | 59 | ### 贡献代码 60 | 61 | OpenSCA项目需要通过您的代码贡献实现维护、完善和功能拓展;贡献代码的主要形式是提交PR。 62 | 63 | 您可以为BUG修复和功能提升提出PR,项目成员会对您的PR进行审核。为了提高审核的效率,我们希望您参照项目的代码规范来组织您的PR,每个PR专注一个问题,并尽量提升您代码的可读性,如果能附上一些说明更佳。 64 | 65 | **如果是关于BUG修复的PR**,在满足规范的基础上,审核标准将主要关注代码质量; 66 | 67 | **如果是关于功能提升的PR**,在满足规范的基础上,审核标准将围绕代码质量、功能创新性、功能实现度和算法潜力这几个方面。 68 | 69 | 项目成员审核完毕并决定采纳您的代码之后,我们会与您取得联系,并邀请您签署贡献者许可协议(CLA),将您的代码所有权授予OpenSCA项目。 70 | 71 | 协议签署完毕后,您的代码贡献会成为OpenSCA项目的一部分,您也会成为我们的Contributor,将可以作为项目成员参与进一步的项目功能规划和落实、BUG判定、PR审核和Contributor认证等过程。 72 | 73 | 74 | --- 75 | 76 | 77 | 我们感激每位贡献者对OpenSCA做出的贡献。 78 | 79 | 再次感谢您对OpenSCA的关注和对我们理念的认可。 80 | 81 | 贡献指南的英文版本请见 [Contributing Guideline-en v.1.0](./Contributing_Guideline-v1.0.md)。 82 | -------------------------------------------------------------------------------- /docs/Quick_Start-zh_CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quick Start 3 | author: Cyber Chen 4 | date: 2023-11-30T11:01:15+08:00 5 | --- 6 | 7 | [返回目录](/docs/README-zh-CN.md) | [English](./Quick_Start.md) 8 | 9 | # 快速开始 10 | 11 | ## 视频教程 12 | 13 | 16 | 17 | ## 传统方式 18 | 19 | ### 下载安装 20 | 21 | - 方式一:使用一键安装脚本 22 | - Mac/Linux 用户可通过以下命令下载并安装 23 | ```shell 24 | curl -sSL https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/master/scripts/install.sh | sh 25 | 26 | # 如果在下载中遇到网络问题,可尝试使用以下命令 27 | curl -sSL https://gitee.com/XmirrorSecurity/OpenSCA-cli/raw/master/scripts/install.sh | sh -s -- gitee 28 | ``` 29 | - Windows 用户可通过以下命令下载并安装(Powershell) 30 | ```powershell 31 | iex "&{$(irm https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/master/scripts/install.ps1)}" 32 | 33 | # 如果在下载中遇到网络问题,可尝试使用以下命令 34 | iex "&{$(irm https://gitee.com/XmirrorSecurity/OpenSCA-cli/raw/master/scripts/install.ps1)} gitee" 35 | ``` 36 | 37 | - 方式二:通过包管理器安装 38 | 39 | - Windows [Winget](https://github.com/microsoft/winget-cli) 安装 40 | ```shell 41 | winget install opensca-cli 42 | ``` 43 | - Windows [Scoop](https://scoop.sh/) 安装 44 | ```shell 45 | scoop bucket add extras 46 | scoop install extras/opensca-cli 47 | ``` 48 | - Mac/Linux 用户可通过 [Homebrew](https://brew.sh/) 安装 49 | ```shell 50 | brew install opensca-cli 51 | ``` 52 | 53 | - 方式三:从 [GitHub](https://github.com/XmirrorSecurity/OpenSCA-cli/releases/latest) 或 [Gitee](https://gitee.com/XmirrorSecurity/OpenSCA-cli/releases/latest) 下载对应系统架构的可执行程序压缩包,并解压到本地任意目录下 54 | 55 | ### 开始检测 56 | 57 | **检测指定目录的依赖关系** 58 | 59 | ```shell 60 | opensca-cli -path {替换为要检测的目录} 61 | ``` 62 | 63 | **检测指定目录的依赖关系,并通过云端数据库获取许可证以及漏洞信息** 64 | 65 | > 您需要先[注册](https://opensca.xmirror.cn/register)并获取 token 66 | 67 | ```shell 68 | opensca-cli -path {替换为要检测的目录} -token {替换为您的 token} 69 | ``` 70 | 71 | ## Docker 72 | 73 | **检测指定目录的依赖关系** 74 | 75 | ```shell 76 | docker run -ti --rm -v {替换为要检测的目录}:/src opensca/opensca-cli:latest 77 | ``` 78 | 79 | **检测指定目录的依赖关系,并通过云端数据库获取许可证以及漏洞信息** 80 | 81 | > 您需要先[注册](https://opensca.xmirror.cn/register)并获取 token 82 | 83 | ```shell 84 | docker run -ti --rm -v {替换为要检测的目录}:/src opensca/opensca-cli:latest -token {替换为您的 token} 85 | ``` -------------------------------------------------------------------------------- /docs/Quick_Start.md: -------------------------------------------------------------------------------- 1 | [Go Back](/docs/README.md) | [中文](./Quick_Start-zh_CN.md) 2 | 3 | # Quick Start 4 | 5 | ## Video 6 | 7 | 10 | 11 | ## Traditional Method 12 | 13 | ### Download & Deploy 14 | 15 | - Method No.1:one-click installation 16 | - For Mac/Linux Users 17 | ```shell 18 | curl -sSL https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/master/scripts/install.sh | sh 19 | 20 | # Try this when internet connection fails 21 | curl -sSL https://gitee.com/XmirrorSecurity/OpenSCA-cli/raw/master/scripts/install.sh | sh -s -- gitee 22 | ``` 23 | - Method No.2:via `Homebrew` (Mac/Linux) 24 | ```shell 25 | brew install opensca-cli 26 | ``` 27 | - Method No.3:Download executable program and decompress from [GitHub](https://github.com/XmirrorSecurity/OpenSCA-cli/releases/latest) or [Gitee](https://gitee.com/XmirrorSecurity/OpenSCA-cli/releases/latest) 28 | 29 | ### Use OpenSCA 30 | 31 | **Report dependencies of the given directory** 32 | 33 | ```shell 34 | opensca-cli -path ${project_path} 35 | ``` 36 | 37 | **Report dependencies and get info of vuls & licenses from cloud knowledge base** 38 | 39 | > Register first [register](https://opensca.xmirror.cn/register) and get token 40 | 41 | ```shell 42 | opensca-cli -path ${project_path} -token ${token} 43 | ``` 44 | 45 | ## Docker 46 | 47 | **Report dependencies of the given directory** 48 | 49 | ```shell 50 | docker run -ti --rm -v ${project_path}:/src opensca/opensca-cli:latest 51 | ``` 52 | 53 | **Report dependencies and get info of vuls & licenses from cloud knowledge base** 54 | 55 | > Register first [register](https://opensca.xmirror.cn/register) and get token 56 | 57 | ```shell 58 | docker run -ti --rm -v ${project_path}:/src opensca/opensca-cli:latest -token ${token} 59 | ``` -------------------------------------------------------------------------------- /docs/README-zh-CN.md: -------------------------------------------------------------------------------- 1 | [English](./README.md) | [简体中文](./README-zh-CN.md) 2 | 3 | # 目录 4 | 5 | - [关于 OpenSCA](./About_OpenSCA-zh_CN.md) 6 | - [快速开始](./Quick_Start-zh_CN.md) 7 | - [用户指南](./User_Guide/) 8 | - [安装](./User_Guide/Installation-zh_CN.md) 9 | - [扫描](./User_Guide/Scanning/) 10 | - [依赖分析](./User_Guide/Scanning/Dependency_Analysis-zh_CN.md) 11 | - [连接漏洞库](./User_Guide/Scanning/Vulnerability_Analysis-zh_CN.md) 12 | - [查看结果](./User_Guide/Viewing_Results-zh_CN.md) 13 | - [生成报告](./User_Guide/Generating_Reports/) 14 | - [SBOM](./User_Guide/Generating_Reports/SBOM-zh_CN.md) 15 | - [检测报告](./User_Guide/Generating_Reports/Reports-zh_CN.md) 16 | - [参数说明](./User_Guide/Parameter_Explanations-zh_CN.md) 17 | - [Docker](./User_Guide/Docker-zh_CN.md) 18 | - [集成配置](./Integrations/) 19 | - [IDE 插件](./Integrations/IDE_Plugins-zh_CN.md) 20 | - [CI/CD](./Integrations/CICD-zh_CN.md) 21 | - 开发者指南 22 | - [贡献指南](./Contributing_Guideline-v1.0-zh_CN.md) 23 | - [代码规范](./Code_Standards-zh_CN.md) 24 | - [常见问题](./Troubleshooting-zh_CN.md) -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | [English](./README.md) | [简体中文](./README-zh-CN.md) 2 | 3 | # Table of Contents 4 | 5 | - [About OpenSCA](./About_OpenSCA.md) 6 | - [Quick Start](./Quick_Start.md) 7 | - [User Guide](./User_Guide/) 8 | - [Installation](./User_Guide/Installation.md) 9 | - [Scanning](./User_Guide/Scanning/) 10 | - [Dependency Analysis](./User_Guide/Scanning/Dependency_Analysis.md) 11 | - [Connecting to Vulnerability Databases](./User_Guide/Scanning/Vulnerability_Analysis.md) 12 | - [Viewing Results](./User_Guide/Viewing_Results.md) 13 | - [Generating Reports](./User_Guide/Generating_Reports/) 14 | - [Software Bill of Materials (SBOM)](./User_Guide/Generating_Reports/SBOM.md) 15 | - [Detection Reports](./User_Guide/Generating_Reports/Reports.md) 16 | - [Parameter Explanations](./User_Guide/Parameter_Explanations.md) 17 | - [Docker](./User_Guide/Docker.md) 18 | - [Integrations](./Integrations/) 19 | - [IDE Plugins](./Integrations/IDE_Plugins.md) 20 | - [CI/CD](./Integrations/CICD.md) 21 | - Developer Guide 22 | - [Contribution Guide](./Contributing_Guideline-v1.0.md) 23 | - [Code Standard](./Code_Standard.md) 24 | - [Troubleshooting](./Troubleshooting.md) -------------------------------------------------------------------------------- /docs/Troubleshooting-zh_CN.md: -------------------------------------------------------------------------------- 1 | [返回目录](/docs/README-zh-CN.md) / [English](./Troubleshooting.md) -------------------------------------------------------------------------------- /docs/Troubleshooting.md: -------------------------------------------------------------------------------- 1 | [Go Back](/docs/README.md) | [中文](./Troubleshooting-zh_CN.md) -------------------------------------------------------------------------------- /docs/User_Guide/Configuration-and-Parameters.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/docs/User_Guide/Configuration-and-Parameters.md -------------------------------------------------------------------------------- /docs/User_Guide/Docker-zh_CN.md: -------------------------------------------------------------------------------- 1 | [返回目录](/docs/README-zh-CN.md) / [English](./Docker.md) -------------------------------------------------------------------------------- /docs/User_Guide/Docker.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/docs/User_Guide/Docker.md -------------------------------------------------------------------------------- /docs/User_Guide/Generating_Reports/Reports-zh_CN.md: -------------------------------------------------------------------------------- 1 | [返回目录](/docs/README-zh-CN.md) / [English](./Reports.md) 2 | 3 | - [生成报告](#生成报告) 4 | - [使用 OpenSCA-cli 生成报告](#使用-opensca-cli-生成报告) 5 | - [基本命令](#基本命令) 6 | - [示例](#示例) 7 | - [使用 json2excel 脚本生成 Excel 格式报告](#使用-json2excel-脚本生成-excel-格式报告) 8 | - [需求](#需求) 9 | - [安装脚本](#安装脚本) 10 | - [使用方法](#使用方法) 11 | 12 | # 生成报告 13 | 14 | OpenSCA 支持生成多种格式的报告, 包括 JSON(`.json`), XML(`.xml`), HTML(`.html`), SQLite(`.sqlite`), CSV(`.csv`), SARIF(`.sarif`) 15 | 16 | > CSV 格式报告仅包含依赖关系, 不包含漏洞信息. 若需要包含漏洞信息的 Excel 格式报告, 可通过 [json2excel](https://github.com/XmirrorSecurity/OpenSCA-cli/blob/master/scripts/json2excel.py) 脚本, 将 JSON 格式报告转换为 Excel 格式报告. 17 | 18 | 此外, OpenSCA 提供 SaaS 服务, 同步扫描结果后, 可以在 [OpenSCA SaaS Console](https://opensca.xmirror.cn/console) 查看和下载报告. 19 | 20 | # 使用 OpenSCA-cli 生成报告 21 | 22 | ## 基本命令 23 | 24 | OpenSCA-cli 使用 `-out` 参数指定报告输出路径, 使用后缀名指定报告格式. 25 | 26 | ```shell 27 | opensca-cli -path {项目路径} -out {报告路径}.{报告格式} 28 | ``` 29 | 30 | `-out` 参数支持指定多个报告路径, 使用半角逗号(`,`)分隔. 31 | 32 | ```shell 33 | opensca-cli -path {项目路径} -out {报告路径1}.{报告格式1},{报告路径2}.{报告格式2} 34 | ``` 35 | 36 | ## 示例 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 55 | 62 | 69 | 76 | 83 | 90 | 91 |
生成 JSON 格式报告生成 XML 格式报告生成 HTML 格式报告生成 SQLite 格式报告生成 CSV 格式报告生成 SARIF 格式报告
49 | 50 | ```shell 51 | opensca-cli -path ~/workscapce/myproject -out ~/workscapce/myproject/report.json 52 | ``` 53 | 54 | 56 | 57 | ```shell 58 | opensca-cli -path ~/workscapce/myproject -out ~/workscapce/myproject/report.xml 59 | ``` 60 | 61 | 63 | 64 | ```shell 65 | opensca-cli -path ~/workscapce/myproject -out ~/workscapce/myproject/report.html 66 | ``` 67 | 68 | 70 | 71 | ```shell 72 | opensca-cli -path ~/workscapce/myproject -out ~/workscapce/myproject/report.sqlite 73 | ``` 74 | 75 | 77 | 78 | ```shell 79 | opensca-cli -path ~/workscapce/myproject -out ~/workscapce/myproject/report.csv 80 | ``` 81 | 82 | 84 | 85 | ```shell 86 | opensca-cli -path ~/workscapce/myproject -out ~/workscapce/myproject/report.sarif 87 | ``` 88 | 89 |
92 | 93 | # 使用 json2excel 脚本生成 Excel 格式报告 94 | 95 | ## 需求 96 | 97 | - Python 3.6+ 98 | - pandas 99 | 100 | ## 安装脚本 101 | 102 | ```shell 103 | # 下载脚本 104 | wget https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/master/scripts/json2excel.py 105 | 106 | # 安装依赖 107 | pip install pandas 108 | ``` 109 | 110 | ## 使用方法 111 | 112 | **修改 json2excel.py** 113 | 114 | 修改 json2excel.py 文件中的以下内容: 115 | 116 | - 将 "result.json" 修改为你的 JSON 格式报告路径 117 | - 将 "result.xlsx" 修改为你的 Excel 格式报告路径 118 | 119 | ```python 120 | # ... 121 | if __name__ == "__main__": 122 | json2excel("result.json", "result.xlsx") 123 | ``` 124 | 125 | **运行脚本** 126 | 127 | ```shell 128 | python json2excel.py 129 | ``` 130 | -------------------------------------------------------------------------------- /docs/User_Guide/Generating_Reports/Reports.md: -------------------------------------------------------------------------------- 1 | [Go Back](/docs/README.md) | [简体中文](./Reports-zh_CN.md) 2 | -------------------------------------------------------------------------------- /docs/User_Guide/Generating_Reports/SBOM-zh_CN.md: -------------------------------------------------------------------------------- 1 | [返回目录](/docs/README-zh-CN.md) / [English](./SBOM.md) 2 | 3 | - [SBOM 清单](#sbom-清单) 4 | - [生成 SBOM 清单](#生成-sbom-清单) 5 | - [基本命令](#基本命令) 6 | - [示例](#示例) 7 | - [转换 SBOM 清单](#转换-sbom-清单) 8 | - [基本命令](#基本命令-1) 9 | - [示例](#示例-1) 10 | 11 | 12 | # SBOM 清单 13 | 14 | OpenSCA 支持生成多种格式的 SBOM 清单, 包括 [DSDX](https://opensca.xmirror.cn/resources/particulars?id=133), [SPDX](https://spdx.dev/), [CycloneDX](https://cyclonedx.org/) 和 [SWID](https://csrc.nist.gov/projects/Software-Identification-SWID). 15 | 16 | 除了生成 SBOM, OpenSCA 还支持将 SBOM 作为输入, 生成漏洞报告或转换为其他格式的 SBOM 清单. 17 | 18 | 此外, OpenSCA 提供 SaaS 服务, 同步扫描结果后, 可以在 [OpenSCA SaaS Console](https://opensca.xmirror.cn/console) 查看和下载 SBOM 清单. 19 | 20 | 支持的 SBOM 格式: 21 | 22 | | SBOM 清单 | 文件后缀 | 23 | | ----------- | -------------------------------- | 24 | | `DSDX` | `.dsdx` `.dsdx.json` `.dsdx.xml` | 25 | | `SPDX` | `.spdx` `.spdx.json` `.spdx.xml` | 26 | | `CycloneDX` | `.cdx.json` `.cdx.xml` | 27 | | `SWID` | `.swid.json` `.swid.xml` | 28 | 29 | # 生成 SBOM 清单 30 | 31 | ## 基本命令 32 | 33 | OpenSCA-cli 使用 `-out` 参数指定 SBOM 清单输出路径, 使用后缀名指定 SBOM 清单格式. 34 | 35 | ```shell 36 | opensca-cli -path {项目路径} -out {SBOM路径}.{SBOM格式} 37 | ``` 38 | 39 | `-out` 参数支持指定多个 SBOM 路径, 使用半角逗号(`,`)分隔. 40 | 41 | ```shell 42 | opensca-cli -path {项目路径} -out {SBOM路径1}.{SBOM格式1},{SBOM路径2}.{SBOM格式2} 43 | ``` 44 | 45 | 也可以同时生成 SBOM 清单和漏洞报告. 46 | 47 | ```shell 48 | opensca-cli -path {项目路径} -out {SBOM路径}.{SBOM格式},{报告路径}.{报告格式} 49 | ``` 50 | 51 | ## 示例 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 68 | 75 | 82 | 89 | 90 |
生成 JSON 格式 DSDX 清单生成 JSON 格式 SPDX 清单生成 JSON 格式 CycloneDX 清单生成 JSON 格式 SWID 清单
62 | 63 | ```shell 64 | opensca-cli -path ~/workscapce/myproject -out ~/workscapce/myproject/sbom.dsdx.json 65 | ``` 66 | 67 | 69 | 70 | ```shell 71 | opensca-cli -path ~/workscapce/myproject -out ~/workscapce/myproject/sbom.spdx.json 72 | ``` 73 | 74 | 76 | 77 | ```shell 78 | opensca-cli -path ~/workscapce/myproject -out ~/workscapce/myproject/sbom.cdx.json 79 | ``` 80 | 81 | 83 | 84 | ```shell 85 | opensca-cli -path ~/workscapce/myproject -out ~/workscapce/myproject/sbom.swid.json 86 | ``` 87 | 88 |
91 | 92 | # 转换 SBOM 清单 93 | 94 | ## 基本命令 95 | 96 | ```shell 97 | opensca-cli -path {SBOM路径}.{SBOM格式} -out {SBOM路径}.{SBOM格式} 98 | ``` 99 | 100 | > 注意: SPDX 和 SWID 格式的 SBOM 清单不包含组件语言信息或包管理器坐标, 在转换时可能会丢失部分信息. 101 | 102 | ## 示例 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 118 | 125 | 132 | 133 |
DSDX 转 SPDXDSDX 转 CycloneDXDSDX 转 SWID
112 | 113 | ```shell 114 | opensca-cli -path ~/workscapce/myproject/sbom.dsdx.json -out ~/workscapce/myproject/sbom.spdx.json 115 | ``` 116 | 117 | 119 | 120 | ```shell 121 | opensca-cli -path ~/workscapce/myproject/sbom.dsdx.json -out ~/workscapce/myproject/sbom.cdx.json 122 | ``` 123 | 124 | 126 | 127 | ```shell 128 | opensca-cli -path ~/workscapce/myproject/sbom.dsdx.json -out ~/workscapce/myproject/sbom.swid.json 129 | ``` 130 | 131 |
134 | -------------------------------------------------------------------------------- /docs/User_Guide/Generating_Reports/SBOM.md: -------------------------------------------------------------------------------- 1 | [Go Back](/docs/README.md) | [简体中文](./SBOM-zh_CN.md) 2 | -------------------------------------------------------------------------------- /docs/User_Guide/Installation-zh_CN.md: -------------------------------------------------------------------------------- 1 | [返回目录](/docs/README-zh-CN.md) | [English](./Installation.md) 2 | 3 | # 一键安装 4 | 5 | - Windows(需要 PowerShell) 6 | ```powershell 7 | iex "&{$(irm https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/master/scripts/install.ps1)}" 8 | 9 | # 如果在下载中遇到网络问题,可尝试使用以下命令 10 | iex "&{$(irm https://gitee.com/XmirrorSecurity/OpenSCA-cli/raw/master/scripts/install.ps1)} gitee" 11 | ``` 12 | - Linux/MacOS 13 | ```shell 14 | curl -sSL https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/master/scripts/install.sh | sh 15 | 16 | # 如果在下载中遇到网络问题,可尝试使用以下命令 17 | curl -sSL https://gitee.com/XmirrorSecurity/OpenSCA-cli/raw/master/scripts/install.sh | sh -s -- gitee 18 | ``` 19 | 20 | # 使用包管理器安装 21 | 22 | - Windows 通过 [Winget](https://github.com/microsoft/winget-cli) 安装 23 | ```shell 24 | winget install opensca-cli 25 | ``` 26 | - Windows 通过 [Scoop](https://scoop.sh/) 安装 27 | ```shell 28 | scoop bucket add extras 29 | scoop install extras/opensca-cli 30 | ``` 31 | - MacOS/Linux 通过 [Homebrew](https://brew.sh/) 安装 32 | ```shell 33 | brew install opensca-cli 34 | ``` 35 | 36 | # 手动安装 37 | 38 | 从 [Github Release](https://github.com/XmirrorSecurity/OpenSCA-cli/releases) 或 [Gitee Release](https://gitee.com/XmirrorSecurity/OpenSCA-cli/releases/) 下载对应系统和处理器架构的压缩包,解压到任意目录即可使用。 39 | 40 | # 从源码构建 41 | 42 | 依赖 Go 1.18+ 43 | 44 | - 克隆此仓库 45 | ```shell 46 | git clone https://github.com/XmirrorSecurity/OpenSCA-cli.git opensca && cd opensca 47 | ``` 48 | - 编译 49 | ```shell 50 | go build 51 | ``` 52 | 使用此命令会生成当前系统和处理器架构的可执行文件,如需生成其他系统架构可配置环境变量后编译 53 | - 禁用CGO_ENABLED CGO_ENABLED=0 54 | - 指定操作系统 GOOS=${OS} \\ darwin,liunx,windows 55 | - 指定体系架构 GOARCH=${arch} \\ amd64,arm64 56 | -------------------------------------------------------------------------------- /docs/User_Guide/Installation.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/docs/User_Guide/Installation.md -------------------------------------------------------------------------------- /docs/User_Guide/Scanning/Dependency_Analysis-zh_CN.md: -------------------------------------------------------------------------------- 1 | [返回目录](/docs/README-zh-CN.md) / [English](./Dependency_Analysis.md) 2 | 3 | - [依赖分析](#依赖分析) 4 | - [使用 OpenSCA-cli 进行依赖分析](#使用-opensca-cli-进行依赖分析) 5 | - [分析本地项目目录](#分析本地项目目录) 6 | - [基本命令](#基本命令) 7 | - [示例](#示例) 8 | - [分析依赖特征文件](#分析依赖特征文件) 9 | - [基本命令](#基本命令-1) 10 | - [示例](#示例-1) 11 | - [分析远程项目](#分析远程项目) 12 | - [基本命令](#基本命令-2) 13 | - [示例](#示例-2) 14 | - [分析 SBOM](#分析-sbom) 15 | 16 | # 依赖分析 17 | 18 | OpenSCA 通过扫描项目依赖特征文件(动态或静态解析),生成项目依赖关系, 帮助用户了解项目的依赖关系,以便更好地管理项目。 19 | 20 | 所谓动态解析是指调用包管理器,获取项目依赖关系;静态解析是通过模拟包管理器的行为,获取项目依赖关系。动态解析的结果通常更加准确,但依赖包管理器;静态解析在某些情况下可能与动态解析结果有出入,但是不需要安装包管理器。 21 | 22 | > 若未指定漏洞数据库,则仅分析依赖关系,不进行漏洞分析。 23 | 24 | 支持的语言和包管理器详见 [关于 OpenSCA](/docs/About_OpenSCA-zh_CN.md) 25 | 26 | # 使用 OpenSCA-cli 进行依赖分析 27 | 28 | ## 分析本地项目目录 29 | 30 | ### 基本命令 31 | 32 | ```shell 33 | opensca-cli -path {项目路径} -out {报告名称}.dsdx,{报告名称}.spdx 34 | ``` 35 | 36 | ### 示例 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 50 | 57 | 58 |
分析 `~/workspace/myproject` 目录分析 `~/workspace/myproject` 目录并生成报告
45 | 46 | ```shell 47 | opensca-cli -path ~/workspace/myproject 48 | ``` 49 | 51 | 52 | ```shell 53 | opensca-cli -path ~/workspace/myproject -out ~/workspace/myproject/report.html 54 | ``` 55 | 56 |
59 | 60 | ## 分析依赖特征文件 61 | 62 | ### 基本命令 63 | 64 | ```shell 65 | opensca-cli -path {依赖特征文件路径} 66 | ``` 67 | 68 | ### 示例 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 83 | 90 | 91 |
分析 `~/workspace/myproject/package.json` 文件分析 `~/workspace/myproject/package.json` 文件并生成报告
77 | 78 | ```shell 79 | opensca-cli -path ~/workspace/myproject/package.json 80 | ``` 81 | 82 | 84 | 85 | ```shell 86 | opensca-cli -path ~/workspace/myproject/package.json -out ~/workspace/myproject/report.html 87 | ``` 88 | 89 |
92 | 93 | ## 分析远程项目 94 | 95 | ### 基本命令 96 | 97 | ```shell 98 | opensca-cli -path {项目地址} 99 | ``` 100 | 101 | ### 示例 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 118 | 125 | 132 | 139 | 140 |
分析 ftp 目录分析 ftp 特征文件分析 http(s) 目录分析 http(s) 特征文件
112 | 113 | ```shell 114 | opensca-cli -path ftp://example.com/project 115 | ``` 116 | 117 | 119 | 120 | ```shell 121 | opensca-cli -path ftp://example.com/project/package.json 122 | ``` 123 | 124 | 126 | 127 | ```shell 128 | opensca-cli -path https://example.com/project 129 | ``` 130 | 131 | 133 | 134 | ```shell 135 | opensca-cli -path https://example.com/project/package.json 136 | ``` 137 | 138 |
141 | 142 | ## 分析 SBOM 143 | 144 | OpenSCA 支持将 SBOM 文件作为输入,进行依赖分析, 详见 [SBOM](/docs/User_Guide/Generating_Reports/SBOM-zh_CN.md) 145 | 146 | 147 | -------------------------------------------------------------------------------- /docs/User_Guide/Scanning/Dependency_Analysis.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/docs/User_Guide/Scanning/Dependency_Analysis.md -------------------------------------------------------------------------------- /docs/User_Guide/Scanning/Vulnerability_Analysis.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/docs/User_Guide/Scanning/Vulnerability_Analysis.md -------------------------------------------------------------------------------- /docs/User_Guide/Viewing_Results-zh_CN.md: -------------------------------------------------------------------------------- 1 | [返回目录](/docs/README-zh-CN.md) / [English](./Viewing_Results.md) -------------------------------------------------------------------------------- /docs/User_Guide/Viewing_Results.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/docs/User_Guide/Viewing_Results.md -------------------------------------------------------------------------------- /example/dep/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 5 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 6 | ) 7 | 8 | func main() { 9 | 10 | // A->C->B 11 | // A->B 12 | A := &model.DepGraph{Name: "A"} 13 | B := &model.DepGraph{Name: "B"} 14 | C := &model.DepGraph{Name: "C"} 15 | A.AppendChild(C) 16 | A.AppendChild(B) 17 | C.AppendChild(B) 18 | 19 | logs.Info("foreach node") 20 | A.ForEachNode(func(p, n *model.DepGraph) bool { 21 | if p != nil { 22 | logs.Infof("%s->%s", p.Name, n.Name) 23 | } 24 | return true 25 | }) 26 | 27 | logs.Info("foreach path") 28 | A.ForEachPath(func(p, n *model.DepGraph) bool { 29 | if p != nil { 30 | logs.Infof("%s->%s", p.Name, n.Name) 31 | } 32 | return true 33 | }) 34 | 35 | logs.Infof("dep tree foreach path, sorted by addition:\n%s", A.Tree(true, false)) 36 | logs.Infof("dep tree foreach node, sorted by addition:\n%s", A.Tree(false, false)) 37 | logs.Infof("dep tree foreach path, sorted by name:\n%s", A.Tree(true, true)) 38 | logs.Infof("dep tree foreach node, sorted by name:\n%s", A.Tree(false, true)) 39 | 40 | A.Build(false, model.Lan_Java) 41 | logs.Infof("build by bfs, foreach path:\n%s", A.Tree(true, false)) 42 | logs.Infof("build by bfs, foreach node:\n%s", A.Tree(false, false)) 43 | 44 | // clear path 45 | A.ForEachNode(func(p, n *model.DepGraph) bool { n.Language = model.Lan_None; n.Path = ""; return true }) 46 | 47 | A.Build(true, model.Lan_Golang) 48 | logs.Infof("build by dfs, foreach path:\n%s", A.Tree(true, false)) 49 | logs.Infof("build by dfs, foreach node:\n%s", A.Tree(false, false)) 50 | 51 | } 52 | -------------------------------------------------------------------------------- /example/file/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 7 | ) 8 | 9 | func main() { 10 | file := model.NewFile("./test_file.txt", "") 11 | lineFunc := func(line string) { fmt.Println(line) } 12 | fmt.Println("read file by line:") 13 | file.ReadLine(lineFunc) 14 | fmt.Println("\nread file by line, no comment c type:") 15 | file.ReadLineNoComment(model.CTypeComment, lineFunc) 16 | fmt.Println("\nread file by line, no comment python type:") 17 | file.ReadLineNoComment(model.PythonTypeComment, lineFunc) 18 | } 19 | -------------------------------------------------------------------------------- /example/file/test_file.txt: -------------------------------------------------------------------------------- 1 | c text scope: 2 | text-c1 // comment-c1 3 | text-c2 /* comment-c2 4 | comment-c3 */ text-c3 5 | text-c4-1 /* comment-c4 */ text-c4-2 6 | text-c5-1 /* comment-c5-1 */ text-c5-2 /* comment-c5-2 */ text-c5-3 7 | python text scope: 8 | text-py1 # comment-py1 9 | text-py2 ''' comment-py2 10 | comment-py3 ''' text-py3 11 | text-py4-1 ''' comment-py4 ''' text-py4-2 12 | text-py5-1 ''' comment-py5-1 ''' text-py5-2 ''' comment-py5-2 ''' text-py5-3 -------------------------------------------------------------------------------- /example/go.mod: -------------------------------------------------------------------------------- 1 | module opensca-example 2 | 3 | go 1.20 4 | 5 | require github.com/xmirrorsecurity/opensca-cli/v3 v3.0.2 6 | 7 | // debug 8 | replace github.com/xmirrorsecurity/opensca-cli/v3 => ../ 9 | 10 | require ( 11 | github.com/BurntSushi/toml v1.3.2 // indirect 12 | github.com/CycloneDX/cyclonedx-go v0.7.2 // indirect 13 | github.com/Masterminds/semver/v3 v3.2.1 // indirect 14 | github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/dustin/go-humanize v1.0.1 // indirect 17 | github.com/fxamacker/cbor/v2 v2.5.0 // indirect 18 | github.com/gdamore/encoding v1.0.0 // indirect 19 | github.com/gdamore/tcell/v2 v2.6.0 // indirect 20 | github.com/glebarez/go-sqlite v1.21.2 // indirect 21 | github.com/go-sql-driver/mysql v1.7.1 // indirect 22 | github.com/google/uuid v1.5.0 // indirect 23 | github.com/hashicorp/errwrap v1.1.0 // indirect 24 | github.com/hashicorp/go-multierror v1.1.1 // indirect 25 | github.com/jinzhu/inflection v1.0.0 // indirect 26 | github.com/jinzhu/now v1.1.5 // indirect 27 | github.com/jlaffaye/ftp v0.2.0 // indirect 28 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 29 | github.com/mattn/go-isatty v0.0.20 // indirect 30 | github.com/mattn/go-runewidth v0.0.15 // indirect 31 | github.com/mattn/go-sqlite3 v1.14.18 // indirect 32 | github.com/nwaples/rardecode v1.1.3 // indirect 33 | github.com/pkg/errors v0.9.1 // indirect 34 | github.com/pmezard/go-difflib v1.0.0 // indirect 35 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 36 | github.com/rivo/tview v0.0.0-20231126152417-33a1d271f2b6 // indirect 37 | github.com/rivo/uniseg v0.4.4 // indirect 38 | github.com/stretchr/testify v1.8.4 // indirect 39 | github.com/titanous/json5 v1.0.0 // indirect 40 | github.com/veraison/swid v1.1.0 // indirect 41 | github.com/x448/float16 v0.8.4 // indirect 42 | golang.org/x/sys v0.14.0 // indirect 43 | golang.org/x/term v0.14.0 // indirect 44 | golang.org/x/text v0.14.0 // indirect 45 | gopkg.in/yaml.v3 v3.0.1 // indirect 46 | gorm.io/driver/mysql v1.5.2 // indirect 47 | gorm.io/driver/sqlite v1.5.4 // indirect 48 | gorm.io/gorm v1.25.5 // indirect 49 | modernc.org/libc v1.34.11 // indirect 50 | modernc.org/mathutil v1.6.0 // indirect 51 | modernc.org/memory v1.7.2 // indirect 52 | modernc.org/sqlite v1.27.0 // indirect 53 | ) 54 | -------------------------------------------------------------------------------- /example/java/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "io/fs" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/common" 11 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 12 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 13 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/java" 14 | ) 15 | 16 | func init() { 17 | 18 | // register default maven repository 19 | java.RegisterMavenRepo( 20 | common.RepoConfig{Url: "https://maven.aliyun.com/repository/public"}, 21 | common.RepoConfig{Url: "https://repo1.maven.org/maven2"}, 22 | // custom maven repostiory like nexus 23 | common.RepoConfig{Url: "", Username: "", Password: ""}, 24 | ) 25 | 26 | // register maven component repository origin 27 | java.RegisterMavenOrigin(func(groupId, artifactId, version string) *java.Pom { 28 | var pom *java.Pom 29 | java.DownloadPomFromRepo(java.PomDependency{GroupId: groupId, ArtifactId: artifactId, Version: version}, func(r io.Reader) { pom = java.ReadPom(r) }) 30 | return pom 31 | }) 32 | } 33 | 34 | func main() { 35 | 36 | projectDir := "../../test/java/9" 37 | 38 | // find pom files 39 | var poms []*java.Pom 40 | filepath.WalkDir(projectDir, func(path string, d fs.DirEntry, err error) error { 41 | if !strings.HasSuffix(path, "pom.xml") { 42 | return nil 43 | } 44 | pomfile := model.NewFile(path, strings.TrimPrefix(path, projectDir)) 45 | pomfile.OpenReader(func(reader io.Reader) { 46 | pom := java.ReadPom(reader) 47 | pom.File = pomfile 48 | poms = append(poms, pom) 49 | }) 50 | return nil 51 | }) 52 | 53 | // pure static parse pom 54 | java.ParsePoms(context.TODO(), poms, nil, func(pom *java.Pom, root *model.DepGraph) { 55 | 56 | logs.Infof("file %s dep track:", pom.File.Relpath()) 57 | root.ForEachNode(func(p, n *model.DepGraph) bool { 58 | logs.Info(n.Expand.(*java.Pom).PomDependency.ImportPathStack()) 59 | return true 60 | }) 61 | 62 | root.Build(false, model.Lan_Java) 63 | logs.Infof("file %s dep tree:\n%s", pom.File.Relpath(), root.Tree(false, false)) 64 | 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /example/javascript/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "io/fs" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/common" 11 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 12 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 13 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/javascript" 14 | ) 15 | 16 | func init() { 17 | 18 | // register npm repository origin 19 | javascript.RegisterNpmOrigin(func(name, version string) *javascript.PackageJson { 20 | var pkgjson *javascript.PackageJson 21 | common.DownloadUrlFromRepos(name, func(repo common.RepoConfig, r io.Reader) { pkgjson = javascript.ReadNpmJson(r, version) }, 22 | common.RepoConfig{Url: "https://r.cnpmjs.org/"}, 23 | ) 24 | return pkgjson 25 | }) 26 | } 27 | 28 | func main() { 29 | 30 | projectDir := "../../test/javascript/5" 31 | 32 | sca := javascript.Sca{} 33 | 34 | var files []*model.File 35 | filepath.WalkDir(projectDir, func(path string, d fs.DirEntry, err error) error { 36 | if !sca.Filter(path) { 37 | return nil 38 | } 39 | file := model.NewFile(path, strings.TrimPrefix(path, projectDir)) 40 | files = append(files, file) 41 | return nil 42 | }) 43 | 44 | sca.Sca(context.TODO(), nil, files, func(file *model.File, root ...*model.DepGraph) { 45 | for _, dep := range root { 46 | dep.Build(false, sca.Language()) 47 | logs.Infof("file %s:\n%s", file.Relpath(), dep.Tree(false, false)) 48 | } 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /example/logs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 9 | ) 10 | 11 | func main() { 12 | 13 | // DEBUG log 14 | logs.Debugf("this is debug message %s", "hello world") 15 | 16 | // set log config 17 | logs.SetLogConfig(func(n *logs.LogConfig) { 18 | // close DEBUG log 19 | n.Debug = false 20 | }) 21 | 22 | // this message will be ignore 23 | logs.Debug("this is debug message") 24 | 25 | logs.Infof("this is info message %d", 123) 26 | 27 | // WARN log 28 | logs.Warn("warn!") 29 | 30 | // ERROR log will print error stack 31 | logs.Error(errors.New("test error")) 32 | 33 | // custom log format 34 | logs.RegisterOut(func(level logs.Level, format string, v ...any) { 35 | log.SetPrefix(fmt.Sprintf("{CUSTOM} level:%d ", level)) 36 | if format == "" { 37 | log.Output(3, fmt.Sprint(v...)) 38 | } else { 39 | log.Output(3, fmt.Sprintf(format, v...)) 40 | } 41 | }) 42 | 43 | logs.Debug("custom log") 44 | 45 | } 46 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import _ "github.com/xmirrorsecurity/opensca-cli/v3" 4 | -------------------------------------------------------------------------------- /example/php/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "io/fs" 8 | "path/filepath" 9 | "strings" 10 | 11 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/common" 12 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 13 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 14 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/php" 15 | ) 16 | 17 | func init() { 18 | 19 | // register composer repository origin 20 | php.RegisterComposerOrigin(func(name, version string) *php.ComposerPackage { 21 | var composer *php.ComposerPackage 22 | common.DownloadUrlFromRepos(fmt.Sprintf("%s.json", name), func(repo common.RepoConfig, r io.Reader) { composer = php.ReadComposerRepoJson(r, name, version) }, 23 | common.RepoConfig{Url: "http://repo.packagist.org/p2"}, 24 | ) 25 | return composer 26 | }) 27 | } 28 | 29 | func main() { 30 | 31 | projectDir := "../../test/php/2" 32 | 33 | sca := php.Sca{} 34 | 35 | var files []*model.File 36 | filepath.WalkDir(projectDir, func(path string, d fs.DirEntry, err error) error { 37 | if !sca.Filter(path) { 38 | return nil 39 | } 40 | file := model.NewFile(path, strings.TrimPrefix(path, projectDir)) 41 | files = append(files, file) 42 | return nil 43 | }) 44 | 45 | sca.Sca(context.TODO(), nil, files, func(file *model.File, root ...*model.DepGraph) { 46 | for _, dep := range root { 47 | dep.Build(false, sca.Language()) 48 | logs.Infof("file %s:\n%s", file.Relpath(), dep.Tree(false, false)) 49 | } 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/xmirrorsecurity/opensca-cli/v3 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/BurntSushi/toml v1.3.2 7 | github.com/CycloneDX/cyclonedx-go v0.7.2 8 | github.com/Masterminds/semver/v3 v3.2.1 9 | github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 10 | github.com/gdamore/tcell/v2 v2.6.0 11 | github.com/glebarez/go-sqlite v1.21.2 12 | github.com/google/uuid v1.5.0 13 | github.com/nwaples/rardecode v1.1.3 14 | github.com/pkg/errors v0.9.1 15 | github.com/rivo/tview v0.0.0-20231126152417-33a1d271f2b6 16 | github.com/titanous/json5 v1.0.0 17 | github.com/veraison/swid v1.1.0 18 | golang.org/x/term v0.14.0 19 | gorm.io/driver/mysql v1.5.2 20 | gorm.io/driver/sqlite v1.5.4 21 | gorm.io/gorm v1.25.5 22 | ) 23 | 24 | require ( 25 | github.com/gdamore/encoding v1.0.0 // indirect 26 | github.com/hashicorp/errwrap v1.1.0 // indirect 27 | github.com/hashicorp/go-multierror v1.1.1 // indirect 28 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 29 | github.com/mattn/go-runewidth v0.0.15 // indirect 30 | github.com/rivo/uniseg v0.4.4 // indirect 31 | golang.org/x/text v0.14.0 // indirect 32 | ) 33 | 34 | require ( 35 | github.com/davecgh/go-spew v1.1.1 // indirect 36 | github.com/dustin/go-humanize v1.0.1 // indirect 37 | github.com/fxamacker/cbor/v2 v2.5.0 // indirect 38 | github.com/go-sql-driver/mysql v1.7.1 // indirect 39 | github.com/jinzhu/inflection v1.0.0 // indirect 40 | github.com/jinzhu/now v1.1.5 // indirect 41 | github.com/jlaffaye/ftp v0.2.0 42 | github.com/mattn/go-isatty v0.0.20 // indirect 43 | github.com/mattn/go-sqlite3 v1.14.18 // indirect 44 | github.com/pmezard/go-difflib v1.0.0 // indirect 45 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 46 | github.com/stretchr/testify v1.8.4 // indirect 47 | github.com/x448/float16 v0.8.4 // indirect 48 | golang.org/x/sys v0.14.0 // indirect 49 | gopkg.in/yaml.v3 v3.0.1 // indirect 50 | modernc.org/libc v1.34.11 // indirect 51 | modernc.org/mathutil v1.6.0 // indirect 52 | modernc.org/memory v1.7.2 // indirect 53 | modernc.org/sqlite v1.27.0 // indirect 54 | ) 55 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | GitTag = $(shell git describe --abbrev=0 --tags) 2 | 3 | release: 4 | goreleaser release --snapshot --clean 5 | echo $(GitTag) > ./dist/version -------------------------------------------------------------------------------- /opensca/common/httpclient.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "crypto/tls" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | var HttpDownloadClient = &http.Client{ 10 | Transport: &http.Transport{ 11 | MaxIdleConns: 50, 12 | MaxConnsPerHost: 50, 13 | MaxIdleConnsPerHost: 50, 14 | IdleConnTimeout: 30 * time.Second, 15 | TLSClientConfig: &tls.Config{ 16 | InsecureSkipVerify: false, 17 | }, 18 | }, 19 | } 20 | 21 | func SetHttpDownloadClient(do func(c *http.Client)) { 22 | if do != nil { 23 | do(HttpDownloadClient) 24 | } 25 | } 26 | 27 | var HttpSaasClient = &http.Client{ 28 | Transport: &http.Transport{ 29 | MaxIdleConns: 1, 30 | MaxConnsPerHost: 1, 31 | MaxIdleConnsPerHost: 1, 32 | IdleConnTimeout: 30 * time.Second, 33 | TLSClientConfig: &tls.Config{ 34 | InsecureSkipVerify: false, 35 | }, 36 | }, 37 | } 38 | 39 | func SetHttpSaasClient(do func(c *http.Client)) { 40 | if do != nil { 41 | do(HttpSaasClient) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /opensca/common/repo.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 10 | ) 11 | 12 | type RepoConfig struct { 13 | Url string `json:"url"` 14 | Username string `json:"username"` 15 | Password string `json:"password"` 16 | } 17 | 18 | func TrimRepo(repos ...RepoConfig) []RepoConfig { 19 | var newRepos []RepoConfig 20 | for _, repo := range repos { 21 | if repo.Url != "" { 22 | newRepos = append(newRepos, repo) 23 | } 24 | } 25 | return newRepos 26 | } 27 | 28 | func DownloadUrlFromRepos(route string, do func(repo RepoConfig, r io.Reader), repos ...RepoConfig) bool { 29 | 30 | repoSet := map[string]bool{} 31 | 32 | for _, repo := range repos { 33 | 34 | if repo.Url == "" { 35 | continue 36 | } 37 | if repoSet[repo.Url] { 38 | continue 39 | } 40 | repoSet[repo.Url] = true 41 | 42 | url := fmt.Sprintf("%s/%s", strings.TrimRight(repo.Url, "/"), strings.TrimLeft(route, "/")) 43 | req, err := http.NewRequest("GET", url, nil) 44 | if err != nil { 45 | logs.Warn(err) 46 | return false 47 | } 48 | 49 | if repo.Username+repo.Password != "" { 50 | req.SetBasicAuth(repo.Username, repo.Password) 51 | } 52 | 53 | resp, err := HttpDownloadClient.Do(req) 54 | if err != nil { 55 | logs.Warn(err) 56 | continue 57 | } 58 | 59 | if resp.StatusCode != 200 { 60 | logs.Warnf("%d %s", resp.StatusCode, url) 61 | io.Copy(io.Discard, resp.Body) 62 | resp.Body.Close() 63 | } else { 64 | logs.Debugf("%d %s", resp.StatusCode, url) 65 | do(repo, resp.Body) 66 | resp.Body.Close() 67 | return true 68 | } 69 | 70 | } 71 | return false 72 | } 73 | -------------------------------------------------------------------------------- /opensca/common/temp.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "time" 7 | 8 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 9 | ) 10 | 11 | var tempdir = ".temp" 12 | 13 | func init() { 14 | // 在opensca-cli所在目录下创建临时目录 15 | excpath, _ := os.Executable() 16 | tempdir = filepath.Join(filepath.Dir(excpath), tempdir) 17 | os.MkdirAll(tempdir, 0755) 18 | // 删除24小时前的临时文件(一般是进程意外中断时未被删除的临时文件) 19 | old := time.Now().Add(-24 * time.Hour) 20 | entries, err := os.ReadDir(tempdir) 21 | if err != nil { 22 | return 23 | } 24 | for _, entry := range entries { 25 | info, err := entry.Info() 26 | if err != nil { 27 | continue 28 | } 29 | if info.ModTime().Before(old) { 30 | os.RemoveAll(filepath.Join(tempdir, entry.Name())) 31 | } 32 | } 33 | } 34 | 35 | func MkdirTemp(pattern string) string { 36 | tmp, err := os.MkdirTemp(tempdir, pattern) 37 | if err != nil { 38 | logs.Warn(err) 39 | } 40 | return tmp 41 | } 42 | 43 | func CreateTemp(pattern string) *os.File { 44 | tempf, err := os.CreateTemp(tempdir, pattern) 45 | if err != nil { 46 | logs.Warn(err) 47 | } 48 | return tempf 49 | } 50 | -------------------------------------------------------------------------------- /opensca/model/dpsbom.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type DpSbomDocument struct { 6 | // 文档名称 7 | DocumentName string `json:"DocumentName"` 8 | // 文档版本 9 | DocumentVersion string `json:"DocumentVersion"` 10 | // 文档创建/更新时间 yyyy-MM-ddTHH:mm:ssTZD 11 | DocumentTime string `json:"DocumentTime"` 12 | // 文档格式 13 | BomFormat string `json:"BomFormat"` 14 | // 生成工具 15 | Tool string `json:"tool"` 16 | // sbom签名信息 17 | Hashes DpSbomHashes `json:"Hashes"` 18 | // 组件列表 19 | Packages []DpSbomPackage `json:"Packages"` 20 | // 依赖关系 21 | Dependencies []DpSbomDependencies `json:"Dependencies"` 22 | } 23 | 24 | type DpSbomPackage struct { 25 | Name string `json:"ComponentName"` 26 | Version string `json:"ComponentVersion"` 27 | 28 | Identifier struct { 29 | Purl string `json:"PURL"` 30 | } `json:"ComponentIdentifier"` 31 | 32 | License []string `json:"License"` 33 | 34 | Author []map[string]string `json:"Author"` 35 | Provider []map[string]string `json:"Provider"` 36 | Hash DpSbomHash `json:"ComponentHash"` 37 | 38 | // 组件信息更新时间 yyyy-MM-ddTHH:mm:ssTZD 39 | Timestamp string `json:"Timestamp"` 40 | } 41 | 42 | type DpSbomDependencies struct { 43 | Ref string `json:"Ref"` 44 | DependsOn []struct { 45 | Target string `json:"Target"` 46 | } `json:"DependsOn"` 47 | } 48 | 49 | func newDependencies(ref string, dependsOn []string) DpSbomDependencies { 50 | deps := DpSbomDependencies{Ref: ref} 51 | deps.DependsOn = make([]struct { 52 | Target string "json:\"Target\"" 53 | }, len(dependsOn)) 54 | for i, d := range dependsOn { 55 | deps.DependsOn[i].Target = d 56 | } 57 | return deps 58 | } 59 | 60 | type DpSbomHashes struct { 61 | Algorithm string `json:"Algorithm"` 62 | HashFile string `json:"HashFile,omitempty"` 63 | DigitalFile string `json:"DigitalFile,omitempty"` 64 | } 65 | 66 | type DpSbomHash struct { 67 | Algorithm string `json:"Algorithm,omitempty"` 68 | Hash string `json:"Hash,omitempty"` 69 | } 70 | 71 | func NewDpSbomDocument(name, creator string) *DpSbomDocument { 72 | version := "1.0.0" 73 | timestamp := time.Now().Format("2006-01-02T15:04:05MST") 74 | return &DpSbomDocument{ 75 | DocumentName: name, 76 | DocumentVersion: version, 77 | DocumentTime: timestamp, 78 | BomFormat: "DP-SBOM-1.0", 79 | Tool: creator, 80 | Hashes: DpSbomHashes{ 81 | Algorithm: "SHA-256", 82 | HashFile: "sha256.txt", 83 | }, 84 | Dependencies: []DpSbomDependencies{}, 85 | } 86 | } 87 | 88 | func (doc *DpSbomDocument) AppendComponents(fn func(*DpSbomPackage)) { 89 | c := DpSbomPackage{} 90 | if fn != nil { 91 | fn(&c) 92 | } 93 | if c.Timestamp == "" { 94 | c.Timestamp = time.Now().Format("2006-01-02T15:04:05MST") 95 | } 96 | if c.Author == nil { 97 | c.Author = []map[string]string{} 98 | } 99 | if c.Provider == nil { 100 | c.Provider = []map[string]string{} 101 | } 102 | doc.Packages = append(doc.Packages, c) 103 | } 104 | 105 | func (doc *DpSbomDocument) AppendDependencies(parentId string, childrenIds []string) { 106 | if doc.Dependencies == nil { 107 | doc.Dependencies = []DpSbomDependencies{} 108 | } 109 | if len(childrenIds) > 0 { 110 | doc.Dependencies = append(doc.Dependencies, newDependencies(parentId, childrenIds)) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /opensca/model/dsdx.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "text/template" 8 | "time" 9 | ) 10 | 11 | type DsdxDocument struct { 12 | // 文档名称 13 | Name string `json:"name" xml:"name"` 14 | // 创作者 15 | Creator string `json:"creator" xml:"creator"` 16 | // dsdx版本 DSDX-1.0 17 | DSDXVersion string `json:"dsdx_version" xml:"dsdx_version"` 18 | // 文档创建时间 yyyy-MM-dd HH:mm:ss 19 | CreateTime string `json:"create_time" xml:"create_time"` 20 | // dsdx文档标识 自动生成 DSDX-${Name}-${Version}-${CreateTime} 21 | DSDXID string `json:"dsdx_id" xml:"dsdx_id"` 22 | // 项目名称 23 | ProjectName string `json:"project_name" xml:"project_name"` 24 | // 组件列表 25 | Components []DsdxComponent `json:"components" xml:"components"` 26 | // 依赖关系 27 | Dependencies DsdxDependencies `json:"dependencies" xml:"-"` 28 | } 29 | 30 | type DsdxComponent struct { 31 | // DSDX-xxx 32 | ID string `json:"id" xml:"id"` 33 | Group string `json:"group,omitempty" xml:"group,omitempty"` 34 | Name string `json:"name" xml:"name"` 35 | Version string `json:"version" xml:"version"` 36 | Language string `json:"language,omitempty" xml:"language,omitempty"` 37 | License []string `json:"license,omitempty" xml:"license,omitempty"` 38 | } 39 | 40 | type DsdxDependencies map[string][]string 41 | 42 | func NewDsdxDocument(name, creator string) *DsdxDocument { 43 | version := "DSDX-1.0" 44 | create := time.Now().Format("2006-01-02 15:04:05") 45 | id := fmt.Sprintf("DSDX-%s-%s-%s", name, version, create) 46 | return &DsdxDocument{ 47 | Name: name, 48 | Creator: creator, 49 | ProjectName: name, 50 | CreateTime: create, 51 | DSDXVersion: version, 52 | DSDXID: id, 53 | } 54 | } 55 | 56 | func (doc *DsdxDocument) AppendComponents(id, group, name, version, language string, license []string) { 57 | if id == "" { 58 | id = fmt.Sprintf("DSDX-%s-%s-%s", group, name, version) 59 | } 60 | doc.Components = append(doc.Components, DsdxComponent{ 61 | ID: id, 62 | Group: group, 63 | Name: name, 64 | Version: version, 65 | Language: language, 66 | License: license, 67 | }) 68 | } 69 | 70 | func (doc *DsdxDocument) AppendDependencies(parentId string, childrenIds []string) { 71 | if doc.Dependencies == nil { 72 | doc.Dependencies = DsdxDependencies{} 73 | } 74 | if len(childrenIds) > 0 { 75 | doc.Dependencies[parentId] = childrenIds 76 | } 77 | } 78 | 79 | func (doc *DsdxDocument) WriteDsdx(w io.Writer) error { 80 | tmpl, err := template.New("tagValue").Funcs(template.FuncMap{ 81 | "tojson": func(o any) string { 82 | data, _ := json.Marshal(o) 83 | return string(data) 84 | }, 85 | }).Parse(dsdxtpl) 86 | if err != nil { 87 | return err 88 | } 89 | return tmpl.Execute(w, doc) 90 | } 91 | 92 | const dsdxtpl = `Name: {{ .Name }} 93 | DSDXVersion: {{ .DSDXVersion }} 94 | DSDXID: {{ .DSDXID }} 95 | Creator: {{ .Creator }} 96 | CreateTime: {{ .CreateTime }} 97 | ProjectName: {{ .ProjectName }} 98 | {{ range .Components }} 99 | ComponentName: {{ .Name }} 100 | ComponentGroup: {{ .Group }} 101 | ComponentVersion: {{ .Version }} 102 | ComponentID: {{ .ID }} 103 | ComponentLanguage: {{ .Language }} 104 | ComponentLicense: {{ .License|tojson }} 105 | {{ end }} 106 | Dependencies: {{ .Dependencies|tojson }} 107 | ` 108 | -------------------------------------------------------------------------------- /opensca/model/language.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Language string 9 | 10 | const ( 11 | Lan_None Language = "" 12 | Lan_Java Language = "Java" 13 | Lan_JavaScript Language = "JavaScript" 14 | Lan_Php Language = "Php" 15 | Lan_Ruby Language = "Ruby" 16 | Lan_Golang Language = "Golang" 17 | Lan_Rust Language = "Rust" 18 | Lan_Erlang Language = "Erlang" 19 | Lan_Python Language = "Python" 20 | ) 21 | 22 | var purlRmap = map[string]Language{ 23 | "cargo": Lan_Rust, 24 | "composer": Lan_Php, 25 | "gem": Lan_Ruby, 26 | "golang": Lan_Golang, 27 | "maven": Lan_Java, 28 | "npm": Lan_JavaScript, 29 | "pypi": Lan_Python, 30 | } 31 | 32 | var purlMap = map[Language]string{} 33 | 34 | func init() { 35 | for k, v := range purlRmap { 36 | purlMap[v] = k 37 | } 38 | } 39 | 40 | func Purl(vendor, name, version string, language Language) string { 41 | pkg := "" 42 | if g, ok := purlMap[language]; ok { 43 | pkg = g 44 | } 45 | if vendor == "" { 46 | return fmt.Sprintf("pkg:%s/%s@%s", pkg, name, version) 47 | } 48 | return fmt.Sprintf("pkg:%s/%s/%s@%s", pkg, vendor, name, version) 49 | } 50 | 51 | func ParsePurl(purl string) (vendor, name, version string, language Language) { 52 | 53 | // purl示例 pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?packaging=sources 54 | 55 | if i := strings.LastIndex(purl, "?"); i != -1 { 56 | purl = purl[:i] 57 | } 58 | 59 | if i := strings.Index(purl, "/"); i == -1 { 60 | return 61 | } else { 62 | if pkg := strings.Split(purl[:i], `:`); len(pkg) != 2 { 63 | return 64 | } else { 65 | if l, ok := purlRmap[pkg[1]]; ok { 66 | language = l 67 | } 68 | } 69 | purl = purl[i+1:] 70 | } 71 | 72 | if i := strings.LastIndex(purl, "@"); i == -1 { 73 | return 74 | } else { 75 | version = purl[i+1:] 76 | name = purl[:i] 77 | } 78 | 79 | if language == Lan_Java { 80 | if i := strings.LastIndex(name, "/"); i != -1 { 81 | vendor = name[:i] 82 | name = name[i+1:] 83 | } 84 | } 85 | 86 | return 87 | } 88 | -------------------------------------------------------------------------------- /opensca/sca/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | "sync" 9 | 10 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 11 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 12 | ) 13 | 14 | var ( 15 | cacheDir string = ".opensca-cache" 16 | cacheOnce = sync.Once{} 17 | ) 18 | 19 | func initCache() { 20 | excpath, _ := os.Executable() 21 | cacheDir = filepath.Join(filepath.Dir(excpath), cacheDir) 22 | if err := os.MkdirAll(cacheDir, 0777); err != nil { 23 | logs.Error(err) 24 | } 25 | } 26 | 27 | func Save(path string, reader io.Reader) bool { 28 | os.MkdirAll(filepath.Dir(path), 0777) 29 | if f, err := os.Create(path); err == nil { 30 | defer f.Close() 31 | io.Copy(f, reader) 32 | return true 33 | } 34 | return false 35 | } 36 | 37 | func Load(path string, do func(reader io.Reader)) bool { 38 | if f, err := os.Open(path); err == nil { 39 | defer f.Close() 40 | do(f) 41 | return true 42 | } 43 | return false 44 | } 45 | 46 | func Path(vendor, name, version string, language model.Language) string { 47 | cacheOnce.Do(initCache) 48 | var path string 49 | switch language { 50 | case model.Lan_Java: 51 | path = filepath.Join(cacheDir, "maven", vendor, name, version, fmt.Sprintf("%s-%s.pom", name, version)) 52 | case model.Lan_JavaScript: 53 | path = filepath.Join(cacheDir, "npm", fmt.Sprintf("%s.json", name)) 54 | case model.Lan_Php: 55 | path = filepath.Join(cacheDir, "composer", fmt.Sprintf("%s.json", name)) 56 | default: 57 | path = filepath.Join(cacheDir, "none", fmt.Sprintf("%s-%s-%s", vendor, name, version)) 58 | } 59 | return path 60 | } 61 | -------------------------------------------------------------------------------- /opensca/sca/erlang/sca.go: -------------------------------------------------------------------------------- 1 | package erlang 2 | 3 | import ( 4 | "context" 5 | "regexp" 6 | 7 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 8 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/filter" 9 | ) 10 | 11 | type Sca struct{} 12 | 13 | func (sca Sca) Language() model.Language { 14 | return model.Lan_Erlang 15 | } 16 | 17 | func (sca Sca) Filter(relpath string) bool { 18 | return filter.ErlangRebarLock(relpath) 19 | } 20 | 21 | func (sca Sca) Sca(ctx context.Context, parent *model.File, files []*model.File, call model.ResCallback) { 22 | for _, f := range files { 23 | if sca.Filter(f.Relpath()) { 24 | call(f, ParseRebarLock(f)) 25 | } 26 | } 27 | } 28 | 29 | func ParseRebarLock(file *model.File) *model.DepGraph { 30 | reg := regexp.MustCompile(`<<"([\w\d]+)">>\S*?pkg,<<"[\w\d]+">>,<<"([.\d]+)">>`) 31 | root := &model.DepGraph{Path: file.Relpath()} 32 | file.ReadLine(func(line string) { 33 | match := reg.FindStringSubmatch(line) 34 | if len(match) > 2 { 35 | root.AppendChild(&model.DepGraph{ 36 | Name: match[1], 37 | Version: match[2], 38 | }) 39 | } 40 | }) 41 | return root 42 | } 43 | -------------------------------------------------------------------------------- /opensca/sca/filter/filter.go: -------------------------------------------------------------------------------- 1 | package filter 2 | 3 | import ( 4 | "path/filepath" 5 | "strings" 6 | ) 7 | 8 | func filterFunc(strFunc func(string, string) bool, args ...string) func(string) bool { 9 | return func(filename string) bool { 10 | for _, suffix := range args { 11 | if strFunc(filename, suffix) { 12 | return true 13 | } 14 | } 15 | return false 16 | } 17 | } 18 | 19 | var ( 20 | JavaPom = filterFunc(strings.HasSuffix, "pom.xml", ".pom") 21 | ) 22 | 23 | var ( 24 | JavaScriptPackageLock = filterFunc(strings.HasSuffix, "package-lock.json") 25 | JavaScriptPackageJson = func(filename string) bool { 26 | return strings.HasSuffix(filename, "package.json") 27 | } 28 | JavaScriptYarnLock = filterFunc(strings.HasSuffix, "yarn.lock") 29 | ) 30 | 31 | var ( 32 | PhpComposer = filterFunc(strings.HasSuffix, "composer.json") 33 | PhpComposerLock = filterFunc(strings.HasSuffix, "composer.lock") 34 | ) 35 | 36 | var ( 37 | RubyGemfileLock = filterFunc(strings.HasSuffix, "Gemfile.lock", "gems.locked") 38 | ) 39 | 40 | var ( 41 | GoMod = filterFunc(strings.HasSuffix, "go.mod") 42 | GoSum = filterFunc(strings.HasSuffix, "go.sum", "go.work.sum") 43 | GoPkgToml = filterFunc(strings.HasSuffix, "Gopkg.toml") 44 | GoPkgLock = filterFunc(strings.HasSuffix, "Gopkg.lock") 45 | ) 46 | 47 | var ( 48 | RustCargoLock = filterFunc(strings.HasSuffix, "Cargo.lock") 49 | ) 50 | 51 | var ( 52 | ErlangRebarLock = filterFunc(strings.HasSuffix, "rebar.lock") 53 | ) 54 | 55 | var ( 56 | GroovyFile = filterFunc(strings.HasSuffix, ".groovy") 57 | GroovyGradle = filterFunc(strings.HasSuffix, ".gradle", ".gradle.kts") 58 | ) 59 | 60 | var ( 61 | PythonSetup = filterFunc(strings.HasSuffix, "setup.py") 62 | PythonPipfile = filterFunc(strings.HasSuffix, "Pipfile") 63 | PythonPipfileLock = filterFunc(strings.HasSuffix, "Pipfile.lock") 64 | PythonRequirementsTxt = func(filename string) bool { 65 | return filterFunc(strings.HasSuffix, ".txt")(filename) && 66 | filterFunc(strings.Contains, "requirements")(filepath.Base(filename)) && !filterFunc(strings.Contains, "test")(filepath.Base(filename)) 67 | } 68 | PythonRequirementsIn = filterFunc(strings.HasSuffix, "requirements.in") 69 | ) 70 | 71 | var ( 72 | SbomSpdx = filterFunc(strings.HasSuffix, ".spdx") 73 | SbomDsdx = filterFunc(strings.HasSuffix, ".dsdx") 74 | SbomJson = filterFunc(strings.HasSuffix, ".json") 75 | SbomXml = filterFunc(strings.HasSuffix, ".xml") 76 | SbomDbSbom = filterFunc(strings.HasSuffix, ".dbsbom") 77 | // SbomRdf = filterFunc(strings.HasSuffix, ".rdf") 78 | ) 79 | 80 | var ( 81 | CompressFile = filterFunc(strings.HasSuffix, 82 | ".zip", 83 | ".jar", 84 | ".war", 85 | ".rar", 86 | ".tar", 87 | ".gz", 88 | ".bz2", 89 | ) 90 | ) 91 | -------------------------------------------------------------------------------- /opensca/sca/golang/gopkg.go: -------------------------------------------------------------------------------- 1 | package golang 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/BurntSushi/toml" 7 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 8 | ) 9 | 10 | type GopkgToml struct { 11 | Constraint []struct { 12 | Name string `json:"name"` 13 | Version string `json:"version"` 14 | } `toml:"constraint"` 15 | } 16 | 17 | type GopkgLock struct { 18 | Projects []struct { 19 | Name string `json:"name"` 20 | Version string `json:"version"` 21 | } `toml:"projects"` 22 | } 23 | 24 | // ParseGopkgToml 解析Gopkg.toml文件 25 | func ParseGopkgToml(f *model.File) *model.DepGraph { 26 | root := &model.DepGraph{Path: f.Relpath()} 27 | gopkg := GopkgToml{} 28 | f.OpenReader(func(reader io.Reader) { 29 | toml.NewDecoder(reader).Decode(&gopkg) 30 | }) 31 | for _, dep := range gopkg.Constraint { 32 | root.AppendChild(&model.DepGraph{Name: dep.Name, Version: dep.Version}) 33 | } 34 | return root 35 | } 36 | 37 | // ParseGopkgLock 解析Gopkg.lock文件 38 | func ParseGopkgLock(f *model.File) *model.DepGraph { 39 | root := &model.DepGraph{Path: f.Relpath()} 40 | pkglock := GopkgLock{} 41 | f.OpenReader(func(reader io.Reader) { 42 | toml.NewDecoder(reader).Decode(&pkglock) 43 | }) 44 | for _, dep := range pkglock.Projects { 45 | root.AppendChild(&model.DepGraph{Name: dep.Name, Version: dep.Version}) 46 | } 47 | return root 48 | } 49 | -------------------------------------------------------------------------------- /opensca/sca/golang/sca.go: -------------------------------------------------------------------------------- 1 | package golang 2 | 3 | import ( 4 | "context" 5 | "path/filepath" 6 | 7 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 8 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/filter" 9 | ) 10 | 11 | type Sca struct{} 12 | 13 | func (sca Sca) Language() model.Language { 14 | return model.Lan_Golang 15 | } 16 | 17 | func (sca Sca) Filter(relpath string) bool { 18 | return filter.GoMod(relpath) || filter.GoSum(relpath) || filter.GoPkgToml(relpath) || filter.GoPkgLock(relpath) 19 | } 20 | 21 | func (sca Sca) Sca(ctx context.Context, parent *model.File, files []*model.File, call model.ResCallback) { 22 | 23 | // map[dir]*File 24 | gomod := map[string]*model.File{} 25 | gosum := map[string]*model.File{} 26 | pkglock := map[string]*model.File{} 27 | pkgtoml := map[string]*model.File{} 28 | 29 | // 记录相关文件 30 | for _, f := range files { 31 | dir := filepath.Dir(f.Relpath()) 32 | if filter.GoPkgToml(f.Relpath()) { 33 | pkgtoml[dir] = f 34 | } 35 | if filter.GoPkgLock(f.Relpath()) { 36 | pkglock[dir] = f 37 | } 38 | if filter.GoMod(f.Relpath()) { 39 | gomod[dir] = f 40 | } 41 | if filter.GoSum(f.Relpath()) { 42 | gosum[dir] = f 43 | } 44 | } 45 | 46 | // 尝试调用 go mod graph 47 | for dir, f := range gomod { 48 | graph := GoModGraph(ctx, f) 49 | if graph != nil && len(graph.Children) > 0 { 50 | call(f, graph) 51 | delete(gomod, dir) 52 | delete(gosum, dir) 53 | } 54 | } 55 | 56 | // 静态解析go.sum 57 | for dir, f := range gosum { 58 | sum := ParseGosum(f) 59 | call(f, sum) 60 | delete(gomod, dir) 61 | } 62 | 63 | // 静态解析go.mod 64 | for _, f := range gomod { 65 | mod := ParseGomod(f) 66 | call(f, mod) 67 | } 68 | 69 | // 静态解析gopkg.lock 70 | for dir, f := range pkglock { 71 | lock := ParseGopkgLock(f) 72 | call(f, lock) 73 | delete(pkgtoml, dir) 74 | } 75 | 76 | // 静态解析gopkg.toml 77 | for _, f := range pkgtoml { 78 | pkg := ParseGopkgToml(f) 79 | call(f, pkg) 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /opensca/sca/groovy/groovy.go: -------------------------------------------------------------------------------- 1 | package groovy 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 8 | ) 9 | 10 | // ParseGroovy 解析groovy文件 11 | func ParseGroovy(file *model.File) *model.DepGraph { 12 | 13 | // @Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE') 14 | depLongReg := regexp.MustCompile(`@Grab\(group='([^'\s]+)', module='([^'\s]+)', version='([^'\s]+)'\)`) 15 | 16 | // @Grab('org.springframework:spring-orm:3.2.5.RELEASE;transitive=false') 17 | depShortReg := regexp.MustCompile(`@Grab\('([^:\s]+):([^:\s]+):([^:;'\)\s]+)`) 18 | 19 | _dep := model.NewDepGraphMap(nil, func(s ...string) *model.DepGraph { 20 | return &model.DepGraph{ 21 | Vendor: s[0], 22 | Name: s[1], 23 | Version: s[2], 24 | } 25 | }).LoadOrStore 26 | 27 | root := &model.DepGraph{Path: file.Relpath()} 28 | 29 | file.ReadLineNoComment(model.CTypeComment, func(line string) { 30 | for _, match := range append(depLongReg.FindAllStringSubmatch(line, -1), depShortReg.FindAllStringSubmatch(line, -1)...) { 31 | 32 | vendor := match[1] 33 | name := match[2] 34 | version := match[3] 35 | index := strings.Index(version, "@") 36 | if index != -1 { 37 | version = version[:index] 38 | } 39 | 40 | if version == "" || strings.Contains(version, "$") { 41 | continue 42 | } 43 | 44 | root.AppendChild(_dep(vendor, name, version)) 45 | } 46 | }) 47 | 48 | return root 49 | } 50 | -------------------------------------------------------------------------------- /opensca/sca/groovy/opensca.gradle: -------------------------------------------------------------------------------- 1 | import groovy.json.JsonOutput 2 | 3 | class DepTree{ 4 | def children 5 | def dep 6 | def dict 7 | DepTree(dep){ 8 | this.dict = [:] 9 | if (dep){ 10 | this.dict.groupId = dep.moduleGroup 11 | this.dict.artifactId = dep.moduleName 12 | this.dict.version = dep.moduleVersion 13 | this.dep = dep 14 | } 15 | this.dict.children = [] 16 | } 17 | def key(){ 18 | return "${this.dict.("groupId")}:${this.dict.get("artifactId")}" 19 | } 20 | } 21 | 22 | def getDepsList(deps){ 23 | def nodes = [] 24 | deps.each{ dep -> 25 | nodes += new DepTree(dep) 26 | } 27 | return nodes 28 | } 29 | 30 | def getDepsTree(deps){ 31 | def result = [] 32 | deps.each{ ds -> 33 | def exist = [:] 34 | def q = getDepsList(ds) 35 | q.each{ d -> 36 | result += d.dict 37 | } 38 | while (!q.isEmpty()){ 39 | def node = q.get(0) 40 | q.remove(0) 41 | if (!exist.get(node.key())){ 42 | exist.put(node.key(), true) 43 | def children = getDepsList(node.dep.children) 44 | q += children 45 | children.each{ d -> 46 | node.dict.children += d.dict 47 | } 48 | } 49 | node.dep = null 50 | } 51 | } 52 | return result 53 | } 54 | 55 | allprojects { 56 | task opensca { 57 | doLast { 58 | def deps = [] 59 | rootProject.allprojects.each{ proj-> 60 | def ds = [] 61 | proj.configurations.each({ conf -> 62 | try { 63 | ds += conf.resolvedConfiguration.firstLevelModuleDependencies 64 | } catch (Exception ex) { 65 | } 66 | }) 67 | deps.add(ds) 68 | } 69 | println("openscaDepStart") 70 | println(JsonOutput.toJson(getDepsTree(deps))) 71 | println("openscaDepEnd") 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /opensca/sca/groovy/sca.go: -------------------------------------------------------------------------------- 1 | package groovy 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 7 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/filter" 8 | ) 9 | 10 | type Sca struct{} 11 | 12 | func (sca Sca) Language() model.Language { 13 | return model.Lan_Java 14 | } 15 | 16 | func (sca Sca) Filter(relpath string) bool { 17 | return filter.GroovyGradle(relpath) || filter.GroovyFile(relpath) 18 | } 19 | 20 | func (sca Sca) Sca(ctx context.Context, parent *model.File, files []*model.File, call model.ResCallback) { 21 | 22 | roots := GradleTree(ctx, parent) 23 | if len(roots) == 0 { 24 | roots = ParseGradle(ctx, files) 25 | } 26 | if len(roots) > 0 { 27 | call(parent, roots...) 28 | } 29 | 30 | for _, f := range files { 31 | if filter.GroovyFile(f.Relpath()) { 32 | call(f, ParseGroovy(f)) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /opensca/sca/groovy/variable.go: -------------------------------------------------------------------------------- 1 | package groovy 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "regexp" 7 | "strings" 8 | 9 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 10 | ) 11 | 12 | // groovy 变量表 13 | type Variable map[string]string 14 | 15 | var ( 16 | refReg = regexp.MustCompile(`((\w+)\[['"](\w+)['"]\])|(\$\{?[^{}"']*\}?)`) 17 | ) 18 | 19 | // Replace 使用当前变量表中的变量替换文本中的变量值 20 | func (v Variable) Replace(text string) string { 21 | 22 | if text == "" { 23 | return text 24 | } 25 | 26 | for exist := map[string]bool{}; !exist[text]; { 27 | exist[text] = true 28 | check := func(k, v string) bool { 29 | return len(v) > 0 && !strings.Contains(v, k) 30 | } 31 | text = refReg.ReplaceAllStringFunc(text, func(s string) string { 32 | if strings.HasPrefix(s, "$") { 33 | k := strings.Trim(s[1:], "{}") 34 | if value, ok := v[k]; ok { 35 | if check(s, value) { 36 | s = value 37 | } 38 | } 39 | } else { 40 | l := strings.Index(s, "[") 41 | if l > 0 { 42 | if value, ok := v[fmt.Sprintf("%s.%s", s[:l], s[l+2:len(s)-2])]; ok { 43 | if check(s, value) { 44 | s = value 45 | } 46 | } 47 | } 48 | } 49 | return s 50 | }) 51 | } 52 | 53 | return text 54 | } 55 | 56 | var startReg = regexp.MustCompile(`\s*(\w+)\s*=?\s*[\[\{]`) 57 | var varReg = regexp.MustCompile(`([\w]+)\s*[=:][\s\n]*['"]?([^\s()/'"]+)['"]?`) 58 | 59 | // Scan 提取文件中的变量 60 | func (v Variable) Scan(file *model.File) { 61 | 62 | if file == nil { 63 | return 64 | } 65 | 66 | var text string 67 | file.OpenReader(func(reader io.Reader) { 68 | data, _ := io.ReadAll(reader) 69 | text = string(data) 70 | }) 71 | 72 | blockIndex := startReg.FindAllStringIndex(text, -1) 73 | for i, bi := range blockIndex { 74 | end := len(text) 75 | if i+1 < len(blockIndex) { 76 | end = blockIndex[i+1][0] 77 | } 78 | m := startReg.FindStringSubmatch(text[bi[0]:bi[1]]) 79 | var object string 80 | if len(m) == 2 { 81 | object = m[1] 82 | } 83 | if object == "" { 84 | continue 85 | } 86 | for _, line := range strings.Split(text[bi[1]:end], "\n") { 87 | match := varReg.FindStringSubmatch(line) 88 | if len(match) == 3 { 89 | if object == "ext" { 90 | v[match[1]] = match[2] 91 | } 92 | v[fmt.Sprintf("%s.%s", object, match[1])] = match[2] 93 | } 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /opensca/sca/java/sca.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "strings" 7 | 8 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/common" 9 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 10 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/filter" 11 | ) 12 | 13 | type Sca struct { 14 | NotUseMvn bool 15 | NotUseStatic bool 16 | } 17 | 18 | func (sca Sca) Language() model.Language { 19 | return model.Lan_Java 20 | } 21 | 22 | func (sca Sca) Filter(relpath string) bool { 23 | return filter.JavaPom(relpath) 24 | } 25 | 26 | func (sca Sca) Sca(ctx context.Context, parent *model.File, files []*model.File, call model.ResCallback) { 27 | 28 | // jar包中的pom仅读取pom自身信息 不获取子依赖 29 | if strings.Contains(parent.Relpath(), ".jar") { 30 | for _, file := range files { 31 | if !filter.JavaPom(file.Relpath()) { 32 | continue 33 | } 34 | file.OpenReader(func(reader io.Reader) { 35 | p := ReadPom(reader) 36 | p.Update(&p.PomDependency) 37 | if !p.Check() { 38 | return 39 | } 40 | call(file, &model.DepGraph{ 41 | Vendor: p.GroupId, 42 | Name: p.ArtifactId, 43 | Version: p.Version, 44 | Path: file.Relpath(), 45 | }) 46 | }) 47 | } 48 | return 49 | } 50 | 51 | // 记录pom文件 52 | poms := []*Pom{} 53 | for _, file := range files { 54 | if filter.JavaPom(file.Relpath()) { 55 | file.OpenReader(func(reader io.Reader) { 56 | pom := ReadPom(reader) 57 | pom.File = file 58 | poms = append(poms, pom) 59 | }) 60 | } 61 | } 62 | 63 | // 记录不需要静态解析的pom 64 | var exclusionPom []*Pom 65 | 66 | // 优先尝试调用mvn 67 | if !sca.NotUseMvn { 68 | for _, pom := range poms { 69 | dep := MvnTree(ctx, pom) 70 | if dep != nil { 71 | call(pom.File, dep) 72 | exclusionPom = append(exclusionPom, pom) 73 | } 74 | } 75 | } 76 | 77 | // 静态解析 78 | if !sca.NotUseStatic { 79 | ParsePoms(ctx, poms, exclusionPom, func(pom *Pom, root *model.DepGraph) { 80 | call(pom.File, root) 81 | }) 82 | } 83 | } 84 | 85 | var defaultMavenRepo = []common.RepoConfig{ 86 | {Url: "https://maven.aliyun.com/repository/public"}, 87 | {Url: "https://repo1.maven.org/maven2"}, 88 | } 89 | 90 | func RegisterMavenRepo(repos ...common.RepoConfig) { 91 | newRepo := common.TrimRepo(repos...) 92 | if len(newRepo) > 0 { 93 | defaultMavenRepo = newRepo 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /opensca/sca/javascript/sca.go: -------------------------------------------------------------------------------- 1 | package javascript 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "path/filepath" 7 | "strings" 8 | 9 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/common" 10 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 11 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/filter" 12 | ) 13 | 14 | type Sca struct{} 15 | 16 | func (sca Sca) Language() model.Language { 17 | return model.Lan_JavaScript 18 | } 19 | 20 | func (sca Sca) Filter(relpath string) bool { 21 | return filter.JavaScriptPackageJson(relpath) || 22 | filter.JavaScriptPackageLock(relpath) || 23 | filter.JavaScriptYarnLock(relpath) 24 | } 25 | 26 | func (sca Sca) Sca(ctx context.Context, parent *model.File, files []*model.File, call model.ResCallback) { 27 | 28 | // map[dirpath] 29 | jsonMap := map[string]*PackageJson{} 30 | jsonNameMap := map[string]*PackageJson{} 31 | // map[dirpath] 32 | lockMap := map[string]*PackageLock{} 33 | // map[dirpath] 34 | nodeMap := map[string]*PackageJson{} 35 | // map[dirpath] 36 | yarnMap := map[string]map[string]*YarnLock{} 37 | 38 | // 将npm相关文件按上述方案分类 39 | for _, f := range files { 40 | 41 | dir := filepath.Dir(strings.ReplaceAll(f.Relpath(), `\`, `/`)) 42 | 43 | if filter.JavaScriptYarnLock(f.Relpath()) { 44 | yarnMap[dir] = ParseYarnLock(f) 45 | } 46 | 47 | if filter.JavaScriptPackageJson(f.Relpath()) { 48 | var js *PackageJson 49 | f.OpenReader(func(reader io.Reader) { 50 | js = readJson[PackageJson](reader) 51 | if js == nil { 52 | return 53 | } 54 | if js.Dependencies == nil { 55 | js.Dependencies = map[string]string{} 56 | } 57 | // 记录peerDependencies 58 | for k, v := range js.PeerDependencies { 59 | js.Dependencies[k] = v 60 | } 61 | js.File = f 62 | if strings.Contains(f.Relpath(), "node_modules") { 63 | nodeMap[dir] = js 64 | } else { 65 | jsonMap[dir] = js 66 | jsonNameMap[js.Name] = js 67 | } 68 | }) 69 | } 70 | 71 | if filter.JavaScriptPackageLock(f.Relpath()) { 72 | f.OpenReader(func(reader io.Reader) { 73 | lock := readJson[PackageLock](reader) 74 | if lock == nil { 75 | return 76 | } 77 | lockMap[dir] = lock 78 | }) 79 | } 80 | } 81 | 82 | // 遍历非node_modules下的package.json 83 | for dir, js := range jsonMap { 84 | 85 | // 尝试从package-lock.json获取 86 | if lock, ok := lockMap[dir]; ok { 87 | call(js.File, ParsePackageJsonWithLock(js, lock)) 88 | continue 89 | } 90 | 91 | // 尝试从yarn.lock获取 92 | if js.File != nil { 93 | if yarn, ok := yarnMap[dir]; ok { 94 | call(js.File, ParsePackageJsonWithYarnLock(js, yarn)) 95 | continue 96 | } 97 | } 98 | 99 | select { 100 | case <-ctx.Done(): 101 | return 102 | default: 103 | } 104 | 105 | // 尝试从node_modules及外部源获取 106 | call(js.File, ParsePackageJsonWithNode(js, nodeMap, jsonNameMap)) 107 | } 108 | } 109 | 110 | var defaultNpmRepo = []common.RepoConfig{ 111 | {Url: "https://r.cnpmjs.org/"}, 112 | } 113 | 114 | func RegisterNpmRepo(repos ...common.RepoConfig) { 115 | newRepo := common.TrimRepo(repos...) 116 | if len(newRepo) > 0 { 117 | defaultNpmRepo = newRepo 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /opensca/sca/javascript/yarn.go: -------------------------------------------------------------------------------- 1 | package javascript 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 7 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 8 | ) 9 | 10 | type YarnLock struct { 11 | Name string 12 | Version string 13 | Dependencies map[string]string 14 | } 15 | 16 | // ParseYarnLock 解析yarn.lock文件结构 17 | func ParseYarnLock(file *model.File) map[string]*YarnLock { 18 | 19 | /* 20 | name@version[, name@version]: 21 | version "xxx" 22 | dependencies: 23 | name "xxx" 24 | name "xxx" 25 | */ 26 | 27 | lock := map[string]*YarnLock{} 28 | 29 | var lastDep *YarnLock 30 | 31 | file.ReadLine(func(line string) { 32 | 33 | if strings.HasPrefix(line, "#") { 34 | return 35 | } 36 | 37 | if !strings.HasPrefix(line, " ") && strings.HasSuffix(line, ":") { 38 | lastDep = &YarnLock{Dependencies: map[string]string{}} 39 | for _, tag := range strings.Split(line, ",") { 40 | i := strings.LastIndex(tag, "@") 41 | if i == -1 { 42 | logs.Warnf("parse file %s line: %s fail", file.Relpath(), line) 43 | continue 44 | } 45 | name := strings.Trim(tag[:i], ` ":`) 46 | version := strings.Trim(tag[i+1:], ` ":`) 47 | lastDep.Name = name 48 | lock[npmkey(name, version)] = lastDep 49 | } 50 | return 51 | } 52 | 53 | if strings.HasPrefix(line, " ") { 54 | line = strings.TrimSpace(line) 55 | i := strings.Index(line, " ") 56 | if i == -1 { 57 | return 58 | } 59 | name := strings.Trim(line[:i], `"`) 60 | version := strings.Trim(line[i+1:], `"`) 61 | lastDep.Dependencies[name] = version 62 | return 63 | } 64 | 65 | if strings.HasPrefix(line, " ") { 66 | line = strings.TrimSpace(line) 67 | if !strings.HasPrefix(line, "version") { 68 | return 69 | } 70 | lastDep.Version = strings.Trim(strings.TrimPrefix(line, "version"), `" `) 71 | return 72 | } 73 | 74 | }) 75 | 76 | return lock 77 | } 78 | 79 | // ParsePackageJsonWithYarnLock 借助yarn.lock文件解析pacakge.json 80 | func ParsePackageJsonWithYarnLock(pkgjson *PackageJson, yarnlock map[string]*YarnLock) *model.DepGraph { 81 | 82 | root := &model.DepGraph{Name: pkgjson.Name, Version: pkgjson.Version, Path: pkgjson.File.Relpath()} 83 | 84 | _dep := _depSet().LoadOrStore 85 | 86 | // 记录依赖 87 | for _, lock := range yarnlock { 88 | dep := _dep(lock.Name, lock.Version) 89 | for name, version := range lock.Dependencies { 90 | sub := yarnlock[npmkey(name, version)] 91 | if sub != nil { 92 | dep.AppendChild(_dep(sub.Name, sub.Version)) 93 | } 94 | } 95 | } 96 | 97 | for name, version := range pkgjson.Dependencies { 98 | lock := yarnlock[npmkey(name, version)] 99 | if lock != nil { 100 | root.AppendChild(_dep(lock.Name, lock.Version)) 101 | } else { 102 | root.AppendChild(&model.DepGraph{Name: name, Version: version}) 103 | } 104 | } 105 | 106 | for name, version := range pkgjson.DevDependencies { 107 | lock := yarnlock[npmkey(name, version)] 108 | if lock != nil { 109 | dep := _dep(lock.Name, lock.Version) 110 | devdep := _dep(lock.Name, lock.Version, "dev") 111 | for _, c := range dep.Children { 112 | devdep.AppendChild(c) 113 | } 114 | root.AppendChild(devdep) 115 | } else { 116 | root.AppendChild(&model.DepGraph{Name: name, Version: version, Develop: true}) 117 | } 118 | } 119 | 120 | return root 121 | } 122 | -------------------------------------------------------------------------------- /opensca/sca/php/sca.go: -------------------------------------------------------------------------------- 1 | package php 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io" 7 | "path" 8 | "strings" 9 | 10 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/common" 11 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 12 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/filter" 13 | ) 14 | 15 | type Sca struct{} 16 | 17 | func (sca Sca) Language() model.Language { 18 | return model.Lan_Php 19 | } 20 | 21 | func (sca Sca) Filter(relpath string) bool { 22 | return filter.PhpComposer(relpath) || filter.PhpComposerLock(relpath) 23 | } 24 | 25 | func (sca Sca) Sca(ctx context.Context, parent *model.File, files []*model.File, call model.ResCallback) { 26 | 27 | jsonMap := map[string]*ComposerJson{} 28 | lockMap := map[string]*ComposerLock{} 29 | 30 | path2dir := func(relpath string) string { return path.Dir(strings.ReplaceAll(relpath, `\`, `/`)) } 31 | 32 | for _, f := range files { 33 | if filter.PhpComposer(f.Relpath()) { 34 | f.OpenReader(func(reader io.Reader) { 35 | var js ComposerJson 36 | json.NewDecoder(reader).Decode(&js) 37 | js.File = f 38 | jsonMap[path2dir(f.Relpath())] = &js 39 | }) 40 | } else if filter.PhpComposerLock(f.Relpath()) { 41 | f.OpenReader(func(reader io.Reader) { 42 | var lock ComposerLock 43 | json.NewDecoder(reader).Decode(&lock) 44 | lockMap[path2dir(f.Relpath())] = &lock 45 | }) 46 | } 47 | } 48 | 49 | loop: 50 | for dir, json := range jsonMap { 51 | 52 | // 通过lock文件补全 53 | if lock, ok := lockMap[dir]; ok { 54 | call(json.File, ParseComposerJsonWithLock(json, lock)) 55 | continue 56 | } 57 | 58 | // vendor中的composer.json没有对应lock则不做处理 59 | for _, dir := range strings.Split(dir, "/") { 60 | if strings.EqualFold(dir, "vendor") { 61 | continue loop 62 | } 63 | } 64 | 65 | select { 66 | case <-ctx.Done(): 67 | return 68 | default: 69 | } 70 | 71 | // 从数据源下载 72 | call(json.File, ParseComposerJsonWithOrigin(json)) 73 | } 74 | } 75 | 76 | var defaultComposerRepo = []common.RepoConfig{ 77 | {Url: "http://repo.packagist.org/p2"}, 78 | } 79 | 80 | func RegisterComposerRepo(repos ...common.RepoConfig) { 81 | newRepo := common.TrimRepo(repos...) 82 | if len(newRepo) > 0 { 83 | defaultComposerRepo = newRepo 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /opensca/sca/python/oss.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import json 4 | 5 | def parse_setup_py(setup_py_path): 6 | """解析setup.py文件""" 7 | 8 | with open(setup_py_path, "r", encoding='utf8') as f: 9 | pass_func = lambda **x: x 10 | try: 11 | import distutils 12 | distutils.core.setup = pass_func # type: ignore 13 | except Exception: 14 | pass 15 | try: 16 | import setuptools 17 | setuptools.setup = pass_func # type: ignore 18 | except Exception: 19 | pass 20 | 21 | # 获取setup参数 22 | args = {} 23 | code = re.sub(r'(?>opensca_end'.format(json.dumps(info))) 33 | 34 | if __name__ == "__main__": 35 | if len(sys.argv) > 1: 36 | parse_setup_py(sys.argv[1]) -------------------------------------------------------------------------------- /opensca/sca/python/pip.go: -------------------------------------------------------------------------------- 1 | package python 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "strings" 7 | 8 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 9 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 10 | ) 11 | 12 | func ParsePipfile(file *model.File) *model.DepGraph { 13 | 14 | pip := struct { 15 | DevPackages map[string]string `toml:"dev-packages"` 16 | Packages map[string]string `toml:"packages"` 17 | }{} 18 | 19 | root := &model.DepGraph{Path: file.Relpath()} 20 | 21 | _dep := model.NewDepGraphMap(nil, func(s ...string) *model.DepGraph { return &model.DepGraph{Name: s[0], Version: s[1]} }).LoadOrStore 22 | 23 | file.OpenReader(func(reader io.Reader) { 24 | if err := json.NewDecoder(reader).Decode(&pip); err != nil { 25 | logs.Warnf("unmarshal file %s err: %s", file.Relpath(), err) 26 | } 27 | }) 28 | 29 | for name, version := range pip.Packages { 30 | root.AppendChild(_dep(name, version)) 31 | } 32 | for name, version := range pip.DevPackages { 33 | dep := _dep(name, version) 34 | if dep == nil { 35 | continue 36 | } 37 | dep.Develop = true 38 | root.AppendChild(dep) 39 | } 40 | 41 | return root 42 | } 43 | 44 | func ParsePipfileLock(file *model.File) *model.DepGraph { 45 | 46 | lock := struct { 47 | Default map[string]struct { 48 | Version string `json:"version"` 49 | } `json:"default"` 50 | }{} 51 | 52 | root := &model.DepGraph{Path: file.Relpath()} 53 | 54 | _dep := model.NewDepGraphMap(nil, func(s ...string) *model.DepGraph { return &model.DepGraph{Name: s[0], Version: s[1]} }).LoadOrStore 55 | 56 | file.OpenReader(func(reader io.Reader) { 57 | if err := json.NewDecoder(reader).Decode(&lock); err != nil { 58 | logs.Warnf("unmarshal file %s err: %s", file.Relpath(), err) 59 | } 60 | }) 61 | 62 | for name, v := range lock.Default { 63 | version := strings.TrimPrefix(v.Version, "==") 64 | root.AppendChild(_dep(name, version)) 65 | } 66 | 67 | return root 68 | } 69 | 70 | func ParseRequirementTxt(file *model.File) *model.DepGraph { 71 | 72 | root := &model.DepGraph{Path: file.Relpath()} 73 | 74 | file.ReadLine(func(line string) { 75 | 76 | if strings.HasPrefix(line, `-r`) { 77 | return 78 | } 79 | 80 | if i := strings.Index(line, "#"); i != -1 { 81 | line = line[:i] 82 | } 83 | 84 | line = strings.TrimSpace(strings.Split(line, ";")[0]) 85 | if strings.ContainsAny(line, `#$%`) || len(line) == 0 { 86 | return 87 | } 88 | 89 | if i := strings.IndexAny(line, "=!<>~"); i == -1 { 90 | root.AppendChild(&model.DepGraph{Name: line}) 91 | } else { 92 | root.AppendChild(&model.DepGraph{Name: line[:i], Version: strings.TrimPrefix(line[i:], "==")}) 93 | } 94 | 95 | }) 96 | 97 | return root 98 | } 99 | 100 | func ParseRequirementIn(file *model.File) *model.DepGraph { 101 | 102 | root := &model.DepGraph{Path: file.Relpath()} 103 | 104 | file.ReadLine(func(line string) { 105 | 106 | if strings.HasPrefix(line, `#`) { 107 | return 108 | } 109 | 110 | words := strings.Fields(line) 111 | if len(words) > 0 { 112 | dep := &model.DepGraph{} 113 | dep.Name = words[0] 114 | dep.Version = strings.Join(words[1:], "") 115 | root.AppendChild(dep) 116 | } 117 | 118 | }) 119 | 120 | return root 121 | } 122 | -------------------------------------------------------------------------------- /opensca/sca/python/sca.go: -------------------------------------------------------------------------------- 1 | package python 2 | 3 | import ( 4 | "context" 5 | "path" 6 | "strings" 7 | 8 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 9 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/filter" 10 | ) 11 | 12 | type Sca struct{} 13 | 14 | func (sca Sca) Language() model.Language { 15 | return model.Lan_Python 16 | } 17 | 18 | func (sca Sca) Filter(relpath string) bool { 19 | return filter.PythonPipfileLock(relpath) || 20 | filter.PythonPipfile(relpath) || 21 | filter.PythonRequirementsIn(relpath) || 22 | filter.PythonRequirementsTxt(relpath) || 23 | filter.PythonSetup(relpath) 24 | } 25 | 26 | func (sca Sca) Sca(ctx context.Context, parent *model.File, files []*model.File, call model.ResCallback) { 27 | 28 | path2dir := func(relpath string) string { return path.Dir(strings.ReplaceAll(relpath, `\`, `/`)) } 29 | 30 | // 记录存在lock文件的目录 31 | lockSet := map[string]bool{} 32 | for _, file := range files { 33 | if filter.PythonPipfileLock(file.Relpath()) { 34 | lockSet[path2dir(file.Relpath())] = true 35 | } 36 | } 37 | 38 | // 记录使用pipenv解析过的目录 39 | pipSet := map[string]bool{} 40 | // 尝试使用pipenv解析 41 | for _, file := range files { 42 | if pipSet[path2dir(file.Relpath())] { 43 | continue 44 | } 45 | if filter.PythonPipfile(file.Relpath()) || 46 | filter.PythonRequirementsTxt(file.Relpath()) { 47 | } else { 48 | continue 49 | } 50 | root := ParsePythonWithEnv(ctx, file) 51 | if root == nil { 52 | continue 53 | } 54 | call(file, root) 55 | pipSet[path2dir(file.Relpath())] = true 56 | } 57 | 58 | // 静态解析 59 | for _, file := range files { 60 | if pipSet[path2dir(file.Relpath())] { 61 | continue 62 | } 63 | if filter.PythonPipfile(file.Relpath()) { 64 | if !lockSet[path2dir(file.Relpath())] { 65 | call(file, ParsePipfile(file)) 66 | } 67 | } else if filter.PythonPipfileLock(file.Relpath()) { 68 | call(file, ParsePipfileLock(file)) 69 | } else if filter.PythonRequirementsIn(file.Relpath()) { 70 | call(file, ParseRequirementIn(file)) 71 | } else if filter.PythonRequirementsTxt(file.Relpath()) { 72 | call(file, ParseRequirementTxt(file)) 73 | } else if filter.PythonSetup(file.Relpath()) { 74 | call(file, ParseSetup(file)) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /opensca/sca/python/setup.go: -------------------------------------------------------------------------------- 1 | package python 2 | 3 | import ( 4 | _ "embed" 5 | "encoding/json" 6 | "io" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "regexp" 11 | "strings" 12 | 13 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 14 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 15 | ) 16 | 17 | // ParseSetup 解析setup.py 18 | func ParseSetup(file *model.File) *model.DepGraph { 19 | 20 | // 尝试调用python解析 21 | root := ParseSetupPyWithPython(file) 22 | if root != nil && len(root.Children) > 0 { 23 | return root 24 | } 25 | 26 | root = &model.DepGraph{Path: file.Relpath()} 27 | 28 | // 静态解析 29 | file.OpenReader(func(reader io.Reader) { 30 | data, err := io.ReadAll(reader) 31 | if err != nil { 32 | return 33 | } 34 | reg := regexp.MustCompile(`install_requires\s*=\s*\[([^\]]+)\]`) 35 | requires := reg.FindStringSubmatch(string(data)) 36 | if len(requires) < 2 { 37 | return 38 | } 39 | model.ReadLineNoComment(strings.NewReader(requires[1]), model.PythonTypeComment, func(line string) { 40 | line = strings.Trim(strings.TrimSpace(line), `'",`) 41 | words := strings.Fields(line) 42 | if len(words) == 0 { 43 | return 44 | } 45 | name := words[0] 46 | var version string 47 | if len(words) > 1 { 48 | version = strings.Join(words[1:], "") 49 | } 50 | root.AppendChild(&model.DepGraph{ 51 | Name: name, 52 | Version: version, 53 | }) 54 | }) 55 | }) 56 | 57 | return root 58 | } 59 | 60 | //go:embed oss.py 61 | var ossPy []byte 62 | 63 | // oss.py 脚本输出的依赖结构 64 | type setupDep struct { 65 | Name string `json:"name"` 66 | Version string `json:"version"` 67 | License string `json:"license"` 68 | Packages []string `json:"packages"` 69 | InstallRequires []string `json:"install_requires"` 70 | Requires []string `json:"requires"` 71 | } 72 | 73 | func ParseSetupPyWithPython(file *model.File) *model.DepGraph { 74 | 75 | if _, err := exec.LookPath("python"); err != nil { 76 | return nil 77 | } 78 | 79 | dir := filepath.Dir(file.Abspath()) 80 | ossfile := filepath.Join(dir, "oss.py") 81 | 82 | // 创建 oss.py 83 | if err := os.WriteFile(ossfile, ossPy, 0777); err != nil { 84 | logs.Warn(err) 85 | return nil 86 | } 87 | 88 | // 解析 setup.py 89 | cmd := exec.Command("python", ossfile, file.Abspath()) 90 | out, _ := cmd.CombinedOutput() 91 | startTag, endTag := `opensca_start<<`, `>>opensca_end` 92 | startIndex, endIndex := strings.Index(string(out), startTag), strings.Index(string(out), endTag) 93 | if startIndex == -1 || endIndex == -1 { 94 | return nil 95 | } else { 96 | out = out[startIndex+len(startTag) : endIndex] 97 | } 98 | 99 | // 获取解析结果 100 | var dep setupDep 101 | if err := json.Unmarshal(out, &dep); err != nil { 102 | logs.Warn(err) 103 | return nil 104 | } 105 | 106 | root := &model.DepGraph{Name: dep.Name, Version: dep.Version, Path: file.Relpath()} 107 | root.AppendLicense(dep.License) 108 | 109 | for _, pkg := range [][]string{dep.Packages, dep.InstallRequires, dep.Requires} { 110 | for _, p := range pkg { 111 | index := strings.IndexAny(p, "=<>") 112 | var name, version string 113 | if index > -1 { 114 | name = p[:index] 115 | version = p[index:] 116 | } else { 117 | name = p 118 | } 119 | root.AppendChild(&model.DepGraph{Name: name, Version: version}) 120 | } 121 | } 122 | 123 | return root 124 | } 125 | -------------------------------------------------------------------------------- /opensca/sca/ruby/gem.go: -------------------------------------------------------------------------------- 1 | package ruby 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 7 | ) 8 | 9 | // ParseGemfileLock 解析Gemfile.lock文件 10 | func ParseGemfileLock(file *model.File) *model.DepGraph { 11 | 12 | // map[name]dep 13 | depMap := map[string]*model.DepGraph{} 14 | 15 | space4 := " " 16 | space6 := " " 17 | parseLine := func(line string) (name, version string) { 18 | line = strings.TrimSpace(line) 19 | i := strings.Index(line, " ") 20 | if i == -1 { 21 | name = line 22 | return 23 | } 24 | name = line[:i] 25 | version = strings.Trim(line[i+1:], "()") 26 | return 27 | } 28 | 29 | // 第一次记录依赖信息 30 | file.ReadLine(func(line string) { 31 | if !strings.HasPrefix(line, space4) || strings.HasPrefix(line, space6) { 32 | return 33 | } 34 | name, version := parseLine(line) 35 | if version == "" { 36 | return 37 | } 38 | depMap[name] = &model.DepGraph{ 39 | Name: name, 40 | Version: version, 41 | } 42 | }) 43 | 44 | // 第二次记录依赖关系 45 | var last string 46 | file.ReadLine(func(line string) { 47 | if strings.HasPrefix(line, space6) { 48 | name, _ := parseLine(line) 49 | depMap[last].AppendChild(depMap[name]) 50 | return 51 | } 52 | if strings.HasPrefix(line, space4) { 53 | last, _ = parseLine(line) 54 | return 55 | } 56 | }) 57 | 58 | root := &model.DepGraph{Path: file.Relpath()} 59 | for _, d := range depMap { 60 | if len(d.Parents) == 0 { 61 | root.AppendChild(d) 62 | } 63 | } 64 | 65 | return root 66 | } 67 | -------------------------------------------------------------------------------- /opensca/sca/ruby/sca.go: -------------------------------------------------------------------------------- 1 | package ruby 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 7 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/filter" 8 | ) 9 | 10 | type Sca struct{} 11 | 12 | func (sca Sca) Language() model.Language { 13 | return model.Lan_Ruby 14 | } 15 | 16 | func (sca Sca) Filter(relpath string) bool { 17 | return filter.RubyGemfileLock(relpath) 18 | } 19 | 20 | func (sca Sca) Sca(ctx context.Context, parent *model.File, files []*model.File, call model.ResCallback) { 21 | for _, file := range files { 22 | if filter.RubyGemfileLock(file.Relpath()) { 23 | call(file, ParseGemfileLock(file)) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /opensca/sca/rust/cargo.go: -------------------------------------------------------------------------------- 1 | package rust 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | 7 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 8 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 9 | 10 | "github.com/BurntSushi/toml" 11 | ) 12 | 13 | // ParseCargoLock 解析Cargo.lock文件 14 | func ParseCargoLock(file *model.File) *model.DepGraph { 15 | 16 | cargo := struct { 17 | Pkgs []struct { 18 | Name string `toml:"name"` 19 | Version string `toml:"version"` 20 | Dependencies []string `toml:"dependencies"` 21 | } `toml:"package"` 22 | }{} 23 | 24 | file.OpenReader(func(reader io.Reader) { 25 | _, err := toml.NewDecoder(reader).Decode(&cargo) 26 | if err != nil { 27 | logs.Warnf("parse %s fail:%s", file.Relpath(), err) 28 | } 29 | }) 30 | 31 | // 记录组件信息 32 | depMap := map[string]*model.DepGraph{} 33 | _dep := model.NewDepGraphMap(nil, func(s ...string) *model.DepGraph { 34 | return &model.DepGraph{ 35 | Name: s[0], 36 | Version: s[1], 37 | } 38 | }).LoadOrStore 39 | for _, c := range cargo.Pkgs { 40 | depMap[c.Name] = _dep(c.Name, c.Version) 41 | } 42 | 43 | // 记录依赖关系 44 | for _, c := range cargo.Pkgs { 45 | dep := _dep(c.Name, c.Version) 46 | for _, dependency := range c.Dependencies { 47 | i := strings.Index(dependency, " ") 48 | if i != -1 { 49 | name, version := dependency[:i], dependency[i+1:] 50 | dep.AppendChild(_dep(name, version)) 51 | } else { 52 | dep.AppendChild(depMap[dependency]) 53 | } 54 | } 55 | } 56 | 57 | root := &model.DepGraph{Path: file.Relpath()} 58 | for _, dep := range depMap { 59 | if len(dep.Parents) == 0 { 60 | root.AppendChild(dep) 61 | } 62 | } 63 | 64 | return root 65 | } 66 | -------------------------------------------------------------------------------- /opensca/sca/rust/sca.go: -------------------------------------------------------------------------------- 1 | package rust 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 7 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/filter" 8 | ) 9 | 10 | type Sca struct{} 11 | 12 | func (sca Sca) Language() model.Language { 13 | return model.Lan_Rust 14 | } 15 | 16 | func (sca Sca) Filter(relpath string) bool { 17 | return filter.RustCargoLock(relpath) 18 | } 19 | 20 | func (sca Sca) Sca(ctx context.Context, parent *model.File, files []*model.File, call model.ResCallback) { 21 | for _, f := range files { 22 | if filter.RustCargoLock(f.Relpath()) { 23 | root := ParseCargoLock(f) 24 | if root != nil && len(root.Children) > 0 { 25 | call(f, root) 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /opensca/sca/sbom/cdx.go: -------------------------------------------------------------------------------- 1 | package sbom 2 | 3 | import ( 4 | "encoding/json" 5 | "encoding/xml" 6 | "io" 7 | 8 | "github.com/CycloneDX/cyclonedx-go" 9 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 10 | ) 11 | 12 | func ParseCdxJson(f *model.File) *model.DepGraph { 13 | bom := &cyclonedx.BOM{} 14 | f.OpenReader(func(reader io.Reader) { 15 | json.NewDecoder(reader).Decode(&bom) 16 | }) 17 | return parseCdxBom(f, bom) 18 | } 19 | 20 | func ParseCdxXml(f *model.File) *model.DepGraph { 21 | bom := &cyclonedx.BOM{} 22 | f.OpenReader(func(reader io.Reader) { 23 | xml.NewDecoder(reader).Decode(&bom) 24 | }) 25 | return parseCdxBom(f, bom) 26 | } 27 | 28 | func parseCdxBom(f *model.File, bom *cyclonedx.BOM) *model.DepGraph { 29 | 30 | if bom == nil { 31 | return nil 32 | } 33 | 34 | if bom.BOMFormat == "" && bom.XMLNS == "" { 35 | return nil 36 | } 37 | 38 | if bom.Components == nil || len(*bom.Components) == 0 { 39 | return nil 40 | } 41 | 42 | depRefMap := map[string]*model.DepGraph{} 43 | _dep := model.NewDepGraphMap(func(s ...string) string { 44 | return s[0] 45 | }, func(s ...string) *model.DepGraph { 46 | return &model.DepGraph{ 47 | Vendor: s[1], 48 | Name: s[2], 49 | Version: s[3], 50 | } 51 | }).LoadOrStore 52 | 53 | for _, d := range *bom.Components { 54 | 55 | if d.PackageURL != "" { 56 | vendor, name, version, language := model.ParsePurl(d.PackageURL) 57 | if name != "" { 58 | dep := _dep(d.BOMRef, vendor, name, version) 59 | dep.Language = language 60 | depRefMap[d.BOMRef] = dep 61 | continue 62 | } 63 | } 64 | 65 | if d.Name != "" { 66 | depRefMap[d.BOMRef] = _dep(d.BOMRef, d.Author, d.Name, d.Version) 67 | } 68 | } 69 | 70 | if bom.Dependencies != nil { 71 | for _, d := range *bom.Dependencies { 72 | dep, ok := depRefMap[d.Ref] 73 | if !ok || d.Dependencies == nil { 74 | continue 75 | } 76 | for _, subRef := range *d.Dependencies { 77 | dep.AppendChild(depRefMap[subRef]) 78 | } 79 | } 80 | } 81 | 82 | root := &model.DepGraph{Path: f.Relpath()} 83 | for _, dep := range depRefMap { 84 | if len(dep.Parents) == 0 { 85 | root.AppendChild(dep) 86 | } 87 | } 88 | 89 | return root 90 | } 91 | -------------------------------------------------------------------------------- /opensca/sca/sbom/dpsbom.go: -------------------------------------------------------------------------------- 1 | package sbom 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | 7 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 8 | ) 9 | 10 | func ParseDpSbomJson(f *model.File) *model.DepGraph { 11 | doc := &model.DpSbomDocument{} 12 | f.OpenReader(func(reader io.Reader) { 13 | json.NewDecoder(reader).Decode(doc) 14 | }) 15 | return parseDpSbomDoc(f, doc) 16 | } 17 | 18 | func parseDpSbomDoc(f *model.File, doc *model.DpSbomDocument) *model.DepGraph { 19 | 20 | if doc == nil { 21 | return nil 22 | } 23 | 24 | depIdMap := map[string]*model.DepGraph{} 25 | _dep := model.NewDepGraphMap(func(s ...string) string { 26 | return s[0] 27 | }, func(s ...string) *model.DepGraph { 28 | vendor, name, version, language := model.ParsePurl(s[0]) 29 | return &model.DepGraph{ 30 | Vendor: vendor, 31 | Name: name, 32 | Version: version, 33 | Language: language, 34 | } 35 | }).LoadOrStore 36 | 37 | for _, pkg := range doc.Packages { 38 | dep := _dep(pkg.Identifier.Purl) 39 | dep.Licenses = pkg.License 40 | depIdMap[pkg.Identifier.Purl] = dep 41 | } 42 | 43 | for _, dependOn := range doc.Dependencies { 44 | parent, ok := depIdMap[dependOn.Ref] 45 | if !ok { 46 | continue 47 | } 48 | for _, dep := range dependOn.DependsOn { 49 | child, ok := depIdMap[dep.Target] 50 | if !ok { 51 | continue 52 | } 53 | parent.AppendChild(child) 54 | } 55 | } 56 | 57 | root := &model.DepGraph{Path: f.Relpath()} 58 | for _, dep := range depIdMap { 59 | if len(dep.Parents) == 0 { 60 | root.AppendChild(dep) 61 | } 62 | } 63 | 64 | return root 65 | } 66 | -------------------------------------------------------------------------------- /opensca/sca/sbom/dsdx.go: -------------------------------------------------------------------------------- 1 | package sbom 2 | 3 | import ( 4 | "encoding/json" 5 | "encoding/xml" 6 | "io" 7 | "strings" 8 | 9 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 10 | ) 11 | 12 | func ParseDsdx(f *model.File) *model.DepGraph { 13 | return parseDsdxDoc(f, ReadDsdx(f)) 14 | } 15 | 16 | func ParseDsdxJson(f *model.File) *model.DepGraph { 17 | doc := &model.DsdxDocument{} 18 | f.OpenReader(func(reader io.Reader) { 19 | json.NewDecoder(reader).Decode(doc) 20 | }) 21 | return parseDsdxDoc(f, doc) 22 | } 23 | 24 | func ParseDsdxXml(f *model.File) *model.DepGraph { 25 | doc := &model.DsdxDocument{} 26 | f.OpenReader(func(reader io.Reader) { 27 | xml.NewDecoder(reader).Decode(doc) 28 | }) 29 | return parseDsdxDoc(f, doc) 30 | } 31 | 32 | func parseDsdxDoc(f *model.File, doc *model.DsdxDocument) *model.DepGraph { 33 | 34 | if doc == nil || len(doc.Components) == 0 { 35 | return nil 36 | } 37 | 38 | depIdMap := map[string]*model.DepGraph{} 39 | _dep := model.NewDepGraphMap(func(s ...string) string { 40 | return s[0] 41 | }, func(s ...string) *model.DepGraph { 42 | return &model.DepGraph{ 43 | Vendor: s[1], 44 | Name: s[2], 45 | Version: s[3], 46 | } 47 | }).LoadOrStore 48 | 49 | for _, c := range doc.Components { 50 | dep := _dep(c.ID, c.Group, c.Name, c.Version) 51 | dep.Language = model.Language(c.Language) 52 | dep.Licenses = c.License 53 | depIdMap[c.ID] = dep 54 | } 55 | 56 | for parentId, childrenIds := range doc.Dependencies { 57 | parent, ok := depIdMap[parentId] 58 | if !ok { 59 | continue 60 | } 61 | for _, id := range childrenIds { 62 | parent.AppendChild(depIdMap[id]) 63 | } 64 | } 65 | 66 | root := &model.DepGraph{Path: f.Relpath()} 67 | for _, dep := range depIdMap { 68 | if len(dep.Parents) == 0 { 69 | root.AppendChild(dep) 70 | } 71 | } 72 | 73 | return root 74 | } 75 | 76 | // ReadDsdx 读取dsdx文件 77 | func ReadDsdx(f *model.File) *model.DsdxDocument { 78 | 79 | dsdx := &model.DsdxDocument{Dependencies: model.DsdxDependencies{}} 80 | 81 | // 记录依赖关系 82 | dependencies := map[string][]string{} 83 | // 记录dsdx中的tag信息 84 | tags := map[string]string{} 85 | 86 | checkAndSet := func(k, v string) { 87 | if _, ok := tags[k]; ok { 88 | dsdx.Components = append(dsdx.Components, model.DsdxComponent{ 89 | ID: tags["id"], 90 | Group: tags["group"], 91 | Name: tags["name"], 92 | Version: tags["version"], 93 | Language: tags["language"], 94 | }) 95 | tags = map[string]string{} 96 | } 97 | tags[k] = strings.TrimSpace(v) 98 | } 99 | 100 | f.ReadLine(func(line string) { 101 | i := strings.Index(line, ":") 102 | if strings.HasPrefix(line, "#") || i == -1 { 103 | return 104 | } 105 | k := strings.TrimSpace(line[:i]) 106 | v := strings.TrimSpace(line[i+1:]) 107 | switch k { 108 | case "ComponentID": 109 | checkAndSet("id", v) 110 | case "ComponentName": 111 | checkAndSet("name", v) 112 | case "ComponentGroup": 113 | checkAndSet("group", v) 114 | case "ComponentVersion": 115 | checkAndSet("version", v) 116 | case "ComponentLanguage": 117 | checkAndSet("language", v) 118 | case "Dependencies": 119 | json.Unmarshal([]byte(v), &dependencies) 120 | } 121 | }) 122 | checkAndSet("id", "") 123 | 124 | for parent, children := range dependencies { 125 | dsdx.Dependencies[parent] = children 126 | } 127 | 128 | return dsdx 129 | } 130 | -------------------------------------------------------------------------------- /opensca/sca/sbom/sca.go: -------------------------------------------------------------------------------- 1 | package sbom 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 7 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/filter" 8 | ) 9 | 10 | type Sca struct{} 11 | 12 | func (sca Sca) Language() model.Language { 13 | return model.Lan_None 14 | } 15 | 16 | func (sca Sca) Filter(relpath string) bool { 17 | return filter.SbomJson(relpath) || filter.SbomXml(relpath) || filter.SbomSpdx(relpath) || filter.SbomDsdx(relpath) 18 | } 19 | 20 | func (sca Sca) Sca(ctx context.Context, parent *model.File, files []*model.File, call model.ResCallback) { 21 | for _, file := range files { 22 | if filter.SbomSpdx(file.Relpath()) { 23 | call(file, ParseSpdx(file)) 24 | } 25 | if filter.SbomDsdx(file.Relpath()) { 26 | call(file, ParseDsdx(file)) 27 | } 28 | if filter.SbomDbSbom(file.Relpath()) { 29 | call(file, ParseDpSbomJson(file)) 30 | } 31 | if filter.SbomJson(file.Relpath()) { 32 | call(file, ParseSpdxJson(file)) 33 | call(file, ParseCdxJson(file)) 34 | call(file, ParseDsdxJson(file)) 35 | call(file, ParseDpSbomJson(file)) 36 | } 37 | if filter.SbomXml(file.Relpath()) { 38 | call(file, ParseSpdxXml(file)) 39 | call(file, ParseCdxXml(file)) 40 | call(file, ParseDsdxXml(file)) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /opensca/sca/sca.go: -------------------------------------------------------------------------------- 1 | package sca 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 7 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/erlang" 8 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/golang" 9 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/groovy" 10 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/java" 11 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/javascript" 12 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/php" 13 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/python" 14 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/ruby" 15 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/rust" 16 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/sbom" 17 | ) 18 | 19 | type Sca interface { 20 | Language() model.Language 21 | Filter(relpath string) bool 22 | Sca(ctx context.Context, parent *model.File, files []*model.File, call model.ResCallback) 23 | } 24 | 25 | var AllSca = []Sca{ 26 | python.Sca{}, 27 | javascript.Sca{}, 28 | golang.Sca{}, 29 | ruby.Sca{}, 30 | rust.Sca{}, 31 | erlang.Sca{}, 32 | php.Sca{}, 33 | java.Sca{}, 34 | groovy.Sca{}, 35 | sbom.Sca{}, 36 | } 37 | -------------------------------------------------------------------------------- /opensca/walk/magic.go: -------------------------------------------------------------------------------- 1 | package walk 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | "strings" 8 | 9 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 10 | ) 11 | 12 | type Magic []byte 13 | 14 | var ( 15 | M_ZIP = Magic{0x50, 0x4B, 0x03, 0x04} 16 | M_RAR = Magic{0x52, 0x61, 0x72, 0x21, 0x1A, 0x07} 17 | M_GZ = Magic{0x1F, 0x8B} 18 | M_BZ2 = Magic{0x42, 0x5A, 0x68} 19 | M_LZ4 = Magic{0x04, 0x22, 0x4D, 0x18} 20 | M_XZ = Magic{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00} 21 | M_AR = Magic{0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, 0x0A} 22 | M_7Z = Magic{0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C} 23 | ) 24 | 25 | // checkFileExt 检查文件后缀 26 | func checkFileExt(abspath string, exts ...string) bool { 27 | for _, ext := range exts { 28 | if ext == "" { 29 | continue 30 | } 31 | if strings.HasSuffix(abspath, ext) { 32 | return true 33 | } 34 | } 35 | return false 36 | } 37 | 38 | // checkFileHead 检查文件头 39 | func checkFileHead(abspath string, ms ...Magic) bool { 40 | reader, err := os.Open(abspath) 41 | if err != nil { 42 | logs.Warn(err) 43 | return false 44 | } 45 | defer reader.Close() 46 | for _, m := range ms { 47 | if len(m) == 0 { 48 | continue 49 | } 50 | h := make([]byte, len(m)) 51 | reader.Seek(0, io.SeekStart) 52 | reader.Read(h) 53 | reader.Seek(0, io.SeekStart) 54 | if bytes.Equal(m, h) { 55 | return true 56 | } 57 | } 58 | return false 59 | } 60 | -------------------------------------------------------------------------------- /opensca/walk/rar.go: -------------------------------------------------------------------------------- 1 | package walk 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 10 | 11 | "github.com/nwaples/rardecode" 12 | ) 13 | 14 | func xrar(ctx context.Context, filter ExtractFileFilter, input, output string) bool { 15 | 16 | if !checkFileHead(input, M_RAR) { 17 | return false 18 | } 19 | 20 | fr, err := rardecode.OpenReader(input, "") 21 | if err != nil { 22 | logs.Warn(err) 23 | return false 24 | } 25 | defer fr.Close() 26 | 27 | for { 28 | 29 | select { 30 | case <-ctx.Done(): 31 | return false 32 | default: 33 | } 34 | 35 | fh, err := fr.Next() 36 | if err == io.EOF { 37 | break 38 | } 39 | if err != nil { 40 | logs.Warn(err) 41 | break 42 | } 43 | 44 | fp := filepath.Join(output, fh.Name) 45 | if fh.IsDir { 46 | os.MkdirAll(fp, 0755) 47 | continue 48 | } 49 | 50 | if filter != nil && !filter(fp) { 51 | continue 52 | } 53 | 54 | os.MkdirAll(filepath.Dir(fp), 0777) 55 | fw, err := os.Create(fp) 56 | if err != nil { 57 | logs.Warn(err) 58 | continue 59 | } 60 | 61 | io.Copy(fw, fr) 62 | fw.Close() 63 | } 64 | return true 65 | } 66 | -------------------------------------------------------------------------------- /opensca/walk/tar.go: -------------------------------------------------------------------------------- 1 | package walk 2 | 3 | import ( 4 | "archive/tar" 5 | "compress/bzip2" 6 | "compress/gzip" 7 | "context" 8 | "io" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 14 | ) 15 | 16 | func xtar(ctx context.Context, filter ExtractFileFilter, input, output string) bool { 17 | 18 | if !checkFileExt(input, ".tar") { 19 | return false 20 | } 21 | 22 | f, err := os.Open(input) 23 | if err != nil { 24 | logs.Warn(err) 25 | return false 26 | } 27 | defer f.Close() 28 | 29 | fr := tar.NewReader(f) 30 | for { 31 | 32 | select { 33 | case <-ctx.Done(): 34 | return false 35 | default: 36 | } 37 | 38 | fh, err := fr.Next() 39 | if err == io.EOF { 40 | break 41 | } 42 | if err != nil { 43 | logs.Warn(err) 44 | break 45 | } 46 | 47 | fp := filepath.Join(output, fh.Name) 48 | 49 | // avoid zip slip 50 | if !strings.HasPrefix(fp, filepath.Clean(output)+string(os.PathSeparator)) { 51 | logs.Warn("Invalid file path: %s", fp) 52 | continue 53 | } 54 | 55 | if fh.Typeflag == tar.TypeDir { 56 | os.MkdirAll(fp, 0755) 57 | continue 58 | } 59 | 60 | if filter != nil && !filter(fp) { 61 | continue 62 | } 63 | 64 | os.MkdirAll(filepath.Dir(fp), 0777) 65 | fw, err := os.Create(fp) 66 | if err != nil { 67 | logs.Warn(err) 68 | continue 69 | } 70 | 71 | io.Copy(fw, fr) 72 | fw.Close() 73 | } 74 | return true 75 | } 76 | 77 | func xgz(input, output string) bool { 78 | 79 | if !checkFileHead(input, M_GZ) { 80 | return false 81 | } 82 | 83 | f, err := os.Open(input) 84 | if err != nil { 85 | logs.Warn(err) 86 | return false 87 | } 88 | defer f.Close() 89 | 90 | fr, err := gzip.NewReader(f) 91 | if err != nil { 92 | logs.Warn(err) 93 | return false 94 | } 95 | defer fr.Close() 96 | 97 | fp := filepath.Join(output, strings.TrimSuffix(filepath.Base(input), filepath.Ext(input))) 98 | fw, err := os.Create(fp) 99 | if err != nil { 100 | logs.Warn(err) 101 | return false 102 | } 103 | 104 | _, err = io.Copy(fw, fr) 105 | fw.Close() 106 | 107 | return err == nil 108 | } 109 | 110 | func xbz2(input, output string) bool { 111 | 112 | if !checkFileHead(input, M_BZ2) { 113 | return false 114 | } 115 | 116 | f, err := os.Open(input) 117 | if err != nil { 118 | logs.Warn(err) 119 | return false 120 | } 121 | defer f.Close() 122 | 123 | fr := bzip2.NewReader(f) 124 | 125 | fp := filepath.Join(output, strings.TrimSuffix(filepath.Base(input), filepath.Ext(input))) 126 | fw, err := os.Create(fp) 127 | if err != nil { 128 | logs.Warn(err) 129 | return false 130 | } 131 | 132 | _, err = io.Copy(fw, fr) 133 | fw.Close() 134 | 135 | return err == nil 136 | } 137 | -------------------------------------------------------------------------------- /opensca/walk/zip.go: -------------------------------------------------------------------------------- 1 | package walk 2 | 3 | import ( 4 | "archive/zip" 5 | "bytes" 6 | "context" 7 | "io" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/common" 13 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 14 | 15 | "github.com/axgle/mahonia" 16 | ) 17 | 18 | func xzip(ctx context.Context, filter ExtractFileFilter, input, output string) bool { 19 | 20 | if !checkFileHead(input, M_ZIP) { 21 | return false 22 | } 23 | 24 | rf, err := zip.OpenReader(input) 25 | if err != nil { 26 | logs.Warn(err) 27 | return false 28 | } 29 | defer rf.Close() 30 | 31 | for _, f := range rf.File { 32 | 33 | select { 34 | case <-ctx.Done(): 35 | return false 36 | default: 37 | } 38 | 39 | if f.FileInfo().IsDir() { 40 | continue 41 | } 42 | 43 | fp := filepath.Join(output, f.Name) 44 | 45 | if f.Flags == 0 { 46 | gbk := mahonia.NewDecoder("gbk").ConvertString(f.Name) 47 | _, cdata, _ := mahonia.NewDecoder("utf-8").Translate([]byte(gbk), true) 48 | fp = filepath.Join(output, string(cdata)) 49 | } 50 | 51 | // avoid zip slip 52 | if !strings.HasPrefix(fp, filepath.Clean(output)+string(os.PathSeparator)) { 53 | logs.Warn("Invalid file path: %s", fp) 54 | continue 55 | } 56 | 57 | if filter != nil && !filter(fp) { 58 | continue 59 | } 60 | 61 | fr, err := f.Open() 62 | if err != nil { 63 | logs.Warn(err) 64 | continue 65 | } 66 | 67 | os.MkdirAll(filepath.Dir(fp), 0777) 68 | fw, err := os.Create(fp) 69 | if err != nil { 70 | logs.Warn(err) 71 | fr.Close() // 提前退出时手动关闭 72 | continue 73 | } 74 | 75 | _, err = io.Copy(fw, fr) 76 | if err != nil { 77 | logs.Warn(err) 78 | } 79 | 80 | fw.Close() 81 | fr.Close() 82 | } 83 | return true 84 | } 85 | 86 | func xjar(ctx context.Context, filter ExtractFileFilter, input, output string) bool { 87 | 88 | if !checkFileExt(input, ".jar") { 89 | return false 90 | } 91 | 92 | // 生成临时文件 93 | tempf := common.CreateTemp("jar") 94 | defer os.Remove(tempf.Name()) 95 | 96 | data, err := os.ReadFile(input) 97 | if err != nil { 98 | logs.Warn(err) 99 | tempf.Close() 100 | return false 101 | } 102 | 103 | // 剔除可执行jar包前的bash脚本 104 | i := bytes.Index(data, M_ZIP) 105 | if i == -1 { 106 | tempf.Close() 107 | return false 108 | } 109 | 110 | tempf.Write(data[i:]) 111 | tempf.Close() 112 | 113 | return xzip(ctx, filter, tempf.Name(), output) 114 | } 115 | -------------------------------------------------------------------------------- /resources/DetectionProcess-zh_CN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/resources/DetectionProcess-zh_CN.png -------------------------------------------------------------------------------- /resources/DetectionProcess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/resources/DetectionProcess.png -------------------------------------------------------------------------------- /resources/jenkins-freestyle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/resources/jenkins-freestyle.png -------------------------------------------------------------------------------- /resources/jenkins-postbuild.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/resources/jenkins-postbuild.png -------------------------------------------------------------------------------- /resources/jenkins-view-html-report.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/resources/jenkins-view-html-report.gif -------------------------------------------------------------------------------- /resources/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/resources/wechat.png -------------------------------------------------------------------------------- /resources/xcheck_function.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/resources/xcheck_function.jpg -------------------------------------------------------------------------------- /resources/xcheck_marketplace.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/resources/xcheck_marketplace.jpg -------------------------------------------------------------------------------- /resources/xcheck_process.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XmirrorSecurity/OpenSCA-cli/1e81968bea174a75c4f4f7ced7eec3e8954951ad/resources/xcheck_process.jpg -------------------------------------------------------------------------------- /test/java/1/mod/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.foo 7 | demo 8 | 1.0 9 | 10 | mod 11 | 12 | 13 | 4.3.6.RELEASE 14 | 15 | 16 | 17 | 18 | org.springframework 19 | spring-context 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/java/1/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.foo 7 | demo 8 | 1.0 9 | pom 10 | 11 | 12 | 4.3.7.RELEASE 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 1.5.2.RELEASE 19 | 20 | 21 | 22 | 23 | 24 | org.springframework 25 | spring-expression 26 | ${exp.version} 27 | 28 | 29 | 30 | 31 | 32 | mod 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/java/10/mod/mod2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.foo 7 | mod 8 | ${pppp} 9 | 10 | mod2 11 | ${pppp} 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/java/10/mod/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.foo 7 | demo 8 | 1.0 9 | 10 | 11 | pom 12 | 13 | mod 14 | ${pppp} 15 | 16 | 17 | mod2 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/java/10/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.foo 7 | demo 8 | 1.0 9 | pom 10 | 11 | 12 | 2.0 13 | ${modVersion} 14 | 15 | 16 | 17 | mod 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/java/11/b/c/d/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com 7 | c 8 | ${revision} 9 | ../pom.xml 10 | 11 | 12 | d 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/java/11/b/c/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com 7 | b 8 | ${revision} 9 | ../pom.xml 10 | 11 | pom 12 | c 13 | 14 | 15 | ${xx} 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/java/11/b/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com 6 | a 7 | ${revision} 8 | ../pom.xml 9 | 10 | b 11 | pom 12 | 13 | 14 | 1.0 15 | 16 | 17 | 18 | 19 | com 20 | xx 21 | ${revision} 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/java/11/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.0.0 5 | 6 | com 7 | a 8 | ${revision} 9 | pom 10 | 11 | 12 | 2.0 13 | ${vv} 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/java/12/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | my.foo 7 | 1.0 8 | demo 9 | 10 | 11 | 12 | com.google.guava 13 | guava 14 | 22.0 15 | 16 | 17 | com.google.code.findbugs 18 | * 19 | 20 | 21 | org.codehaus.mojo 22 | * 23 | 24 | 25 | com.google.j2objc 26 | * 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/java/13/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | my.foo 7 | 1.0 8 | demo 9 | 10 | 11 | 12 | org.jboss.resteasy 13 | resteasy-jaxrs 14 | 3.15.6.Final 15 | 16 | 17 | org.jboss.spec.javax.ws.rs 18 | * 19 | 20 | 21 | org.jboss.spec.javax.xml.bind 22 | * 23 | 24 | 25 | org.reactivestreams 26 | * 27 | 28 | 29 | jakarta.validation 30 | * 31 | 32 | 33 | org.jboss.spec.javax.annotation 34 | * 35 | 36 | 37 | com.sun.activation 38 | * 39 | 40 | 41 | org.apache.httpcomponents 42 | * 43 | 44 | 45 | com.github.stephenc.jcip 46 | * 47 | 48 | 49 | org.jboss.logging 50 | * 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /test/java/14/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | my.foo 7 | 1.0 8 | demo 9 | 10 | 11 | 12 | org.glassfish.jaxb 13 | jaxb-runtime 14 | 2.3.3-b02 15 | 16 | 17 | jakarta.xml.bind 18 | * 19 | 20 | 21 | com.sun.istack 22 | * 23 | 24 | 25 | com.sun.activation 26 | * 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/java/15/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | 7 | my.foo 8 | 1.0 9 | demo 10 | 11 | 12 | 13 | 14 | com.fasterxml.jackson.datatype 15 | jackson-datatype-jsr310 16 | 2.17.0 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/java/16/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | org.example 5 | demo 6 | 1.0 7 | 8 | 9 | 10 | com.aliyun 11 | tea 12 | test 13 | 14 | 15 | * 16 | * 17 | 18 | 19 | 20 | 21 | com.aliyun 22 | alibabacloud-dkms-gcs-sdk 23 | 0.5.2 24 | 25 | 26 | com.squareup.okhttp3 27 | okhttp 28 | 29 | 30 | com.aliyun 31 | alibabacloud-dkms-gcs-openapi 32 | 33 | 34 | com.aliyun 35 | alibabacloud-dkms-gcs-openapi-util 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | com.aliyun 44 | alibabacloud-dkms-gcs-sdk 45 | 0.5.2 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /test/java/17/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.springframework.boot 6 | spring-boot-starter-parent 7 | 2.3.12.RELEASE 8 | 9 | 10 | foo 11 | demo 12 | pom 13 | 1.0 14 | 15 | 16 | 3.1.1 17 | 18 | 19 | 20 | 21 | 22 | org.apache.dubbo 23 | dubbo-bom 24 | ${dubbo.version} 25 | pom 26 | import 27 | 28 | 29 | 30 | 31 | 32 | 33 | org.apache.logging.log4j 34 | log4j-core 35 | 36 | 37 | org.apache.logging.log4j 38 | log4j-api 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/java/2/mod/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.foo 7 | demo 8 | 1.0 9 | 10 | mod 11 | 12 | 13 | 4.3.6.RELEASE 14 | 15 | 16 | 17 | 18 | org.springframework 19 | spring-context 20 | 21 | 22 | org.springframework 23 | spring-aop 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/java/2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.foo 7 | demo 8 | 1.0 9 | pom 10 | 11 | 12 | 4.3.7.RELEASE 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 1.5.2.RELEASE 19 | 20 | 21 | 22 | 23 | 24 | org.springframework 25 | spring-expression 26 | ${exp.version} 27 | 28 | 29 | 30 | 31 | 32 | mod 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/java/3/mod/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.foo 7 | demo 8 | 1.0 9 | 10 | mod 11 | 12 | 13 | 14 | org.springframework 15 | spring-context 16 | 4.3.6.RELEASE 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/java/3/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.foo 7 | demo 8 | 1.0 9 | pom 10 | 11 | 12 | org.springframework.boot 13 | spring-boot-starter-parent 14 | 1.5.2.RELEASE 15 | 16 | 17 | 18 | 19 | 20 | org.springframework 21 | spring-expression 22 | 4.3.5.RELEASE 23 | test 24 | 25 | 26 | 27 | 28 | 29 | mod 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/java/4/mod/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.foo 7 | demo 8 | 1.0 9 | 10 | mod 11 | 12 | 13 | 4.3.4.RELEASE 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/java/4/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.foo 7 | demo 8 | 1.0 9 | pom 10 | 11 | 12 | org.springframework.boot 13 | spring-boot-starter-parent 14 | 1.5.2.RELEASE 15 | 16 | 17 | 18 | 4.3.6.RELEASE 19 | 20 | 21 | 22 | 23 | 24 | org.springframework 25 | spring-expression 26 | ${tt.v} 27 | 28 | 29 | org.springframework 30 | spring-core 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | org.springframework 40 | spring-expression 41 | 42 | 43 | 44 | 45 | mod 46 | 47 | 48 | -------------------------------------------------------------------------------- /test/java/5/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | org.jetbrains.kotlin 8 | kotlin-bom 9 | 1.6.21 10 | 11 | 12 | my.foo 13 | 1.4.10 14 | demo 15 | 16 | 17 | 18 | org.jetbrains.kotlin 19 | kotlin-stdlib 20 | ${kotlin.version} 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/java/6/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | my.foo 7 | 1.4.10 8 | demo 9 | 10 | 11 | 1.6.20 12 | 13 | 14 | 15 | 16 | org.jetbrains.kotlin 17 | kotlin-stdlib 18 | 19 | 20 | 21 | 22 | 23 | 24 | org.jetbrains.kotlin 25 | kotlin-stdlib-common 26 | ${kotlin.version} 27 | 28 | 29 | org.jetbrains.kotlin 30 | kotlin-bom 31 | 1.6.21 32 | import 33 | pom 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/java/7/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | my.foo 7 | 1.4.10 8 | demo 9 | 10 | 11 | 12 | 13 | org.jetbrains.kotlin 14 | kotlin-stdlib 15 | 1.6.21 16 | 17 | 18 | 19 | org.jetbrains.kotlin 20 | kotlin-stdlib 21 | 1.6.20 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/java/8/mod/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.foo 7 | mod 8 | 1.0 9 | 10 | 11 | 2.0.3 12 | 13 | 14 | 15 | 16 | com.alibaba.nacos 17 | nacos-all 18 | ${test-alibaba.version} 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/java/8/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.foo 7 | demo 8 | 1.0 9 | pom 10 | 11 | 12 | 2.0.4 13 | 14 | 15 | 16 | 17 | com.foo 18 | mod 19 | 1.0 20 | 21 | 22 | 23 | 24 | mod 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/java/9/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | my.foo 7 | 1.0 8 | demo 9 | 10 | 11 | 12 | 13 | org.springframework.boot 14 | spring-boot-starter-json 15 | 2.7.14 16 | 17 | 18 | org.springframework.boot 19 | * 20 | 21 | 22 | org.springframework 23 | * 24 | 25 | 26 | com.fasterxml.jackson.datatype 27 | * 28 | 29 | 30 | com.fasterxml.jackson.module 31 | * 32 | 33 | 34 | 35 | 36 | 37 | com.alibaba.nacos 38 | nacos-client 39 | 2.0.4 40 | 41 | 42 | org.apache.httpcomponents 43 | * 44 | 45 | 46 | commons-codec 47 | * 48 | 49 | 50 | org.reflections 51 | * 52 | 53 | 54 | com.google.guava 55 | * 56 | 57 | 58 | org.javassist 59 | * 60 | 61 | 62 | io.prometheus 63 | * 64 | 65 | 66 | org.yaml 67 | * 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /test/javascript/1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-test", 3 | "version": "1.0.1", 4 | "main": "none", 5 | "dependencies": { 6 | "cliui": "^6.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/javascript/2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-test", 3 | "version": "1.0.1", 4 | "main": "none", 5 | "dependencies": { 6 | "cliui": "^6.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/javascript/3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-test", 3 | "version": "1.0.1", 4 | "main": "none", 5 | "dependencies": { 6 | "cliui": "^6.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/javascript/3/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | ansi-regex@^5.0.1: 6 | version "5.0.1" 7 | resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 8 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 9 | 10 | ansi-styles@^4.0.0: 11 | version "4.3.0" 12 | resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 13 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 14 | dependencies: 15 | color-convert "^2.0.1" 16 | 17 | cliui@^6.0.0: 18 | version "6.0.0" 19 | resolved "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" 20 | integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== 21 | dependencies: 22 | string-width "^4.2.0" 23 | strip-ansi "^6.0.0" 24 | wrap-ansi "^6.2.0" 25 | 26 | color-convert@^2.0.1: 27 | version "2.0.1" 28 | resolved "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 29 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 30 | dependencies: 31 | color-name "~1.1.4" 32 | 33 | color-name@~1.1.4: 34 | version "1.1.4" 35 | resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 36 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 37 | 38 | emoji-regex@^8.0.0: 39 | version "8.0.0" 40 | resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 41 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 42 | 43 | is-fullwidth-code-point@^3.0.0: 44 | version "3.0.0" 45 | resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 46 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 47 | 48 | string-width@^4.1.0, string-width@^4.2.0: 49 | version "4.2.3" 50 | resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" 51 | integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== 52 | dependencies: 53 | emoji-regex "^8.0.0" 54 | is-fullwidth-code-point "^3.0.0" 55 | strip-ansi "^6.0.1" 56 | 57 | strip-ansi@^6.0.0, strip-ansi@^6.0.1: 58 | version "6.0.1" 59 | resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 60 | integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 61 | dependencies: 62 | ansi-regex "^5.0.1" 63 | 64 | wrap-ansi@^6.2.0: 65 | version "6.2.0" 66 | resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" 67 | integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== 68 | dependencies: 69 | ansi-styles "^4.0.0" 70 | string-width "^4.1.0" 71 | strip-ansi "^6.0.0" 72 | -------------------------------------------------------------------------------- /test/javascript/4/node_modules/ansi-regex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ansi-regex", 3 | "version": "5.0.1", 4 | "description": "Regular expression for matching ANSI escape codes", 5 | "license": "MIT", 6 | "repository": "chalk/ansi-regex", 7 | "author": { 8 | "name": "Sindre Sorhus", 9 | "email": "sindresorhus@gmail.com", 10 | "url": "sindresorhus.com" 11 | }, 12 | "engines": { 13 | "node": ">=8" 14 | }, 15 | "scripts": { 16 | "test": "xo && ava && tsd", 17 | "view-supported": "node fixtures/view-codes.js" 18 | }, 19 | "files": [ 20 | "index.js", 21 | "index.d.ts" 22 | ], 23 | "keywords": [ 24 | "ansi", 25 | "styles", 26 | "color", 27 | "colour", 28 | "colors", 29 | "terminal", 30 | "console", 31 | "cli", 32 | "string", 33 | "tty", 34 | "escape", 35 | "formatting", 36 | "rgb", 37 | "256", 38 | "shell", 39 | "xterm", 40 | "command-line", 41 | "text", 42 | "regex", 43 | "regexp", 44 | "re", 45 | "match", 46 | "test", 47 | "find", 48 | "pattern" 49 | ], 50 | "devDependencies": { 51 | "ava": "^2.4.0", 52 | "tsd": "^0.9.0", 53 | "xo": "^0.25.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/javascript/4/node_modules/ansi-styles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ansi-styles", 3 | "version": "4.3.0", 4 | "description": "ANSI escape codes for styling strings in the terminal", 5 | "license": "MIT", 6 | "repository": "chalk/ansi-styles", 7 | "funding": "https://github.com/chalk/ansi-styles?sponsor=1", 8 | "author": { 9 | "name": "Sindre Sorhus", 10 | "email": "sindresorhus@gmail.com", 11 | "url": "sindresorhus.com" 12 | }, 13 | "engines": { 14 | "node": ">=8" 15 | }, 16 | "scripts": { 17 | "test": "xo && ava && tsd", 18 | "screenshot": "svg-term --command='node screenshot' --out=screenshot.svg --padding=3 --width=55 --height=3 --at=1000 --no-cursor" 19 | }, 20 | "files": [ 21 | "index.js", 22 | "index.d.ts" 23 | ], 24 | "keywords": [ 25 | "ansi", 26 | "styles", 27 | "color", 28 | "colour", 29 | "colors", 30 | "terminal", 31 | "console", 32 | "cli", 33 | "string", 34 | "tty", 35 | "escape", 36 | "formatting", 37 | "rgb", 38 | "256", 39 | "shell", 40 | "xterm", 41 | "log", 42 | "logging", 43 | "command-line", 44 | "text" 45 | ], 46 | "dependencies": { 47 | "color-convert": "^2.0.1" 48 | }, 49 | "devDependencies": { 50 | "@types/color-convert": "^1.9.0", 51 | "ava": "^2.3.0", 52 | "svg-term-cli": "^2.1.1", 53 | "tsd": "^0.11.0", 54 | "xo": "^0.25.3" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/javascript/4/node_modules/cliui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cliui", 3 | "version": "6.0.0", 4 | "description": "easily create complex multi-column command-line-interfaces", 5 | "main": "index.js", 6 | "scripts": { 7 | "pretest": "standard", 8 | "test": "nyc mocha", 9 | "coverage": "nyc --reporter=text-lcov mocha | coveralls" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "http://github.com/yargs/cliui.git" 14 | }, 15 | "config": { 16 | "blanket": { 17 | "pattern": [ 18 | "index.js" 19 | ], 20 | "data-cover-never": [ 21 | "node_modules", 22 | "test" 23 | ], 24 | "output-reporter": "spec" 25 | } 26 | }, 27 | "standard": { 28 | "ignore": [ 29 | "**/example/**" 30 | ], 31 | "globals": [ 32 | "it" 33 | ] 34 | }, 35 | "keywords": [ 36 | "cli", 37 | "command-line", 38 | "layout", 39 | "design", 40 | "console", 41 | "wrap", 42 | "table" 43 | ], 44 | "author": "Ben Coe ", 45 | "license": "ISC", 46 | "dependencies": { 47 | "string-width": "^4.2.0", 48 | "strip-ansi": "^6.0.0", 49 | "wrap-ansi": "^6.2.0" 50 | }, 51 | "devDependencies": { 52 | "chai": "^4.2.0", 53 | "chalk": "^3.0.0", 54 | "coveralls": "^3.0.3", 55 | "mocha": "^6.2.2", 56 | "nyc": "^14.1.1", 57 | "standard": "^12.0.1" 58 | }, 59 | "files": [ 60 | "index.js" 61 | ], 62 | "engine": { 63 | "node": ">=8" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/javascript/4/node_modules/color-convert/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "color-convert", 3 | "description": "Plain color conversion functions", 4 | "version": "2.0.1", 5 | "author": "Heather Arthur ", 6 | "license": "MIT", 7 | "repository": "Qix-/color-convert", 8 | "scripts": { 9 | "pretest": "xo", 10 | "test": "node test/basic.js" 11 | }, 12 | "engines": { 13 | "node": ">=7.0.0" 14 | }, 15 | "keywords": [ 16 | "color", 17 | "colour", 18 | "convert", 19 | "converter", 20 | "conversion", 21 | "rgb", 22 | "hsl", 23 | "hsv", 24 | "hwb", 25 | "cmyk", 26 | "ansi", 27 | "ansi16" 28 | ], 29 | "files": [ 30 | "index.js", 31 | "conversions.js", 32 | "route.js" 33 | ], 34 | "xo": { 35 | "rules": { 36 | "default-case": 0, 37 | "no-inline-comments": 0, 38 | "operator-linebreak": 0 39 | } 40 | }, 41 | "devDependencies": { 42 | "chalk": "^2.4.2", 43 | "xo": "^0.24.0" 44 | }, 45 | "dependencies": { 46 | "color-name": "~1.1.4" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/javascript/4/node_modules/color-name/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "color-name", 3 | "version": "1.1.4", 4 | "description": "A list of color names and its values", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js" 8 | ], 9 | "scripts": { 10 | "test": "node test.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git@github.com:colorjs/color-name.git" 15 | }, 16 | "keywords": [ 17 | "color-name", 18 | "color", 19 | "color-keyword", 20 | "keyword" 21 | ], 22 | "author": "DY ", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/colorjs/color-name/issues" 26 | }, 27 | "homepage": "https://github.com/colorjs/color-name" 28 | } 29 | -------------------------------------------------------------------------------- /test/javascript/4/node_modules/emoji-regex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emoji-regex", 3 | "version": "8.0.0", 4 | "description": "A regular expression to match all Emoji-only symbols as per the Unicode Standard.", 5 | "homepage": "https://mths.be/emoji-regex", 6 | "main": "index.js", 7 | "types": "index.d.ts", 8 | "keywords": [ 9 | "unicode", 10 | "regex", 11 | "regexp", 12 | "regular expressions", 13 | "code points", 14 | "symbols", 15 | "characters", 16 | "emoji" 17 | ], 18 | "license": "MIT", 19 | "author": { 20 | "name": "Mathias Bynens", 21 | "url": "https://mathiasbynens.be/" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/mathiasbynens/emoji-regex.git" 26 | }, 27 | "bugs": "https://github.com/mathiasbynens/emoji-regex/issues", 28 | "files": [ 29 | "LICENSE-MIT.txt", 30 | "index.js", 31 | "index.d.ts", 32 | "text.js", 33 | "es2015/index.js", 34 | "es2015/text.js" 35 | ], 36 | "scripts": { 37 | "build": "rm -rf -- es2015; babel src -d .; NODE_ENV=es2015 babel src -d ./es2015; node script/inject-sequences.js", 38 | "test": "mocha", 39 | "test:watch": "npm run test -- --watch" 40 | }, 41 | "devDependencies": { 42 | "@babel/cli": "^7.2.3", 43 | "@babel/core": "^7.3.4", 44 | "@babel/plugin-proposal-unicode-property-regex": "^7.2.0", 45 | "@babel/preset-env": "^7.3.4", 46 | "mocha": "^6.0.2", 47 | "regexgen": "^1.3.0", 48 | "unicode-12.0.0": "^0.7.9" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/javascript/4/node_modules/is-fullwidth-code-point/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "is-fullwidth-code-point", 3 | "version": "3.0.0", 4 | "description": "Check if the character represented by a given Unicode code point is fullwidth", 5 | "license": "MIT", 6 | "repository": "sindresorhus/is-fullwidth-code-point", 7 | "author": { 8 | "name": "Sindre Sorhus", 9 | "email": "sindresorhus@gmail.com", 10 | "url": "sindresorhus.com" 11 | }, 12 | "engines": { 13 | "node": ">=8" 14 | }, 15 | "scripts": { 16 | "test": "xo && ava && tsd-check" 17 | }, 18 | "files": [ 19 | "index.js", 20 | "index.d.ts" 21 | ], 22 | "keywords": [ 23 | "fullwidth", 24 | "full-width", 25 | "full", 26 | "width", 27 | "unicode", 28 | "character", 29 | "string", 30 | "codepoint", 31 | "code", 32 | "point", 33 | "is", 34 | "detect", 35 | "check" 36 | ], 37 | "devDependencies": { 38 | "ava": "^1.3.1", 39 | "tsd-check": "^0.5.0", 40 | "xo": "^0.24.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/javascript/4/node_modules/string-width/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "string-width", 3 | "version": "4.2.3", 4 | "description": "Get the visual width of a string - the number of columns required to display it", 5 | "license": "MIT", 6 | "repository": "sindresorhus/string-width", 7 | "author": { 8 | "name": "Sindre Sorhus", 9 | "email": "sindresorhus@gmail.com", 10 | "url": "sindresorhus.com" 11 | }, 12 | "engines": { 13 | "node": ">=8" 14 | }, 15 | "scripts": { 16 | "test": "xo && ava && tsd" 17 | }, 18 | "files": [ 19 | "index.js", 20 | "index.d.ts" 21 | ], 22 | "keywords": [ 23 | "string", 24 | "character", 25 | "unicode", 26 | "width", 27 | "visual", 28 | "column", 29 | "columns", 30 | "fullwidth", 31 | "full-width", 32 | "full", 33 | "ansi", 34 | "escape", 35 | "codes", 36 | "cli", 37 | "command-line", 38 | "terminal", 39 | "console", 40 | "cjk", 41 | "chinese", 42 | "japanese", 43 | "korean", 44 | "fixed-width" 45 | ], 46 | "dependencies": { 47 | "emoji-regex": "^8.0.0", 48 | "is-fullwidth-code-point": "^3.0.0", 49 | "strip-ansi": "^6.0.1" 50 | }, 51 | "devDependencies": { 52 | "ava": "^1.4.1", 53 | "tsd": "^0.7.1", 54 | "xo": "^0.24.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/javascript/4/node_modules/strip-ansi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "strip-ansi", 3 | "version": "6.0.1", 4 | "description": "Strip ANSI escape codes from a string", 5 | "license": "MIT", 6 | "repository": "chalk/strip-ansi", 7 | "author": { 8 | "name": "Sindre Sorhus", 9 | "email": "sindresorhus@gmail.com", 10 | "url": "sindresorhus.com" 11 | }, 12 | "engines": { 13 | "node": ">=8" 14 | }, 15 | "scripts": { 16 | "test": "xo && ava && tsd" 17 | }, 18 | "files": [ 19 | "index.js", 20 | "index.d.ts" 21 | ], 22 | "keywords": [ 23 | "strip", 24 | "trim", 25 | "remove", 26 | "ansi", 27 | "styles", 28 | "color", 29 | "colour", 30 | "colors", 31 | "terminal", 32 | "console", 33 | "string", 34 | "tty", 35 | "escape", 36 | "formatting", 37 | "rgb", 38 | "256", 39 | "shell", 40 | "xterm", 41 | "log", 42 | "logging", 43 | "command-line", 44 | "text" 45 | ], 46 | "dependencies": { 47 | "ansi-regex": "^5.0.1" 48 | }, 49 | "devDependencies": { 50 | "ava": "^2.4.0", 51 | "tsd": "^0.10.0", 52 | "xo": "^0.25.3" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/javascript/4/node_modules/wrap-ansi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wrap-ansi", 3 | "version": "6.2.0", 4 | "description": "Wordwrap a string with ANSI escape codes", 5 | "license": "MIT", 6 | "repository": "chalk/wrap-ansi", 7 | "author": { 8 | "name": "Sindre Sorhus", 9 | "email": "sindresorhus@gmail.com", 10 | "url": "sindresorhus.com" 11 | }, 12 | "engines": { 13 | "node": ">=8" 14 | }, 15 | "scripts": { 16 | "test": "xo && nyc ava" 17 | }, 18 | "files": [ 19 | "index.js" 20 | ], 21 | "keywords": [ 22 | "wrap", 23 | "break", 24 | "wordwrap", 25 | "wordbreak", 26 | "linewrap", 27 | "ansi", 28 | "styles", 29 | "color", 30 | "colour", 31 | "colors", 32 | "terminal", 33 | "console", 34 | "cli", 35 | "string", 36 | "tty", 37 | "escape", 38 | "formatting", 39 | "rgb", 40 | "256", 41 | "shell", 42 | "xterm", 43 | "log", 44 | "logging", 45 | "command-line", 46 | "text" 47 | ], 48 | "dependencies": { 49 | "ansi-styles": "^4.0.0", 50 | "string-width": "^4.1.0", 51 | "strip-ansi": "^6.0.0" 52 | }, 53 | "devDependencies": { 54 | "ava": "^2.1.0", 55 | "chalk": "^2.4.2", 56 | "coveralls": "^3.0.3", 57 | "has-ansi": "^3.0.0", 58 | "nyc": "^14.1.1", 59 | "xo": "^0.24.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/javascript/4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-test", 3 | "version": "1.0.1", 4 | "main": "none", 5 | "dependencies": { 6 | "cliui": "^6.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/javascript/5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-test", 3 | "version": "1.0.1", 4 | "main": "none", 5 | "dependencies": { 6 | "cliui": "^6.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/javascript/javascript_test.go: -------------------------------------------------------------------------------- 1 | package javascript 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/javascript" 7 | "github.com/xmirrorsecurity/opensca-cli/v3/test/tool" 8 | ) 9 | 10 | func Test_JavaScript(t *testing.T) { 11 | 12 | ansi := tool.Dep("strip-ansi", "6.0.1", 13 | tool.Dep("ansi-regex", "5.0.1"), 14 | ) 15 | 16 | std := tool.Dep("", "", 17 | tool.Dep("js-test", "1.0.1", 18 | tool.Dep("cliui", "6.0.0", 19 | tool.Dep("string-width", "4.2.3", 20 | tool.Dep("emoji-regex", "8.0.0"), 21 | tool.Dep("is-fullwidth-code-point", "3.0.0"), 22 | ansi, 23 | ), 24 | ansi, 25 | tool.Dep("wrap-ansi", "6.2.0", 26 | tool.Dep("ansi-styles", "4.3.0", 27 | tool.Dep("color-convert", "2.0.1", 28 | tool.Dep("color-name", "1.1.4"), 29 | ), 30 | ), 31 | ), 32 | ), 33 | ), 34 | ) 35 | 36 | tool.RunTaskCase(t, javascript.Sca{})([]tool.TaskCase{ 37 | // package.lock 38 | {Path: "1", Result: std}, 39 | // package.lock v3 40 | {Path: "2", Result: std}, 41 | // yarn.lock 42 | {Path: "3", Result: std}, 43 | // node_modules 44 | {Path: "4", Result: std}, 45 | // simple 46 | {Path: "5", Result: std}, 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /test/php/1/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opensca/test", 3 | "license": "MIT", 4 | "type": "extension", 5 | "require": { 6 | "php": ">=7.0", 7 | "http-interop/http-factory-guzzle": "^1.2", 8 | "psr/http-message": "^2.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/php/2/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opensca/test", 3 | "license": "MIT", 4 | "type": "extension", 5 | "require": { 6 | "php": ">=7.0", 7 | "http-interop/http-factory-guzzle": "^1.2", 8 | "psr/http-message": "^2.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/php/php_test.go: -------------------------------------------------------------------------------- 1 | package php 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/php" 7 | "github.com/xmirrorsecurity/opensca-cli/v3/test/tool" 8 | ) 9 | 10 | func Test_Php(t *testing.T) { 11 | 12 | std := tool.Dep("", "", 13 | tool.Dep("opensca/test", "", 14 | tool.Dep("http-interop/http-factory-guzzle", "1.2.0", 15 | tool.Dep("guzzlehttp/psr7", "2.6.1", 16 | tool.Dep("ralouphie/getallheaders", "3.0.3"), 17 | ), 18 | tool.Dep("psr/http-factory", "1.0.2"), 19 | ), 20 | tool.Dep("psr/http-message", "2.0"), 21 | ), 22 | ) 23 | 24 | tool.RunTaskCase(t, php.Sca{})([]tool.TaskCase{ 25 | 26 | // composer.lock 27 | {Path: "1", Result: std}, 28 | 29 | // composer.json 30 | {Path: "2", Result: std}, 31 | }) 32 | 33 | } 34 | -------------------------------------------------------------------------------- /test/python/1/requirements.txt: -------------------------------------------------------------------------------- 1 | elasticsearch 2 | -------------------------------------------------------------------------------- /test/python/2/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | elasticsearch = "*" 8 | 9 | [dev-packages] 10 | 11 | [requires] 12 | python_version = "3.11" 13 | -------------------------------------------------------------------------------- /test/python/3/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "f3dbaae5a7068c238fcd78ebadecb627c7b2a5bff8c948d04b3672e2dee01713" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.11" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "certifi": { 20 | "hashes": [ 21 | "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", 22 | "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" 23 | ], 24 | "markers": "python_version >= '3.6'", 25 | "version": "==2023.7.22" 26 | }, 27 | "elastic-transport": { 28 | "hashes": [ 29 | "sha256:19db271ab79c9f70f8c43f8f5b5111408781a6176b54ab2e54d713b6d9ceb815", 30 | "sha256:b9ad708ceb7fcdbc6b30a96f886609a109f042c0b9d9f2e44403b3133ba7ff10" 31 | ], 32 | "markers": "python_version >= '3.6'", 33 | "version": "==8.4.0" 34 | }, 35 | "elasticsearch": { 36 | "hashes": [ 37 | "sha256:0795cbf0f61482070741c09ba02ac8fdf18f5984912fbd08b248fadd8a8c9952", 38 | "sha256:d3367fc013e04fc7aad349a6de9fad1ee04fb6d627b0e7896aa505c12fde5e04" 39 | ], 40 | "index": "pypi", 41 | "markers": "python_version >= '3.6' and python_version < '4'", 42 | "version": "==8.9.0" 43 | }, 44 | "urllib3": { 45 | "hashes": [ 46 | "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f", 47 | "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14" 48 | ], 49 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 50 | "version": "==1.26.16" 51 | } 52 | }, 53 | "develop": {} 54 | } 55 | -------------------------------------------------------------------------------- /test/python/python_test.go: -------------------------------------------------------------------------------- 1 | package python 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/python" 7 | "github.com/xmirrorsecurity/opensca-cli/v3/test/tool" 8 | ) 9 | 10 | func Test_Python(t *testing.T) { 11 | 12 | tool.RunTaskCase(t, python.Sca{})([]tool.TaskCase{ 13 | 14 | // rquirements.txt 15 | {Path: "1", Result: tool.Dep("", "", tool.Dep("", "", 16 | tool.Dep("elasticsearch", "8.9.0", 17 | tool.Dep("elastic-transport", "8.4.0", 18 | tool.Dep("certifi", "2023.7.22"), 19 | tool.Dep("urllib3", "1.26.16"), 20 | ), 21 | ), 22 | ))}, 23 | 24 | // Pipfile 25 | {Path: "2", Result: tool.Dep("", "", tool.Dep("", "", 26 | tool.Dep("elasticsearch", "8.9.0", 27 | tool.Dep("elastic-transport", "8.4.0", 28 | tool.Dep("certifi", "2023.7.22"), 29 | tool.Dep("urllib3", "1.26.16"), 30 | ), 31 | ), 32 | ))}, 33 | 34 | // Pipfile.lock 35 | {Path: "3", Result: tool.Dep("", "", tool.Dep("", "", 36 | tool.Dep("elasticsearch", "8.9.0"), 37 | tool.Dep("elastic-transport", "8.4.0"), 38 | tool.Dep("certifi", "2023.7.22"), 39 | tool.Dep("urllib3", "1.26.16"), 40 | ))}, 41 | }) 42 | 43 | } 44 | -------------------------------------------------------------------------------- /test/ruby/1/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.8.5) 5 | public_suffix (>= 2.0.2, < 6.0) 6 | cookiejar (0.3.3) 7 | em-http-request (1.1.7) 8 | addressable (>= 2.3.4) 9 | cookiejar (!= 0.3.1) 10 | em-socksify (>= 0.3) 11 | eventmachine (>= 1.0.3) 12 | http_parser.rb (>= 0.6.0) 13 | em-socksify (0.3.2) 14 | eventmachine (>= 1.0.0.beta.4) 15 | eventmachine (1.2.7) 16 | http_parser.rb (0.8.0) 17 | public_suffix (5.0.3) 18 | 19 | PLATFORMS 20 | x64-mingw-ucrt 21 | 22 | DEPENDENCIES 23 | em-http-request 24 | 25 | BUNDLED WITH 26 | 2.4.19 27 | -------------------------------------------------------------------------------- /test/ruby/ruby_test.go: -------------------------------------------------------------------------------- 1 | package ruby 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/ruby" 7 | "github.com/xmirrorsecurity/opensca-cli/v3/test/tool" 8 | ) 9 | 10 | func Test_Ruby(t *testing.T) { 11 | tool.RunTaskCase(t, ruby.Sca{})([]tool.TaskCase{ 12 | 13 | // Gemfile.lock 14 | {Path: "1", Result: tool.Dep("", "", 15 | tool.Dep("em-http-request", "1.1.7", 16 | tool.Dep("addressable", "2.8.5", 17 | tool.Dep("public_suffix", "5.0.3"), 18 | ), 19 | tool.Dep("cookiejar", "0.3.3"), 20 | tool.Dep("em-socksify", "0.3.2"), 21 | tool.Dep("eventmachine", "1.2.7"), 22 | tool.Dep("http_parser.rb", "0.8.0"), 23 | ), 24 | )}, 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /test/rust/1/Cargo.lock: -------------------------------------------------------------------------------- 1 | version = 3 2 | 3 | [[package]] 4 | name = "foo" 5 | version = "0.1.0" 6 | dependencies = [ 7 | "tokio", 8 | "windows-targets", 9 | ] 10 | 11 | [[package]] 12 | name = "tokio" 13 | version = "1.28.0" 14 | dependencies = [ 15 | "windows-sys", 16 | ] 17 | 18 | [[package]] 19 | name = "windows-sys" 20 | version = "0.48.0" 21 | dependencies = [ 22 | "windows-targets", 23 | ] 24 | 25 | [[package]] 26 | name = "windows-targets" 27 | version = "0.48.0" 28 | dependencies = [ 29 | "windows_x86_64_gnu", 30 | ] 31 | 32 | [[package]] 33 | name = "windows_x86_64_gnu" 34 | version = "0.48.0" 35 | -------------------------------------------------------------------------------- /test/rust/rust_test.go: -------------------------------------------------------------------------------- 1 | package rust 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/rust" 7 | "github.com/xmirrorsecurity/opensca-cli/v3/test/tool" 8 | ) 9 | 10 | func Test_Rust(t *testing.T) { 11 | tool.RunTaskCase(t, rust.Sca{})([]tool.TaskCase{ 12 | 13 | // Cargo.lock 14 | {Path: "1", Result: tool.Dep("", "", 15 | tool.Dep("foo", "0.1.0", 16 | tool.Dep("tokio", "1.28.0", 17 | tool.Dep("windows-sys", "0.48.0"), 18 | ), 19 | tool.Dep("windows-targets", "0.48.0", 20 | tool.Dep("windows_x86_64_gnu", "0.48.0"), 21 | ), 22 | ), 23 | )}, 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /test/tool/tool.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca" 8 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs" 9 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/model" 10 | "github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca" 11 | ) 12 | 13 | func Diff(a, b *model.DepGraph) bool { 14 | clear := func(p, n *model.DepGraph) bool { 15 | n.Path = "" 16 | n.Language = model.Lan_None 17 | return true 18 | } 19 | a.ForEachNode(clear) 20 | b.ForEachNode(clear) 21 | return a.Tree(false, true) != b.Tree(false, true) 22 | } 23 | 24 | func Dep3(vendor, name, version string, children ...*model.DepGraph) *model.DepGraph { 25 | root := &model.DepGraph{ 26 | Vendor: vendor, 27 | Name: name, 28 | Version: version, 29 | } 30 | for _, c := range children { 31 | root.AppendChild(c) 32 | } 33 | return root 34 | } 35 | 36 | func DevDep3(vendor, name, version string, children ...*model.DepGraph) *model.DepGraph { 37 | root := Dep3(vendor, name, version, children...) 38 | root.Develop = true 39 | return root 40 | } 41 | 42 | func Dep(name, version string, children ...*model.DepGraph) *model.DepGraph { 43 | return Dep3("", name, version, children...) 44 | } 45 | 46 | func DevDep(name, version string, children ...*model.DepGraph) *model.DepGraph { 47 | return DevDep3("", name, version, children...) 48 | } 49 | 50 | type TaskCase struct { 51 | Path string 52 | Result *model.DepGraph 53 | } 54 | 55 | func RunTaskCase(t *testing.T, sca ...sca.Sca) func(cases []TaskCase) { 56 | return func(cases []TaskCase) { 57 | for _, c := range cases { 58 | r := opensca.RunTask(context.Background(), &opensca.TaskArg{ 59 | DataOrigin: c.Path, 60 | Sca: sca, 61 | }) 62 | result := &model.DepGraph{} 63 | for _, dep := range r.Deps { 64 | result.AppendChild(dep) 65 | } 66 | if Diff(result, c.Result) { 67 | logs.Debugf("%s\nres:\n%sstd:\n%s", c.Path, result.Tree(false, true), c.Result.Tree(false, true)) 68 | t.Fail() 69 | } 70 | } 71 | } 72 | } 73 | --------------------------------------------------------------------------------