Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ It performs static security analysis across multiple languages and frameworks:
|----------|-------|
| Python | [Bandit][Bandit], [Safety][Safety] |
| Ruby | [Brakeman][Brakeman] |
| JavaScript | [Npm Audit][NpmAudit], [Yarn Audit][YarnAudit] |
| JavaScript | [Npm Audit][NpmAudit], [Yarn Audit][YarnAudit], [Pnpm Audit][PnpmAudit] |
| Go | [Gosec][Gosec] |
| Java | [SpotBugs][SpotBugs] + [Find Sec Bugs][FindSec] |
| HCL | [TFSec][TFSec] |
Expand All @@ -38,7 +38,7 @@ huskyci-client (runs inside code-analysis runner pod)
huskyci-api (Kubernetes deployment, creates scanner pods)
|
v
Scanner pods (enry, bandit, gosec, gitleaks, npmaudit, etc.)
Scanner pods (enry, bandit, gosec, gitleaks, npmaudit, yarnaudit, pnpmaudit, etc.)
|
v
Results collected, returned to client, reported to SonarQube
Expand Down Expand Up @@ -135,6 +135,7 @@ Each security test can be disabled at runtime by setting its corresponding envir
| `HUSKYCI_DISABLE_GOSEC` | Disable Go Gosec |
| `HUSKYCI_DISABLE_NPMAUDIT` | Disable JavaScript Npm Audit |
| `HUSKYCI_DISABLE_YARNAUDIT` | Disable JavaScript Yarn Audit |
| `HUSKYCI_DISABLE_PNPMAUDIT` | Disable JavaScript Pnpm Audit |
| `HUSKYCI_DISABLE_BRAKEMAN` | Disable Ruby Brakeman |
| `HUSKYCI_DISABLE_SPOTBUGS` | Disable Java SpotBugs |
| `HUSKYCI_DISABLE_TFSEC` | Disable HCL TFSec |
Expand Down Expand Up @@ -196,6 +197,7 @@ huskyCI is licensed under the [BSD 3-Clause License](LICENSE.md).
[Gosec]: https://github.com/securego/gosec
[NpmAudit]: https://docs.npmjs.com/cli/audit
[YarnAudit]: https://yarnpkg.com/lang/en/docs/cli/audit/
[PnpmAudit]: https://pnpm.io/cli/audit
[Gitleaks]: https://github.com/gitleaks/gitleaks
[SpotBugs]: https://spotbugs.github.io
[FindSec]: https://find-sec-bugs.github.io
Expand Down
43 changes: 41 additions & 2 deletions api/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ npmaudit:
cat /tmp/errorNpmaudit
fi
else
if [ ! -f yarn.lock ]; then
if [ ! -f yarn.lock ] && [ ! -f pnpm-lock.yaml ]; then
echo 'ERROR_PACKAGE_LOCK_NOT_FOUND'
fi
fi
Expand Down Expand Up @@ -261,7 +261,7 @@ yarnaudit:
cat /tmp/errorYarnAudit
fi
else
if [ ! -f package-lock.json ]; then
if [ ! -f package-lock.json ] && [ ! -f pnpm-lock.yaml ]; then
echo 'ERROR_YARN_LOCK_NOT_FOUND'
fi
fi
Expand All @@ -274,6 +274,45 @@ yarnaudit:
default: true
timeOutInSeconds: 360

pnpmaudit:
name: pnpmaudit
image: huskyci/pnpmaudit
imageTag: "11.5.2"
cmd: |+
mkdir -p ~/.ssh &&
echo '%GIT_PRIVATE_SSH_KEY%' > ~/.ssh/huskyci_id_rsa &&
chmod 600 ~/.ssh/huskyci_id_rsa &&
echo "IdentityFile ~/.ssh/huskyci_id_rsa" >> /etc/ssh/ssh_config &&
echo "StrictHostKeyChecking no" >> /etc/ssh/ssh_config &&
GIT_TERMINAL_PROMPT=0 git clone -b "%GIT_BRANCH%" --single-branch --depth 1 "%GIT_REPO%" code --quiet 2> /tmp/errorGitClonePnpmAudit
if [ $? -eq 0 ]; then
cd code
if [ -f .npmrc ]; then
rm -f .npmrc
fi
if [ -f pnpm-lock.yaml ]; then
pnpm audit --json --prod > /tmp/results.json 2> /tmp/errorPnpmaudit
RC=$?
if [ $RC -eq 0 ] || [ $RC -eq 1 ]; then
cat /tmp/results.json
else
echo 'ERROR_RUNNING_PNPM_AUDIT'
cat /tmp/errorPnpmaudit
fi
else
if [ ! -f package-lock.json ] && [ ! -f yarn.lock ]; then
echo 'ERROR_PNPM_LOCK_NOT_FOUND'
fi
fi
else
echo "ERROR_CLONING"
cat /tmp/errorGitClonePnpmAudit
fi
type: Language
language: JavaScript
default: true
timeOutInSeconds: 360

