Add build and CodeQL scan scripts

- Created a build script to compile the Go backend.
- Added a CodeQL scan script to automate the creation and analysis of CodeQL databases for Go and JavaScript/TypeScript, including necessary checks for dependencies.
This commit is contained in:
Wikid82
2025-11-20 23:59:56 -05:00
parent 959f56eab6
commit 29e1523364
10 changed files with 115 additions and 21 deletions
@@ -49,7 +49,11 @@ func (h *BackupHandler) Delete(c *gin.Context) {
func (h *BackupHandler) Download(c *gin.Context) {
filename := c.Param("filename")
path := h.service.GetBackupPath(filename)
path, err := h.service.GetBackupPath(filename)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if _, err := os.Stat(path); os.IsNotExist(err) {
c.JSON(http.StatusNotFound, gin.H{"error": "Backup not found"})
@@ -4,6 +4,7 @@ import (
"net/http"
"os"
"strconv"
"strings"
"github.com/Wikid82/CaddyProxyManagerPlus/backend/internal/models"
"github.com/Wikid82/CaddyProxyManagerPlus/backend/internal/services"
@@ -65,6 +66,10 @@ func (h *LogsHandler) Download(c *gin.Context) {
filename := c.Param("filename")
path, err := h.service.GetLogPath(filename)
if err != nil {
if strings.Contains(err.Error(), "invalid filename") {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusNotFound, gin.H{"error": "Log file not found"})
return
}
+32 -10
View File
@@ -159,22 +159,41 @@ func (s *BackupService) addDirToZip(w *zip.Writer, srcDir, zipBase string) error
// DeleteBackup removes a backup file
func (s *BackupService) DeleteBackup(filename string) error {
// Basic sanitization to prevent directory traversal
clean := filepath.Base(filepath.Clean(filename))
return os.Remove(filepath.Join(s.BackupDir, clean))
cleanName := filepath.Base(filename)
if filename != cleanName {
return fmt.Errorf("invalid filename: path traversal attempt detected")
}
path := filepath.Join(s.BackupDir, cleanName)
if !strings.HasPrefix(path, filepath.Clean(s.BackupDir)) {
return fmt.Errorf("invalid filename: path traversal attempt detected")
}
return os.Remove(path)
}
// GetBackupPath returns the full path to a backup file (for downloading)
func (s *BackupService) GetBackupPath(filename string) string {
clean := filepath.Base(filepath.Clean(filename))
return filepath.Join(s.BackupDir, clean)
func (s *BackupService) GetBackupPath(filename string) (string, error) {
cleanName := filepath.Base(filename)
if filename != cleanName {
return "", fmt.Errorf("invalid filename: path traversal attempt detected")
}
path := filepath.Join(s.BackupDir, cleanName)
if !strings.HasPrefix(path, filepath.Clean(s.BackupDir)) {
return "", fmt.Errorf("invalid filename: path traversal attempt detected")
}
return path, nil
}
// RestoreBackup restores the database and caddy data from a zip archive
func (s *BackupService) RestoreBackup(filename string) error {
cleanName := filepath.Base(filename)
if filename != cleanName {
return fmt.Errorf("invalid filename: path traversal attempt detected")
}
// 1. Verify backup exists
clean := filepath.Base(filepath.Clean(filename))
srcPath := filepath.Join(s.BackupDir, clean)
srcPath := filepath.Join(s.BackupDir, cleanName)
if !strings.HasPrefix(srcPath, filepath.Clean(s.BackupDir)) {
return fmt.Errorf("invalid filename: path traversal attempt detected")
}
if _, err := os.Stat(srcPath); err != nil {
return err
}
@@ -214,13 +233,16 @@ func (s *BackupService) unzip(src, dest string) error {
rc, err := f.Open()
if err != nil {
outFile.Close()
_ = outFile.Close()
return err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
// Check for close errors on writable file
if closeErr := outFile.Close(); closeErr != nil && err == nil {
err = closeErr
}
rc.Close()
if err != nil {
@@ -50,7 +50,8 @@ func TestBackupService_CreateAndList(t *testing.T) {
assert.True(t, backups[0].Size > 0)
// Test GetBackupPath
path := service.GetBackupPath(filename)
path, err := service.GetBackupPath(filename)
require.NoError(t, err)
assert.Equal(t, filepath.Join(service.BackupDir, filename), path)
// Test Restore
@@ -113,14 +114,14 @@ func TestBackupService_PathTraversal(t *testing.T) {
os.MkdirAll(service.BackupDir, 0755)
// Test GetBackupPath with traversal
// Should resolve to .../backups/passwd, NOT .../etc/passwd
path := service.GetBackupPath("../../etc/passwd")
expected := filepath.Join(service.BackupDir, "passwd")
assert.Equal(t, expected, path)
// Should return error
_, err := service.GetBackupPath("../../etc/passwd")
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid filename")
// Test DeleteBackup with traversal
// Should try to delete .../backups/passwd, fail because it doesn't exist
err := service.DeleteBackup("../../etc/passwd")
// Should return error
err = service.DeleteBackup("../../etc/passwd")
assert.Error(t, err)
assert.True(t, os.IsNotExist(err))
assert.Contains(t, err.Error(), "invalid filename")
}
+9 -2
View File
@@ -3,6 +3,7 @@ package services
import (
"bufio"
"encoding/json"
"fmt"
"os"
"path/filepath"
@@ -59,8 +60,14 @@ func (s *LogService) ListLogs() ([]LogFile, error) {
// GetLogPath returns the absolute path to a log file if it exists and is valid
func (s *LogService) GetLogPath(filename string) (string, error) {
clean := filepath.Base(filepath.Clean(filename))
path := filepath.Join(s.LogDir, clean)
cleanName := filepath.Base(filename)
if filename != cleanName {
return "", fmt.Errorf("invalid filename: path traversal attempt detected")
}
path := filepath.Join(s.LogDir, cleanName)
if !strings.HasPrefix(path, filepath.Clean(s.LogDir)) {
return "", fmt.Errorf("invalid filename: path traversal attempt detected")
}
// Verify file exists
if _, err := os.Stat(path); err != nil {
@@ -103,6 +103,15 @@ func TestLogService(t *testing.T) {
_, err = service.GetLogPath("missing.log")
assert.Error(t, err)
// Test GetLogPath - Invalid
_, err = service.GetLogPath("nonexistent.log")
assert.Error(t, err)
// Test GetLogPath - Traversal
_, err = service.GetLogPath("../../etc/passwd")
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid filename")
// Test ListLogs - Directory Not Exist
nonExistService := NewLogService(&config.Config{DatabasePath: filepath.Join(t.TempDir(), "missing", "cpm.db")})
logs, err = nonExistService.ListLogs()
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Executable
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
cd backend && go build ./...
+42
View File
@@ -0,0 +1,42 @@
#!/bin/bash
set -e
# Check if gh is installed
if ! command -v gh &> /dev/null; then
echo "Error: GitHub CLI (gh) is not installed."
exit 1
fi
# Check if gh-codeql extension is installed
if ! gh extension list | grep -q "github/gh-codeql"; then
echo "Installing GitHub CodeQL extension..."
gh extension install github/gh-codeql
fi
echo "Creating CodeQL database..."
# Remove existing db if any
rm -rf codeql-db
# Clean up build artifacts and coverage reports to prevent false positives
echo "Cleaning up build artifacts..."
rm -rf frontend/dist backend/coverage
# Create the database cluster
echo "Creating CodeQL database cluster..."
# We specify --command to ensure Go builds correctly
# We include javascript to scan the frontend (TypeScript/React)
# We use --db-cluster to support multiple languages
gh codeql database create codeql-db --language=go,javascript --db-cluster --source-root . --command "./tools/build.sh" --overwrite
echo "Analyzing CodeQL database..."
# Analyze Go
echo "Analyzing Go..."
gh codeql database analyze codeql-db/go codeql/go-queries:codeql-suites/go-security-and-quality.qls --format=sarif-latest --output=codeql-results-go.sarif --download
# Analyze JavaScript/TypeScript
echo "Analyzing JavaScript/TypeScript..."
gh codeql database analyze codeql-db/javascript codeql/javascript-queries:codeql-suites/javascript-security-and-quality.qls --format=sarif-latest --output=codeql-results-js.sarif --download
echo "Scan complete."
echo "Go results: codeql-results-go.sarif"
echo "JS/TS results: codeql-results-js.sarif"