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
13 changes: 11 additions & 2 deletions engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ func buildSecret(
pluginName string,
) (*secrets.Secret, error) {
gitInfo := item.GetGitInfo()
itemId, err := getFindingId(item, &value)
findingID, err := getFindingId(item, &value)
if err != nil {
return nil, fmt.Errorf("failed to get finding ID: %w", err)
}
Expand Down Expand Up @@ -509,7 +509,7 @@ func buildSecret(
}

secret := &secrets.Secret{
ID: itemId,
ID: findingID,
Source: item.GetSource(),
RuleID: value.RuleID,
StartLine: startLine,
Expand All @@ -520,6 +520,15 @@ func buildSecret(
LineContent: lineContent,
RuleDescription: value.Description,
}

if pluginName == "confluence" {
if pageID, ok := plugins.ParseConfluenceItemID(item.GetID()); ok {
if secret.ExtraDetails == nil {
secret.ExtraDetails = make(map[string]interface{})
}
secret.ExtraDetails["confluence.pageId"] = pageID
}
}
return secret, nil
}

Expand Down
33 changes: 33 additions & 0 deletions engine/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1223,3 +1223,36 @@ func TestProcessScoreWithoutValidation(t *testing.T) {
})
}
}

func TestBuildSecret(t *testing.T) {
t.Run("confluence plugin sets page id in extra details", func(t *testing.T) {
rawPlugin := plugins.NewConfluencePlugin()
confluencePlugin, ok := rawPlugin.(*plugins.ConfluencePlugin)
pageID := "6995346180"
version := 9
itemID := confluencePlugin.NewConfluenceItemID(pageID, version)
pluginName := confluencePlugin.GetName()
sourceURL := "https://example.atlassian.net/wiki/spaces/SCS/pages/" + pageID
content := "dummy"
it := &item{
id: itemID,
source: sourceURL,
content: &content,
}
finding := report.Finding{
RuleID: "github-pat",
StartLine: 1,
EndLine: 1,
Line: "token=SECRET",
Secret: "SECRET",
Description: "test finding",
}
secret, err := buildSecret(context.Background(), it, finding, pluginName)
require.NoError(t, err)
require.NotNil(t, secret)
require.NotNil(t, secret.ExtraDetails, "ExtraDetails should not be nil for confluence plugin")
value, ok := secret.ExtraDetails["confluence.pageId"]
assert.True(t, ok, "ExtraDetails should contain key %q", "confluence.pageId")
assert.Equal(t, pageID, value)
})
}
84 changes: 84 additions & 0 deletions lib/reporting/report_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
var (
ruleID1 = "ruleID1"
ruleID2 = "ruleID2"
ruleID4 = "ruleID4"
result1 = &secrets.Secret{
ID: "ID1",
Source: "file1",
Expand Down Expand Up @@ -64,6 +65,24 @@ var (
CvssScore: 0.0,
RuleDescription: "Rule Description",
}
// result for confluence.pageId validation
result4 = &secrets.Secret{
ID: "ID4",
Source: "file4",
RuleID: "ruleID4",
StartLine: 0,
EndLine: 0,
LineContent: "line content4",
StartColumn: 11,
EndColumn: 130,
Value: "value 4",
ValidationStatus: secrets.UnknownResult,
CvssScore: 0.0,
RuleDescription: "Rule Description",
ExtraDetails: map[string]interface{}{
"confluence.pageId": "1234567890",
},
}
)

// test expected outputs
Expand All @@ -81,6 +100,12 @@ var (
Text: result2.RuleDescription,
},
}
rule4Sarif = &SarifRule{
ID: ruleID4,
FullDescription: &Message{
Text: result4.RuleDescription,
},
}
// sarif results
result1Sarif = Results{
Message: Message{
Expand Down Expand Up @@ -175,6 +200,38 @@ var (
"cvssScore": result3.CvssScore,
},
}
result4Sarif = Results{
Message: Message{
Text: createMessageText(result4.RuleID, result4.Source),
},
RuleId: ruleID4,
Locations: []Locations{
{
PhysicalLocation: PhysicalLocation{
ArtifactLocation: ArtifactLocation{
URI: result4.Source,
},
Region: Region{
StartLine: result4.StartLine,
StartColumn: result4.StartColumn,
EndLine: result4.EndLine,
EndColumn: result4.EndColumn,
Snippet: Snippet{
Text: result4.Value,
Properties: Properties{
"lineContent": strings.TrimSpace(result4.LineContent),
},
},
},
},
},
},
Properties: Properties{
"validationStatus": string(result4.ValidationStatus),
"cvssScore": result4.CvssScore,
"confluence.pageId": result4.ExtraDetails["confluence.pageId"],
},
}
)