spotbugs:
name: spotbugs
image: huskyci/spotbugs
Expand Down
2 changes: 2 additions & 0 deletions api/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type APIConfig struct {
BrakemanSecurityTest *types.SecurityTest
NpmAuditSecurityTest *types.SecurityTest
YarnAuditSecurityTest *types.SecurityTest
PnpmAuditSecurityTest *types.SecurityTest
SpotBugsSecurityTest *types.SecurityTest
GitleaksSecurityTest *types.SecurityTest
SafetySecurityTest *types.SecurityTest
Expand Down Expand Up @@ -155,6 +156,7 @@ func (dF DefaultConfig) SetOnceConfig() {
BrakemanSecurityTest: dF.getSecurityTestConfig("brakeman"),
NpmAuditSecurityTest: dF.getSecurityTestConfig("npmaudit"),
YarnAuditSecurityTest: dF.getSecurityTestConfig("yarnaudit"),
PnpmAuditSecurityTest: dF.getSecurityTestConfig("pnpmaudit"),
SpotBugsSecurityTest: dF.getSecurityTestConfig("spotbugs"),
GitleaksSecurityTest: dF.getSecurityTestConfig("gitleaks"),
SafetySecurityTest: dF.getSecurityTestConfig("safety"),
Expand Down
10 changes: 10 additions & 0 deletions api/context/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,16 @@ var _ = Describe("Context", func() {
Default: fakeCaller.expectedBoolFromConfig,
TimeOutInSeconds: fakeCaller.expectedIntFromConfig,
},
PnpmAuditSecurityTest: &types.SecurityTest{
Name: fakeCaller.expectedStringFromConfig,
Image: fakeCaller.expectedStringFromConfig,
ImageTag: fakeCaller.expectedStringFromConfig,
Cmd: fakeCaller.expectedStringFromConfig,
Type: fakeCaller.expectedStringFromConfig,
Language: fakeCaller.expectedStringFromConfig,
Default: fakeCaller.expectedBoolFromConfig,
TimeOutInSeconds: fakeCaller.expectedIntFromConfig,
},
SafetySecurityTest: &types.SecurityTest{
Name: fakeCaller.expectedStringFromConfig,
Image: fakeCaller.expectedStringFromConfig,
Expand Down
144 changes: 144 additions & 0 deletions api/securitytest/pnpmaudit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright 2019 Globo.com authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package securitytest

import (
"encoding/json"
"fmt"
"strings"

"github.com/githubanotaai/huskyci-api/api/log"
"github.com/githubanotaai/huskyci-api/api/types"
"github.com/githubanotaai/huskyci-api/api/util"
)

// PnpmAuditOutput is the struct that stores all pnpm audit output.
type PnpmAuditOutput struct {
Advisories map[string]PnpmAdvisory `json:"advisories"`
Metadata PnpmMetadata `json:"metadata"`
}

// PnpmAdvisory is a single advisory from pnpm audit.
type PnpmAdvisory struct {
ID int `json:"id"`
Title string `json:"title"`
ModuleName string `json:"module_name"`
VulnerableVersions string `json:"vulnerable_versions"`
PatchedVersions string `json:"patched_versions"`
Severity string `json:"severity"`
CWE string `json:"cwe"`
GithubAdvisoryID string `json:"github_advisory_id"`
URL string `json:"url"`
Findings []PnpmFinding `json:"findings"`
}

// PnpmFinding represents a specific finding of a vulnerable dependency.
type PnpmFinding struct {
Version string `json:"version"`
Paths []string `json:"paths"`
Dev bool `json:"dev"`
Optional bool `json:"optional"`
Bundled bool `json:"bundled"`
}

// PnpmMetadata is the struct that holds vulnerabilities summary.
type PnpmMetadata struct {
Vulnerabilities PnpmVulnerabilitiesSummary `json:"vulnerabilities"`
}

// PnpmVulnerabilitiesSummary is the struct that has all types of possible vulnerabilities.
type PnpmVulnerabilitiesSummary struct {
Info int `json:"info"`
Low int `json:"low"`
Moderate int `json:"moderate"`
High int `json:"high"`
Critical int `json:"critical"`
}

func analyzePnpmaudit(pnpmAuditScan *SecTestScanInfo) error {

pnpmAuditOutput := PnpmAuditOutput{}
pnpmAuditScan.FinalOutput = pnpmAuditOutput

// if pnpm-lock was not found, a warning will be generated as a low vuln
pnpmLockNotFound := strings.Contains(pnpmAuditScan.Container.COutput, "ERROR_PNPM_LOCK_NOT_FOUND")
if pnpmLockNotFound {
pnpmAuditScan.PnpmLockNotFound = true
pnpmAuditScan.preparePnpmAuditVulns()
pnpmAuditScan.prepareContainerAfterScan()
return nil
}

// nil cOutput states that no Issues were found (another lockfile present, silent skip).
if pnpmAuditScan.Container.COutput == "" {
pnpmAuditScan.prepareContainerAfterScan()
return nil
}

// Unmarshal rawOutput into finalOutput.
if err := json.Unmarshal([]byte(pnpmAuditScan.Container.COutput), &pnpmAuditOutput); err != nil {
log.Error("analyzePnpmaudit", "PNPMAUDIT", 1014, pnpmAuditScan.Container.COutput, err)
pnpmAuditScan.ErrorFound = util.HandleScanError(pnpmAuditScan.Container.COutput, err)
pnpmAuditScan.prepareContainerAfterScan()
return pnpmAuditScan.ErrorFound
}
pnpmAuditScan.FinalOutput = pnpmAuditOutput

pnpmAuditScan.preparePnpmAuditVulns()
pnpmAuditScan.prepareContainerAfterScan()
return nil
}

func (pnpmAuditScan *SecTestScanInfo) preparePnpmAuditVulns() {

huskyCIPnpmauditResults := types.HuskyCISecurityTestOutput{}

if pnpmAuditScan.PnpmLockNotFound {
pnpmauditVuln := types.HuskyCIVulnerability{}
pnpmauditVuln.Language = "JavaScript"
pnpmauditVuln.SecurityTool = "PnpmAudit"
pnpmauditVuln.Severity = "low"
pnpmauditVuln.Title = "No pnpm-lock.yaml found."
pnpmauditVuln.Details = "It looks like your project doesn't have a pnpm-lock.yaml file. If you use pnpm to handle your dependencies, it would be a good idea to commit it so huskyCI can check for vulnerabilities."

pnpmAuditScan.Vulnerabilities.LowVulns = append(pnpmAuditScan.Vulnerabilities.LowVulns, pnpmauditVuln)
return
}

pnpmAuditOutput := pnpmAuditScan.FinalOutput.(PnpmAuditOutput)

for _, advisory := range pnpmAuditOutput.Advisories {
pnpmauditVuln := types.HuskyCIVulnerability{}
pnpmauditVuln.Language = "JavaScript"
pnpmauditVuln.SecurityTool = "PnpmAudit"
pnpmauditVuln.File = "pnpm-lock.yaml"
pnpmauditVuln.Title = fmt.Sprintf("Vulnerable Dependency: %s %s (%s)", advisory.ModuleName, advisory.VulnerableVersions, advisory.Title)
pnpmauditVuln.VunerableBelow = advisory.VulnerableVersions
pnpmauditVuln.Code = advisory.ModuleName
pnpmauditVuln.Details = fmt.Sprintf("GHSA: %s\nCWE: %s\nURL: %s\nPatched: %s", advisory.GithubAdvisoryID, advisory.CWE, advisory.URL, advisory.PatchedVersions)

for i, finding := range advisory.Findings {
pnpmauditVuln.Version += fmt.Sprintf("Finding %d:\n", i)
pnpmauditVuln.Version += fmt.Sprintf(" Version: %s\n", finding.Version)
for _, path := range finding.Paths {
pnpmauditVuln.Version += fmt.Sprintf(" Path: %s\n", path)
}
}

switch advisory.Severity {
case "info", "low":
pnpmauditVuln.Severity = "low"
huskyCIPnpmauditResults.LowVulns = append(huskyCIPnpmauditResults.LowVulns, pnpmauditVuln)
case "moderate":
pnpmauditVuln.Severity = "medium"
huskyCIPnpmauditResults.MediumVulns = append(huskyCIPnpmauditResults.MediumVulns, pnpmauditVuln)
case "high", "critical":
pnpmauditVuln.Severity = "high"
huskyCIPnpmauditResults.HighVulns = append(huskyCIPnpmauditResults.HighVulns, pnpmauditVuln)
}
}

pnpmAuditScan.Vulnerabilities = huskyCIPnpmauditResults
}
Loading
Loading