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
1 change: 1 addition & 0 deletions pathfinder-rules/android/WebViewJavaScriptEnabled.cql
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @precision medium
* @tags security
* external/cwe/cwe-079
* @ruleprovider android
*/
FROM method_invocation AS mi
WHERE mi.getName() == "setJavaScriptEnabled" && "true" in mi.getArgumentName()
Expand Down
1 change: 1 addition & 0 deletions pathfinder-rules/android/WebViewaddJavascriptInterface.cql
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @precision medium
* @tags security
* external/cwe/cwe-079
* @ruleprovider android
*/

predicate isJavaScriptEnabled(method_invocation mi) {
Expand Down
1 change: 1 addition & 0 deletions pathfinder-rules/android/WebViewsetAllowContentAccess.cql
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @precision medium
* @tags security
* external/cwe/cwe-079
* @ruleprovider android
*/
FROM method_invocation AS mi
WHERE mi.getName() == "setAllowContentAccess" && "true" in mi.getArgumentName()
Expand Down
1 change: 1 addition & 0 deletions pathfinder-rules/android/WebViewsetAllowFileAccess.cql
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @precision medium
* @tags security
* external/cwe/cwe-079
* @ruleprovider android
*/
FROM method_invocation AS mi
WHERE mi.getName() == "setAllowFileAccess" && "true" in mi.getArgumentName()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @precision medium
* @tags security
* external/cwe/cwe-079
* @ruleprovider android
*/
FROM method_invocation AS mi
WHERE mi.getName() == "setAllowFileAccessFromFileURLs" && "true" in mi.getArgumentName()
Expand Down
1 change: 1 addition & 0 deletions pathfinder-rules/java/BlowfishUsage.cql
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @precision medium
* @tags security
* external/cwe/cwe-327
* @ruleprovider java
*/

FROM method_invocation AS mi
Expand Down
1 change: 1 addition & 0 deletions pathfinder-rules/java/DefaultHttpClient.cql
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @precision medium
* @tags security
* external/cwe/cwe-326
* @ruleprovider java
*/

FROM ClassInstanceExpr AS cie
Expand Down
1 change: 1 addition & 0 deletions pathfinder-rules/java/InsecureRandom.cql
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @precision medium
* @tags security
* external/cwe/cwe-330
* @ruleprovider java
*/

FROM method_invocation AS mi
Expand Down
1 change: 1 addition & 0 deletions pathfinder-rules/java/RC4Usage.cql
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @precision medium
* @tags security
* external/cwe/cwe-327
* @ruleprovider java
*/

FROM method_invocation AS mi
Expand Down
1 change: 1 addition & 0 deletions pathfinder-rules/java/SHA1Usage.cql
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @precision medium
* @tags security
* external/cwe/cwe-328
* @ruleprovider java
*/

FROM method_invocation AS mi
Expand Down
1 change: 1 addition & 0 deletions pathfinder-rules/java/UnEncryptedSocketConnection.cql
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @precision medium
* @tags security
* external/cwe/cwe-319
* @ruleprovider java
*/

FROM ClassInstanceExpr AS cie
Expand Down
125 changes: 116 additions & 9 deletions sourcecode-parser/cmd/ci.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,23 @@ import (
"path/filepath"
"strings"

"github.com/owenrumney/go-sarif/v2/sarif"

"github.com/shivasurya/code-pathfinder/sourcecode-parser/graph"

"github.com/spf13/cobra"
)

type Rule struct {
ID string `json:"id"`
Description string `json:"description"`
Impact string `json:"impact"`
Severity string `json:"severity"`
Passed bool `json:"passed" default:"true"`
Query string `json:"query"`
RuleProvider string `json:"rule_provider"`
}

var ciCmd = &cobra.Command{
Use: "ci",
Short: "Scan a project for vulnerabilities with ruleset in ci mode",
Expand Down Expand Up @@ -53,12 +65,13 @@ var ciCmd = &cobra.Command{
for _, rule := range ruleset {
queryInput := ParseQuery(rule)
rulesetResult := make(map[string]interface{})
result, err := processQuery(queryInput, codeGraph, output)
result, err := processQuery(queryInput.Query, codeGraph, output)

if output == "json" {
if output == "json" || output == "sarif" {
var resultObject map[string]interface{}
json.Unmarshal([]byte(result), &resultObject) //nolint:all
rulesetResult["query"] = queryInput
rulesetResult["query"] = queryInput.Query
rulesetResult["rule"] = queryInput
rulesetResult["result"] = resultObject
outputResult = append(outputResult, rulesetResult)
} else {
Expand Down Expand Up @@ -97,15 +110,74 @@ var ciCmd = &cobra.Command{
fmt.Println("Error writing output file: ", err)
}
}
} else if output == "sarif" {
sarifReport, err := generateSarifReport(outputResult)
if err != nil {
fmt.Println("Error generating sarif report: ", err)
os.Exit(1)
}
if graph.IsGitHubActions() {
// append GITHUB_WORKSPACE to output file path
outputFile = os.Getenv("GITHUB_WORKSPACE") + "/" + outputFile
}
if err := sarifReport.WriteFile(outputFile); err != nil {
fmt.Println("Error writing sarif report: ", err)
os.Exit(1)
}
}
},
}

func generateSarifReport(results []map[string]interface{}) (*sarif.Report, error) {
report, err := sarif.New(sarif.Version210)
if err != nil {
return nil, err
}
run := sarif.NewRunWithInformationURI("CodePathFinder", "https://codepathfinder.dev")
for _, result := range results {
localresult := result["result"].(map[string]interface{}) //nolint:all
resultSet := localresult["result_set"].([]interface{}) //nolint:all
pb := sarif.NewPropertyBag()
rule := result["rule"].(Rule) //nolint:all
pb.Add("impact", rule.Impact)
pb.Add("ruleProvider", rule.RuleProvider)

run.AddRule(rule.ID).
WithDescription(rule.Description).
WithProperties(pb.Properties).
WithMarkdownHelp("# markdown")

for _, finding := range resultSet {
findingMap := finding.(map[string]interface{}) //nolint:all
file, _ := findingMap["file"].(string) //nolint:all
line, _ := findingMap["line"].(float64) //nolint:all
// convert line to int
lineInt := int(line)

run.CreateResultForRule(rule.ID).
WithLevel(strings.ToLower(rule.Severity)).
WithMessage(sarif.NewTextMessage(rule.Description)).
AddLocation(
sarif.NewLocationWithPhysicalLocation(
sarif.NewPhysicalLocation().
WithArtifactLocation(
sarif.NewSimpleArtifactLocation(file),
).WithRegion(
sarif.NewSimpleRegion(lineInt, lineInt),
),
),
)
}
}
report.AddRun(run)
return report, nil
}

func init() {
rootCmd.AddCommand(ciCmd)
ciCmd.Flags().StringP("output", "o", "", "Supported output format: json")
ciCmd.Flags().StringP("output", "o", "", "Supported output format: json, sarif")
ciCmd.Flags().StringP("output-file", "f", "", "Output file path")
ciCmd.Flags().StringP("project", "p", "", "Project to analyze")
ciCmd.Flags().StringP("project", "p", "", "Source code to analyze")
ciCmd.Flags().StringP("ruleset", "r", "", "Ruleset to use example: cfp/java or directory path")
}

Expand Down Expand Up @@ -178,20 +250,55 @@ func downloadRuleset(ruleset string) ([]string, error) {
return rules, nil
}

func ParseQuery(query string) string {
func ParseQuery(query string) Rule {
// split query into lines
lines := strings.Split(query, "\n")
findLineFound := false
commentLineFound := false
query = ""
comment := ""
rule := Rule{}
for _, line := range lines {
// check if line starts with :
if strings.HasPrefix(strings.TrimSpace(line), "predicate") || strings.HasPrefix(strings.TrimSpace(line), "FROM") {
if strings.HasPrefix(strings.TrimSpace(line), "/*") { //nolint:all
comment += line
commentLineFound = true
} else if strings.HasPrefix(strings.TrimSpace(line), "predicate") || strings.HasPrefix(strings.TrimSpace(line), "FROM") {
findLineFound = true
query += line + " "
} else if findLineFound {
query += line + " "
} else if commentLineFound {
comment += line
key, value := ParseCommentLine(line)
switch key {
case "@id":
rule.ID = value
case "@description":
rule.Description = value
case "@problem.severity":
rule.Severity = value
case "@security-severity":
rule.Impact = value
case "@ruleprovider":
rule.RuleProvider = value
}
} else if strings.HasPrefix(strings.TrimSpace(line), "*/") {
commentLineFound = false
}
}
query = strings.TrimSpace(query)
return query
rule.Query = strings.TrimSpace(query)
return rule
}

func ParseCommentLine(line string) (key, value string) {
// parse comment start with "* @name <VALUE_MAY_CONTAIN_SPACE>"
comment := strings.TrimSpace(line)
comment = strings.TrimPrefix(comment, "*")
comment = strings.TrimSpace(comment)
parts := strings.Split(comment, " ")
if len(parts) > 1 {
return parts[0], strings.Join(parts[1:], " ")
}
return "", ""
}
24 changes: 14 additions & 10 deletions sourcecode-parser/cmd/ci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestCiCmd(t *testing.T) {
{
name: "Basic CI command",
args: []string{"ci", "--help"},
expectedOutput: "Scan a project for vulnerabilities with ruleset in ci mode\n\nUsage:\n pathfinder ci [flags]\n\nFlags:\n -h, --help help for ci\n -o, --output string Supported output format: json\n -f, --output-file string Output file path\n -p, --project string Project to analyze\n -r, --ruleset string Ruleset to use example: cfp/java or directory path\n",
expectedOutput: "Scan a project for vulnerabilities with ruleset in ci mode\n\nUsage:\n pathfinder ci [flags]\n\nFlags:\n -h, --help help for ci\n -o, --output string Supported output format: json, sarif\n -f, --output-file string Output file path\n -p, --project string Source code to analyze\n -r, --ruleset string Ruleset to use example: cfp/java or directory path\n",
},
}

Expand Down Expand Up @@ -64,37 +64,41 @@ func TestParseQuery(t *testing.T) {
tests := []struct {
name string
input string
expected string
expected Rule
}{
{
name: "Single predicate",
input: "predicate foo()\n{\n bar\n}",
expected: "predicate foo() { bar }",
expected: Rule{ID: "", Description: "", Impact: "", Severity: "", Passed: false, Query: "predicate foo() { bar }", RuleProvider: ""},
},
{
name: "Multiple predicates",
input: "some code\npredicate foo()\n{\n bar\n}\npredicate baz()\n{\n qux\n}",
expected: "predicate foo() { bar } predicate baz() { qux }",
},
expected: Rule{ID: "", Description: "", Impact: "", Severity: "", Passed: false, Query: "predicate foo() { bar } predicate baz() { qux }", RuleProvider: ""}},
{
name: "FROM clause",
input: "SELECT *\nFROM table\nWHERE condition",
expected: "FROM table WHERE condition",
expected: Rule{ID: "", Description: "", Impact: "", Severity: "", Passed: false, Query: "FROM table WHERE condition", RuleProvider: ""},
},
{
name: "Mixed predicates and FROM",
input: "predicate foo()\n{\n bar\n}\nSELECT *\nFROM table\nWHERE condition",
expected: "predicate foo() { bar } SELECT * FROM table WHERE condition",
expected: Rule{ID: "", Description: "", Impact: "", Severity: "", Passed: false, Query: "predicate foo() { bar } SELECT * FROM table WHERE condition", RuleProvider: ""},
},
{
name: "No matching lines",
input: "Some random\ntext without\nmatching lines",
expected: "",
input: "cmd.Rule(cmd.Rule{ID:\"\", Description:\"\", Impact:\"\", Severity:\"\", Passed:false, Query:\"\", RuleProvider:\"\"})",
expected: Rule{ID: "", Description: "", Impact: "", Severity: "", Passed: false, Query: "", RuleProvider: ""},
},
{
name: "Empty input",
input: "",
expected: "",
expected: Rule{ID: "", Description: "", Impact: "", Severity: "", Passed: false, Query: "", RuleProvider: ""},
},
{
name: "Single line comment",
input: "/**\n * @name Android WebView JavaScript settings\n * @description Enabling setAllowFileAccessFromFileURLs leak s&&box access to file:/// URLs.\n * @kind problem\n * @id java/Android/webview-javascript-enabled\n * @problem.severity warning\n * @security-severity 6.1\n * @precision medium\n * @tags security\n * external/cwe/cwe-079\n * @ruleprovider android\n */\nFROM method_invocation AS mi\nWHERE mi.getName() == \"setAllowFileAccessFromFileURLs\" && \"true\" in mi.getArgumentName()\nSELECT mi.getName(), \"File access enabled\"",
expected: Rule{ID: "java/Android/webview-javascript-enabled", Description: "Enabling setAllowFileAccessFromFileURLs leak s&&box access to file:/// URLs.", Impact: "6.1", Severity: "warning", Passed: false, Query: "FROM method_invocation AS mi WHERE mi.getName() == \"setAllowFileAccessFromFileURLs\" && \"true\" in mi.getArgumentName() SELECT mi.getName(), \"File access enabled\"", RuleProvider: "android"},
},
}

Expand Down
2 changes: 1 addition & 1 deletion sourcecode-parser/cmd/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func processQuery(input string, codeGraph *graph.CodeGraph, output string) (stri
parsedQuery.Expression = strings.SplitN(parts[1], "SELECT", 2)[0]
}
entities, formattedOutput := graph.QueryEntities(codeGraph, parsedQuery)
if output == "json" {
if output == "json" || output == "sarif" {
analytics.ReportEvent(analytics.QueryCommandJSON)
// convert struct to query_results
results := make(map[string]interface{})
Expand Down
1 change: 1 addition & 0 deletions sourcecode-parser/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (

require (
github.com/fatih/color v1.17.0
github.com/owenrumney/go-sarif/v2 v2.3.3
github.com/stretchr/testify v1.9.0
)

Expand Down
Loading