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
11 changes: 7 additions & 4 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ jobs:
with:
languages: go

- name: build
uses: docker://golang:1.23.2-alpine3.20
- name: Set up Go
uses: actions/setup-go@v5
with:
entrypoint: /bin/sh
args: -c "go build ."
go-version-file: go.mod
check-latest: true

- name: Build
run: go build .

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
79 changes: 79 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Security Checks

on:
push:
branches: [main, release]
pull_request:
branches: [main, release]
schedule:
- cron: "0 6 * * 1" # Weekly on Mondays

permissions:
contents: read
security-events: write

jobs:
govulncheck:
name: Go Vulnerability Check
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true

- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest

- name: Run govulncheck
run: govulncheck ./...

dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Dependency Review
uses: actions/dependency-review-action@v4
with:
fail-on-severity: moderate
deny-licenses: GPL-2.0, GPL-3.0

gosec:
name: Security Scan
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true

- name: Run Gosec Security Scanner
uses: securego/gosec@v2.19.0
with:
args: "-fmt=sarif -out=gosec.sarif ./..."

- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: gosec.sarif
11 changes: 10 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
on: [push, pull_request]

name: Run Tests

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
checks: write
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
- name: Run tests with coverage
run: |
go test -v -covermode=count -coverprofile=coverage.out
- name: Coveralls
uses: coverallsapp/github-action@v2.3.6
with:
github-token: ${{ secrets.github_token }}
github-token: ${{ secrets.GITHUB_TOKEN }}
file: coverage.out
format: golang
32 changes: 30 additions & 2 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package disk

import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)

Expand Down Expand Up @@ -64,13 +66,36 @@ func NewWithConfig(config *ClientConfig, token ...string) (*Client, error) {
config = DefaultClientConfig()
}

// Validate and sanitize token
sanitizedToken := strings.TrimSpace(token[0])
if sanitizedToken == "" {
return nil, errors.New("access token cannot be empty")
}

// Initialize logger
logger := NewLogger(config.Logger)

// Create HTTP client with secure TLS configuration
transport := &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
},
},
MaxIdleConns: 10,
MaxIdleConnsPerHost: 2,
IdleConnTimeout: 90 * time.Second,
}

return &Client{
AccessToken: token[0],
AccessToken: sanitizedToken,
HTTPClient: &http.Client{
Timeout: config.DefaultTimeout,
Timeout: config.DefaultTimeout,
Transport: transport,
},
Config: config,
Logger: logger,
Expand Down Expand Up @@ -121,6 +146,9 @@ func (c *Client) doRequest(ctx context.Context, method HttpMethod, resource stri

if method == GET || method == DELETE {
body = nil
} else if data != nil {
// Limit request body size to prevent memory exhaustion
body = io.LimitReader(data, 100*1024*1024) // 100MB limit
}

requestURL := API_URL + resource
Expand Down
37 changes: 33 additions & 4 deletions resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,38 @@ import (
"errors"
"fmt"
"net/url"
"path/filepath"
"strconv"
"strings"
)

// validatePath sanitizes and validates file paths to prevent path traversal attacks
func validatePath(path string) error {
if path == "" {
return errors.New("path cannot be empty")
}

// Remove any null bytes
if strings.Contains(path, "\x00") {
return errors.New("path contains null bytes")
}

// Clean the path to resolve any .. sequences
cleaned := filepath.Clean(path)

// Check for path traversal attempts
if strings.Contains(cleaned, "..") {
return errors.New("path traversal detected")
}

// Check for excessively long paths
if len(path) > 4096 {
return errors.New("path too long")
}

return nil
}

func (c *Client) buildDeleteResourceURL(path string, permanently bool) string {
query := url.Values{}
query.Set("path", path)
Expand All @@ -19,8 +48,8 @@ func (c *Client) buildDeleteResourceURL(path string, permanently bool) string {

// todo: add *ErrorResponse to return
func (c *Client) DeleteResource(ctx context.Context, path string, permanently bool) error {
if path == "" {
return errors.New("delete error: path cannot be empty")
if err := validatePath(path); err != nil {
return fmt.Errorf("delete error: %w", err)
}

url := c.buildDeleteResourceURL(path, permanently)
Expand All @@ -40,8 +69,8 @@ func (c *Client) DeleteResource(ctx context.Context, path string, permanently bo
}

func (c *Client) GetMetadata(ctx context.Context, path string) (*Resource, *ErrorResponse) {
if len(path) < 1 {
return nil, &ErrorResponse{Error: "path cannot be empty"}
if err := validatePath(path); err != nil {
return nil, &ErrorResponse{Error: err.Error()}
}

var resource *Resource
Expand Down
Loading