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
40 changes: 26 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: CI Pipeline

on:
push:
branches: ["*"]
pull_request:
branches: ["*"]

Expand Down Expand Up @@ -94,23 +92,37 @@ jobs:
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest

- name: Run govulncheck and fail if vulnerabilities are found
- name: Run govulncheck and generate SARIF
run: |
echo "Current directory: $(pwd)"
echo "Files in current directory:"
ls -la
govulncheck -format=sarif ./... > govulncheck-results.sarif

govulncheck -json ./... > vuln.json
count=$(jq '[.[] | select(.finding != null and .finding.trace != null)] | length' vuln.json || echo 0)
echo "Found $count vulnerabilities"

if [ "$count" -gt 0 ]; then
echo " Vulnerabilities found by govulncheck"
cat vuln.json
exit 1
echo "⚠️ Vulnerabilities found by govulncheck (see Security tab for details)"
else
echo "✅ No vulnerabilities found by govulncheck"
fi
continue-on-error: true

- name: Upload govulncheck results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: govulncheck-results.sarif
category: govulncheck

- name: Install gosec
run: go install github.com/securego/gosec/v2/cmd/gosec@latest

- name: Run gosec security scanner
run: |
gosec -fmt sarif -out gosec-results.sarif ./...
gosec -fmt sarif -out gosec-results.sarif -exclude G304 ./...
continue-on-error: true

- name: Upload gosec results to GitHub Security tab
Expand Down Expand Up @@ -138,12 +150,6 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: +security-and-quality

- name: Set up Go
uses: actions/setup-go@v4
with:
Expand All @@ -162,6 +168,12 @@ jobs:
- name: Install dependencies
run: go mod download

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: +security-and-quality

- name: Build for CodeQL
run: |
go build -v ./cmd/mpcium
Expand All @@ -180,7 +192,7 @@ jobs:
actions: read
contents: read
security-events: write

steps:
- name: Checkout code
uses: actions/checkout@v4
Expand Down
15 changes: 13 additions & 2 deletions cmd/mpcium-cli/generate-identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"time"

"filippo.io/age"
"github.com/fystack/mpcium/pkg/common/pathutil"
"github.com/urfave/cli/v3"
"golang.org/x/term"
)
Expand Down Expand Up @@ -92,6 +93,11 @@ func generateIdentity(ctx context.Context, c *cli.Command) error {
return fmt.Errorf("error checking peers file: %w", err)
}

// Validate the peers file path for security
if err := pathutil.ValidateFilePath(peersPath); err != nil {
return fmt.Errorf("invalid peers file path: %w", err)
}