func TestAddSecretToFile(t *testing.T) {
Expand Down Expand Up @@ -293,6 +350,33 @@ func TestGetOutputSarif(t *testing.T) {
},
},
},
{
name: "includes confluence.pageId in sarif result properties",
arg: &Report{
TotalItemsScanned: 1,
TotalSecretsFound: 1,
Results: map[string][]*secrets.Secret{
"secret1": {result4},
},
},
wantErr: false,
want: []Runs{
{
Tool: Tool{
Driver: Driver{
Name: "report",
SemanticVersion: "1",
Rules: []*SarifRule{
rule4Sarif,
},
},
},
Results: []Results{
result4Sarif,
},
},
},
},
}

for _, tt := range tests {
Expand Down
24 changes: 16 additions & 8 deletions lib/reporting/sarif.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,26 @@ func getResults(report *Report) []Results {
return results
}

for _, secrets := range report.Results {
for _, secret := range secrets {
for _, secretsSlice := range report.Results {
for _, secret := range secretsSlice {
props := Properties{
"validationStatus": secret.ValidationStatus,
"cvssScore": secret.CvssScore,
}

if secret.ExtraDetails != nil {
if pageID, ok := secret.ExtraDetails["confluence.pageId"]; ok {
props["confluence.pageId"] = pageID
}
}

r := Results{
Message: Message{
Text: createMessageText(secret.RuleID, secret.Source),
},
RuleId: secret.RuleID,
Locations: getLocation(secret),
Properties: Properties{
"validationStatus": secret.ValidationStatus,
"cvssScore": secret.CvssScore,
},
RuleId: secret.RuleID,
Locations: getLocation(secret),
Properties: props,
}
results = append(results, r)
}
Expand Down
30 changes: 29 additions & 1 deletion plugins/confluence.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ func chunkStrings(input []string, chunkSize int) [][]string {

// convertPageToItem converts a Confluence Page into an ISourceItem.
func (p *ConfluencePlugin) convertPageToItem(page *Page) ISourceItem {
itemID := fmt.Sprintf("%s-%s-%s", p.GetName(), page.ID, strconv.Itoa(page.Version.Number))
itemID := p.NewConfluenceItemID(page.ID, page.Version.Number)

sourceURL := ""
if resolvedURL, ok := p.resolveConfluenceSourceURL(page, page.Version.Number); ok {
Expand Down Expand Up @@ -668,3 +668,31 @@ func isValidNumericID(s string) bool {
}
return true
}

// NewConfluenceItemID builds the item ID for a Confluence page.
func (p *ConfluencePlugin) NewConfluenceItemID(pageID string, version int) string {
return fmt.Sprintf("%s-%s-%d", p.GetName(), pageID, version)
}

// ParseConfluenceItemID extracts the Confluence page ID from an item ID
// produced by NewConfluenceItemID. It returns ("", false) if the ID does not
// conform to the expected pattern.
func ParseConfluenceItemID(id string) (string, bool) {
parts := strings.Split(id, "-")
if len(parts) != 3 {
return "", false
}

// Last segment must be an integer version.
if _, err := strconv.Atoi(parts[len(parts)-1]); err != nil {
return "", false
}

// Second-to-last segment must be a valid numeric pageId.
pageID := parts[len(parts)-2]
if !isValidNumericID(pageID) {
return "", false
}

return pageID, true
}
17 changes: 17 additions & 0 deletions plugins/confluence_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1699,6 +1699,23 @@ func TestTrimNonEmpty(t *testing.T) {
}
}

func TestNewConfluenceItemID(t *testing.T) {
p := &ConfluencePlugin{}
pageID := "6995346180"
version := 9
expectedID := "confluence-6995346180-9"
actual := p.NewConfluenceItemID(pageID, version)
assert.Equal(t, expectedID, actual)
}

func TestParseConfluenceItemID(t *testing.T) {
p := &ConfluencePlugin{}
id := p.NewConfluenceItemID("123456", 3)
actualPageID, ok := ParseConfluenceItemID(id)
assert.True(t, ok)
assert.Equal(t, "123456", actualPageID)
}

func newPluginWithMock(t *testing.T) (*ConfluencePlugin, *gomock.Controller, *MockConfluenceClient, *chunk.MockIChunk) {
t.Helper()
ctrl := gomock.NewController(t)
Expand Down
Loading