Skip to content

Commit a26e00d

Browse files
frjcompCopilot
andauthored
Gosec (#350)
* Add gosec security scanning GitHub Actions workflow --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
1 parent 807db53 commit a26e00d

13 files changed

Lines changed: 90 additions & 13 deletions

File tree

.github/workflows/gosec.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: gosec
2+
on:
3+
push:
4+
branches:
5+
- main
6+
pull_request:
7+
8+
permissions:
9+
contents: read
10+
security-events: write
11+
12+
jobs:
13+
gosec:
14+
name: security scan
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v5
18+
- uses: actions/setup-go@v6
19+
with:
20+
go-version: stable
21+
- name: Run Gosec Security Scanner
22+
uses: securego/gosec@master
23+
with:
24+
args: '-no-fail -fmt json -out results.json ./src/pipeleak/...'
25+
26+
- name: Check Gosec Results
27+
run: |
28+
FOUND=$(jq '.Stats.found // 0' results.json)
29+
echo "Gosec found $FOUND issue(s)"
30+
if [ "$FOUND" -gt 0 ]; then
31+
echo "❌ Security issues detected! Please fix them before merging."
32+
exit 1
33+
else
34+
echo "✅ No security issues found."
35+
fi

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ src/pipeleak/pipeleak
88
src/pipeleak/pipeleak.exe
99
src/pipeleak/coverage.out
1010
.vscode
11+
results.json

src/pipeleak/cmd/docs/docs.go

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import (
77
"os/exec"
88
"path/filepath"
99
"strings"
10+
"time"
1011

1112
"golang.org/x/text/cases"
1213
"golang.org/x/text/language"
1314
"gopkg.in/yaml.v3"
1415

16+
"github.com/CompassSecurity/pipeleak/pkg/format"
1517
"github.com/rs/zerolog/log"
1618
"github.com/spf13/cobra"
1719
"github.com/spf13/cobra/doc"
@@ -47,14 +49,15 @@ func generateDocs(cmd *cobra.Command, dir string, level int) error {
4749

4850
if len(cmd.Commands()) > 0 {
4951
dir = filepath.Join(dir, cmd.Name())
50-
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
52+
if err := os.MkdirAll(dir, format.DirUserGroupRead); err != nil {
5153
return err
5254
}
5355
filename = filepath.Join(dir, "index.md")
5456
} else {
5557
filename = filepath.Join(dir, getFileName(cmd, level))
5658
}
5759

60+
// #nosec G304 - Creating docs markdown file at controlled internal path during docs generation
5861
f, err := os.Create(filename)
5962
if err != nil {
6063
return err
@@ -164,19 +167,21 @@ func writeMkdocsYaml(rootCmd *cobra.Command, outputDir string) error {
164167
nav = append([]map[string]interface{}{introEntry, methodologyEntry}, nav...)
165168

166169
assetsDir := filepath.Join(outputDir, "pipeleak", "assets")
167-
if err := os.MkdirAll(assetsDir, os.ModePerm); err != nil {
170+
if err := os.MkdirAll(assetsDir, format.DirUserGroupRead); err != nil {
168171
return err
169172
}
170173

171174
assetFiles := []string{"logo.png", "favicon.ico", "social.png"}
172175
for _, fname := range assetFiles {
173176
src := filepath.Join("..", "..", "docs", fname)
174177
dst := filepath.Join(assetsDir, fname)
178+
// #nosec G304 - Reading doc assets from controlled internal paths during docs generation
175179
data, err := os.ReadFile(src)
176180
if err != nil {
177181
return err
178182
}
179-
if err := os.WriteFile(dst, data, 0644); err != nil {
183+
// #nosec G306 - Documentation assets should be world-readable
184+
if err := os.WriteFile(dst, data, format.FilePublicRead); err != nil {
180185
return err
181186
}
182187
}
@@ -283,7 +288,8 @@ func writeMkdocsYaml(rootCmd *cobra.Command, outputDir string) error {
283288
}
284289

285290
filename := filepath.Join(outputDir, "mkdocs.yml")
286-
return os.WriteFile(filename, yamlData, 0644)
291+
// #nosec G306 - mkdocs.yml is a public documentation configuration file
292+
return os.WriteFile(filename, yamlData, format.FilePublicRead)
287293
}
288294

289295
var serve bool
@@ -330,7 +336,7 @@ func copyDir(src, dst string) error {
330336
if err != nil {
331337
return err
332338
}
333-
if err := os.MkdirAll(dst, os.ModePerm); err != nil {
339+
if err := os.MkdirAll(dst, format.DirUserGroupRead); err != nil {
334340
return err
335341
}
336342
for _, entry := range entries {
@@ -350,11 +356,13 @@ func copyDir(src, dst string) error {
350356
}
351357

352358
func copyFile(src, dst string) error {
359+
// #nosec G304 - Copying docs files between controlled internal paths during docs generation
353360
in, err := os.Open(src)
354361
if err != nil {
355362
return err
356363
}
357364
defer func() { _ = in.Close() }()
365+
// #nosec G304 - Creating docs destination file at controlled internal path during docs generation
358366
out, err := os.Create(dst)
359367
if err != nil {
360368
return err
@@ -382,7 +390,7 @@ func Docs(cmd *cobra.Command, args []string) {
382390
}
383391
}
384392

385-
if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
393+
if err := os.MkdirAll(outputDir, format.DirUserGroupRead); err != nil {
386394
log.Fatal().Err(err).Msg("Failed to create pipeleak directory")
387395
}
388396

@@ -414,7 +422,15 @@ func Docs(cmd *cobra.Command, args []string) {
414422
siteDir := filepath.Join(outputDir, "site")
415423
log.Info().Msgf("Serving docs %s at http://localhost:8000 ... (Ctrl+C to quit)", siteDir)
416424
http.Handle("/", http.FileServer(http.Dir(siteDir)))
417-
if err := http.ListenAndServe(":8000", nil); err != nil {
425+
426+
server := &http.Server{
427+
Addr: ":8000",
428+
ReadTimeout: 15 * time.Second,
429+
WriteTimeout: 15 * time.Second,
430+
IdleTimeout: 60 * time.Second,
431+
}
432+
433+
if err := server.ListenAndServe(); err != nil {
418434
log.Fatal().Err(err).Msg("Failed to start HTTP server")
419435
}
420436
}

src/pipeleak/cmd/root.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/CompassSecurity/pipeleak/cmd/gitea"
1414
"github.com/CompassSecurity/pipeleak/cmd/github"
1515
"github.com/CompassSecurity/pipeleak/cmd/gitlab"
16+
"github.com/CompassSecurity/pipeleak/pkg/format"
1617
"github.com/CompassSecurity/pipeleak/pkg/logging"
1718
"github.com/rs/zerolog"
1819
"github.com/rs/zerolog/log"
@@ -114,10 +115,11 @@ func initLogger(cmd *cobra.Command) {
114115
colorEnabled := LogColor
115116

116117
if LogFile != "" {
118+
// #nosec G304 - User-provided log file path via --log-file flag, user controls their own filesystem
117119
runLogFile, err := os.OpenFile(
118120
LogFile,
119121
os.O_APPEND|os.O_CREATE|os.O_WRONLY,
120-
0664,
122+
format.FileUserReadWrite,
121123
)
122124
if err != nil {
123125
panic(err)
@@ -130,7 +132,6 @@ func initLogger(cmd *cobra.Command) {
130132
}
131133
}
132134

133-
// Create the fatal hook to restore terminal state
134135
fatalHook := FatalHook{}
135136

136137
if JsonLogoutput {

src/pipeleak/main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package main
33
import (
44
"encoding/json"
55
"io"
6-
_ "net/http/pprof"
76
"os"
87

98
"github.com/CompassSecurity/pipeleak/cmd"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package format
2+
3+
import "io/fs"
4+
5+
// Common file permission constants used throughout the application.
6+
// These constants provide named values for file and directory permissions
7+
// instead of using magic numbers.
8+
const (
9+
// DirUserGroupRead is for directories that should be readable by owner and group (rwxr-x---)
10+
DirUserGroupRead fs.FileMode = 0750
11+
12+
// FilePublicRead is for files that should be world-readable (rw-r--r--)
13+
// Used for documentation, assets, and other public files
14+
FilePublicRead fs.FileMode = 0644
15+
16+
// FileUserReadWrite is for files that should only be readable by owner (rw-------)
17+
// Used for sensitive files like logs, secrets, and configuration
18+
FileUserReadWrite fs.FileMode = 0600
19+
)

src/pipeleak/pkg/format/string.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func RandomStringN(n int) string {
2525
letterBytes := "abcdefghijklmnopqrstuvwxyz"
2626
b := make([]byte, n)
2727
for i := range b {
28+
// #nosec G404 - Random string generation for non-security purposes (identifiers, filenames)
2829
b[i] = letterBytes[rand.Intn(len(letterBytes))]
2930
}
3031
return string(b)

src/pipeleak/pkg/gitlab/renovate/enum.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ func dumpConfigFileContents(project *gitlab.Project, ciCdYml string, renovateCon
447447
} else {
448448
if len(ciCdYml) > 0 {
449449
ciCdPath := filepath.Join(projectDir, "gitlab-ci.yml")
450-
if err := os.WriteFile(ciCdPath, []byte(ciCdYml), 0700); err != nil {
450+
if err := os.WriteFile(ciCdPath, []byte(ciCdYml), format.FileUserReadWrite); err != nil {
451451
log.Error().Err(err).Str("file", ciCdPath).Msg("Failed to write CI/CD YAML to disk")
452452
}
453453
}
@@ -458,7 +458,7 @@ func dumpConfigFileContents(project *gitlab.Project, ciCdYml string, renovateCon
458458
safeFilename = "renovate.json"
459459
}
460460
configPath := filepath.Join(projectDir, safeFilename)
461-
if err := os.WriteFile(configPath, []byte(renovateConfigFile), 0700); err != nil {
461+
if err := os.WriteFile(configPath, []byte(renovateConfigFile), format.FileUserReadWrite); err != nil {
462462
log.Error().Err(err).Str("file", configPath).Msg("Failed to write Renovate config to disk")
463463
}
464464
}

src/pipeleak/pkg/gitlab/shodan/shodan.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func NewShodanCmd() *cobra.Command {
5353

5454
func Shodan(cmd *cobra.Command, args []string) {
5555

56+
// #nosec G304 - User-provided file path via CLI flag, user controls their own filesystem
5657
jsonFile, err := os.Open(shodanJson)
5758
if err != nil {
5859
log.Fatal().Stack().Err(err).Msg("failed opening file")

src/pipeleak/pkg/httpclient/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ func GetPipeleakHTTPClient(cookieUrl string, cookies []*http.Cookie, defaultHead
9898
return false, nil
9999
}
100100

101+
// #nosec G402 - InsecureSkipVerify required for security scanning tool to connect to untrusted targets
101102
tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
102103

103104
proxyServer, useHttpProxy := os.LookupEnv("HTTP_PROXY")

0 commit comments

Comments
 (0)