// Read peers file
peersData, err := os.ReadFile(peersPath)
if err != nil {
Expand All @@ -111,7 +117,7 @@ func generateIdentity(ctx context.Context, c *cli.Command) error {
}

// Create identity directory
if err := os.MkdirAll(identityDir, 0755); err != nil {
if err := os.MkdirAll(identityDir, 0750); err != nil {
return fmt.Errorf("failed to create identity directory: %w", err)
}

Expand Down Expand Up @@ -152,7 +158,7 @@ func generateNodeIdentity(nodeName, nodeID, identityDir string, encrypt bool, pa
if err != nil {
return fmt.Errorf("failed to marshal identity: %w", err)
}
if err := os.WriteFile(identityPath, identityBytes, 0644); err != nil {
if err := os.WriteFile(identityPath, identityBytes, 0600); err != nil {
return fmt.Errorf("failed to write identity JSON: %w", err)
}

Expand All @@ -168,6 +174,11 @@ func generateNodeIdentity(nodeName, nodeID, identityDir string, encrypt bool, pa
return fmt.Errorf("encrypted key file %s already exists. Use --overwrite to force", encryptedKeyPath)
}

// Validate the encrypted key path for security
if err := pathutil.ValidateFilePath(encryptedKeyPath); err != nil {
return fmt.Errorf("invalid encrypted key file path: %w", err)
}

// Encrypt with age and passphrase
outFile, err := os.Create(encryptedKeyPath)
if err != nil {
Expand Down
11 changes: 9 additions & 2 deletions cmd/mpcium-cli/generate-initiator.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"time"

"filippo.io/age"
"github.com/fystack/mpcium/pkg/common/pathutil"
"github.com/urfave/cli/v3"
)

Expand All @@ -34,7 +35,7 @@ func generateInitiatorIdentity(ctx context.Context, c *cli.Command) error {
overwrite := c.Bool("overwrite")

// Create output directory if it doesn't exist
if err := os.MkdirAll(outputDir, 0755); err != nil {
if err := os.MkdirAll(outputDir, 0750); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}

Expand Down Expand Up @@ -97,7 +98,7 @@ func generateInitiatorIdentity(ctx context.Context, c *cli.Command) error {
return fmt.Errorf("failed to marshal identity JSON: %w", err)
}

if err := os.WriteFile(identityPath, identityBytes, 0643); err != nil {
if err := os.WriteFile(identityPath, identityBytes, 0600); err != nil {
return fmt.Errorf("failed to save identity file: %w", err)
}

Expand All @@ -111,6 +112,12 @@ func generateInitiatorIdentity(ctx context.Context, c *cli.Command) error {

// Create encrypted key file
encKeyPath := keyPath + ".age"

// Validate the encrypted key path for security
if err := pathutil.ValidateFilePath(encKeyPath); err != nil {
return fmt.Errorf("invalid encrypted key file path: %w", err)
}

outFile, err := os.Create(encKeyPath)
if err != nil {
return fmt.Errorf("failed to create encrypted private key file: %w", err)
Expand Down
4 changes: 2 additions & 2 deletions cmd/mpcium-cli/generate-peers.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func generatePeers(ctx context.Context, c *cli.Command) error {
// Create directory if it doesn't exist
dir := filepath.Dir(outputPath)
if dir != "." {
if err := os.MkdirAll(dir, 0755); err != nil {
if err := os.MkdirAll(dir, 0750); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
}
Expand All @@ -56,7 +56,7 @@ func generatePeers(ctx context.Context, c *cli.Command) error {
}

// Write to file
if err := os.WriteFile(outputPath, peersJSON, 0644); err != nil {
if err := os.WriteFile(outputPath, peersJSON, 0600); err != nil {
return fmt.Errorf("failed to write file: %w", err)
}

Expand Down
6 changes: 6 additions & 0 deletions cmd/mpcium-cli/register-peers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"os"

"github.com/fystack/mpcium/pkg/common/pathutil"
"github.com/fystack/mpcium/pkg/config"
"github.com/fystack/mpcium/pkg/infra"
"github.com/fystack/mpcium/pkg/logger"
Expand All @@ -20,6 +21,11 @@ func registerPeers(ctx context.Context, c *cli.Command) error {
// Hardcoded prefix for MPC peers in Consul
prefix := "mpc_peers/"

// Validate the input file path for security
if err := pathutil.ValidateFilePath(inputPath); err != nil {
return fmt.Errorf("invalid input file path: %w", err)
}

// Check if input file exists
if _, err := os.Stat(inputPath); os.IsNotExist(err) {
return fmt.Errorf("input file %s does not exist", inputPath)
Expand Down
2 changes: 1 addition & 1 deletion examples/generate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func main() {
if err != nil {
logger.Error("Failed to marshal wallet IDs", err)
} else {
err = os.WriteFile("wallets.json", data, 0644)
err = os.WriteFile("wallets.json", data, 0600)
if err != nil {
logger.Error("Failed to write wallets.json", err)
} else {
Expand Down
51 changes: 51 additions & 0 deletions pkg/common/pathutil/pathutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package pathutil

import (
"fmt"
"path/filepath"
"strings"
)

// SafePath validates and constructs a safe file path within a base directory
func SafePath(baseDir, filename string) (string, error) {
// Clean the filename to prevent path traversal
cleanFilename := filepath.Clean(filename)

// Check for path traversal attempts
if strings.Contains(cleanFilename, "..") {
return "", fmt.Errorf("invalid filename: path traversal not allowed")
}

// Construct the full path
fullPath := filepath.Join(baseDir, cleanFilename)

// Ensure the path is within the base directory
absBase, err := filepath.Abs(baseDir)
if err != nil {
return "", fmt.Errorf("failed to get absolute path for base directory: %w", err)
}

absPath, err := filepath.Abs(fullPath)
if err != nil {
return "", fmt.Errorf("failed to get absolute path: %w", err)
}

if !strings.HasPrefix(absPath, absBase) {
return "", fmt.Errorf("path outside base directory not allowed")
}

return fullPath, nil
}

// ValidateFilePath validates a file path for security concerns
func ValidateFilePath(filePath string) error {
// Clean the path
cleanPath := filepath.Clean(filePath)

// Check for path traversal attempts
if strings.Contains(cleanPath, "..") {
return fmt.Errorf("invalid file path: path traversal not allowed")
}

return nil
}
91 changes: 91 additions & 0 deletions pkg/common/pathutil/pathutil_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package pathutil

import (
"testing"
)

func TestValidateFilePath(t *testing.T) {
tests := []struct {
name string
path string
wantErr bool
}{
{
name: "valid simple path",
path: "test.json",
wantErr: false,
},
{
name: "valid relative path",
path: "config/test.json",
wantErr: false,
},
{
name: "path traversal attempt",
path: "../../../etc/passwd",
wantErr: true,
},
{
name: "path traversal with clean",
path: "config/../../../etc/passwd",
wantErr: true,
},
{
name: "valid absolute path",
path: "/tmp/test.json",
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateFilePath(tt.path)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateFilePath() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func TestSafePath(t *testing.T) {
tests := []struct {
name string
baseDir string
filename string
wantErr bool
}{
{
name: "valid file in base dir",
baseDir: "/tmp",
filename: "test.json",
wantErr: false,
},
{
name: "path traversal attempt",
baseDir: "/tmp",
filename: "../../../etc/passwd",
wantErr: true,
},
{
name: "path traversal with clean",
baseDir: "/tmp",
filename: "config/../../../etc/passwd",
wantErr: true,
},
{
name: "valid subdirectory",
baseDir: "/tmp",
filename: "config/test.json",
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := SafePath(tt.baseDir, tt.filename)
if (err != nil) != tt.wantErr {
t.Errorf("SafePath() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
Loading
Loading