diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 98fbc00..4a323be 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -65,9 +65,9 @@ jobs: go-version: "1.24" - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Lint code - uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84 # v6.5.2 + uses: golangci/golangci-lint-action@0a35821d5c230e903fcfe077583637dea1b27b47 # v9.0.0 with: - version: v1.64.8 + version: v2.6.1 working-directory: . only-new-issues: false args: --timeout=10m @@ -101,7 +101,7 @@ jobs: - name: Setup gotestsum uses: autero1/action-gotestsum@7263b9d73912eec65f46337689e59fac865c425f # v2.0.0 with: - gotestsum_version: "1.12.1" + gotestsum_version: "1.13.0" - name: Run tests run: gotestsum --format short-verbose ./... @@ -134,7 +134,7 @@ jobs: - name: Setup gotestsum uses: autero1/action-gotestsum@7263b9d73912eec65f46337689e59fac865c425f # v2.0.0 with: - gotestsum_version: "1.12.1" + gotestsum_version: "1.13.0" - name: Run tests run: gotestsum --format short-verbose ./... @@ -167,6 +167,6 @@ jobs: - name: Setup gotestsum uses: autero1/action-gotestsum@7263b9d73912eec65f46337689e59fac865c425f # v2.0.0 with: - gotestsum_version: "1.12.1" + gotestsum_version: "1.13.0" - name: Run tests run: gotestsum --format short-verbose ./... diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index e5f1c79..0000000 --- a/.golangci.yml +++ /dev/null @@ -1,93 +0,0 @@ -run: - timeout: 10m - modules-download-mode: readonly - tests: false - -linters-settings: - goconst: - min-len: 5 - min-occurrences: 4 - misspell: - locale: US - depguard: - rules: - main: - deny: - - pkg: "io/ioutil" - desc: "ioutil is deprecated starting with Go 1.16" - revive: - # Specifying any rule explicitly will disable the default-enabled rules. - # Manually specify the defaults along with `context-as-argument`. - rules: - - name: blank-imports - disabled: false - - name: dot-imports - disabled: false - - name: error-naming - disabled: false - - name: error-return - disabled: false - - name: error-strings - disabled: false - - name: exported - disabled: false - - name: increment-decrement - disabled: false - - name: indent-error-flow - disabled: false - - name: receiver-naming - disabled: false - - name: range - disabled: false - - name: var-naming - disabled: false - stylecheck: - # ST1000 checks for missing package comments. We don't use these for most - # packages, so let's disable this check. - checks: ["all", "-ST1000"] - -linters: - exclusions: - paths: - - "testing.go" - - ".*\\.pb\\.go" - - ".*\\.gen\\.go" - disable-all: true - enable: - - asciicheck - - bidichk - - bodyclose - - depguard - - dogsled - - errcheck - - exhaustive - - gochecknoinits - - gocognit - - goconst - - gocritic - - gocyclo - - gofmt - - gofumpt - - goimports - - gomodguard - - gosec - - gosimple - - govet - - ineffassign - - misspell - - nakedret - - nestif - - noctx - - nolintlint - - paralleltest - - prealloc - - revive - - staticcheck - - stylecheck - - typecheck - - unconvert - - unparam - - unused - - unused - - wastedassign - - whitespace diff --git a/README.md b/README.md index 4d42397..87fda4a 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,363 @@ # Go Secure SDK -Package security provides various security-in-mind built features across -various domains. +[![Go Reference](https://pkg.go.dev/badge/github.com/DataDog/go-secure-sdk.svg)](https://pkg.go.dev/github.com/DataDog/go-secure-sdk) +[![Go Report Card](https://goreportcard.com/badge/github.com/DataDog/go-secure-sdk)](https://goreportcard.com/report/github.com/DataDog/go-secure-sdk) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) -The package is a part of the "Secure SDK" project. +A comprehensive security-focused Go SDK providing secure-by-default implementations for common operations that are prone to security vulnerabilities. This project is designed to be a one-stop-shop for security features and libraries for Go developers. -It provides a set of libraries to mitigate common security issues and -vulnerabilities. The project is designed to be a one-stop-shop for security -features and libraries for Go developers. +## Overview -The project is released to the public as a set of open-source libraries to -cover Datadog open-source projects. +The Go Secure SDK is part of Datadog's commitment to secure software development. It provides a set of libraries to mitigate common security issues and vulnerabilities across various domains including compression, cryptography, I/O operations, networking, and filesystem operations. -The project is licensed under the Apache License, Version 2.0. The license -can be found in the LICENSE file in the root of the project. +## Features -## Functions +- **๐Ÿ”’ Secure by Default**: All components implement security best practices out of the box +- **๐Ÿ›ก๏ธ Attack Prevention**: Built-in protection against common attacks (zip-slip, SSRF, path traversal, etc.) +- **โšก Production Ready**: Battle-tested code used in Datadog's production environments +- **๐Ÿ“ฆ Modular Design**: Use only the packages you need +- **๐Ÿงช Well Tested**: Comprehensive test coverage with security-focused test cases +- **๐Ÿ“š Well Documented**: Extensive documentation and examples for every package -### func [InDevMode](flags.go#L19) +## Installation -`func InDevMode() bool` +```bash +go get github.com/DataDog/go-secure-sdk +``` -InDevMode returns the development mode flag status. +## Requirements -### func [InFIPSMode](flags.go#L47) +- Go 1.24.0 or higher -`func InFIPSMode() bool` +## Packages -InFIPSMode returns the FIPS compliance mode flag status. +### ๐Ÿ—œ๏ธ Compression & Archives -### func [SetDevMode](flags.go#L27) +#### `compression/archive/tar` -`func SetDevMode() (revert func())` +Secure TAR archive creation and extraction with protection against: +- Zip-slip attacks +- Path traversal attacks +- Archive bombs (size/count limits) +- Symbolic link recursion attacks -SetDevMode enables the local development mode in this package and returns a -function to revert the configuration. +```go +import "github.com/DataDog/go-secure-sdk/compression/archive/tar" -Calling this method multiple times once the flag is enabled produces no effect. +// Extract with security controls +err := tar.Extract(reader, "/safe/output/path", + tar.WithMaxArchiveSize(100 << 20), // 100MB max + tar.WithMaxEntryCount(10000), // Max 10k files + tar.WithMaxFileSize(10 << 20), // 10MB per file +) +``` -### func [SetFIPSMode](flags.go#L56) +[๐Ÿ“– Documentation](compression/archive/tar/README.md) -`func SetFIPSMode() (revert func())` +#### `compression/archive/zip` -SetFIPSMode enables the FIPS compliance mode in this package and returns a -function to revert the configuration. +Hardened ZIP archive operations with similar security controls as TAR. -Calling this method multiple times once the flag is enabled produces no effect. +```go +import "github.com/DataDog/go-secure-sdk/compression/archive/zip" -## Sub Packages +// Create with compression control +err := zip.Create(fileSystem, writer, + zip.WithCompressionLevel(flate.DefaultCompression), + zip.WithExcludeFilter(func(path string, fi fs.FileInfo) bool { + return strings.HasSuffix(path, ".zip") + }), +) +``` -* [compression](./compression): Package compression provides hardened compression related features. +[๐Ÿ“– Documentation](compression/archive/zip/README.md) -* [compression/archive](./compression/archive): Package archive provides high level compressed archive management features. +### ๐Ÿ” Cryptography -* [compression/archive/tar](./compression/archive/tar): Package tar provides TAR archive management functions +#### `crypto/hashutil` -* [compression/archive/tar/builder](./compression/archive/tar/builder): Package builder provides a tar archive builder essentially for testing purposes. +Secure cryptographic hash functions with support for multiple algorithms in a single pass. -* [compression/archive/zip](./compression/archive/zip): Package zip provides hardened ZIP archive management functions +```go +import "github.com/DataDog/go-secure-sdk/crypto/hashutil" -* [crypto/hashutil](./crypto/hashutil): Package hashutil provides secured cryptographic hash functions +// Compute multiple hashes in one read +hashes, err := hashutil.FileHashes(root, "file.bin", + crypto.SHA256, + crypto.SHA384, + crypto.SHA512, +) +``` -* [crypto/keyutil](./crypto/keyutil): Package keyutil provides cryptographic keys management functions. +[๐Ÿ“– Documentation](crypto/hashutil/README.md) -* [generator/randomness](./generator/randomness): Package randomness provides `math/rand` dropin replace with secured initialization. +#### `crypto/keyutil` -* [ioutil](./ioutil): Package ioutil provides I/O hardened operations. +Comprehensive cryptographic key management supporting multiple formats and operations. -* [net](./net): Package net provides network security related functions. +```go +import "github.com/DataDog/go-secure-sdk/crypto/keyutil" -* [net/httpclient](./net/httpclient): Package httpclient provides a SSRF-safe HTTP client implementation. +// Generate key pair +pub, priv, err := keyutil.GenerateKeyPair(keyutil.EC) -* [net/httpclient/mock](./net/httpclient/mock): Package mock is a generated GoMock package. +// Convert to JWK +jwk, err := keyutil.ToJWK(priv) -* [net/tlsclient](./net/tlsclient): Package tlsclient provides hardened TLS dialer functions. +// Encrypt JWK with password +encrypted, err := keyutil.ToEncryptedJWK(jwk, []byte("password")) +``` + +[๐Ÿ“– Documentation](crypto/keyutil/README.md) + +### ๐ŸŽฒ Random Generation + +#### `generator/randomness` + +Cryptographically secure random generation with `math/rand` compatible API. + +```go +import "github.com/DataDog/go-secure-sdk/generator/randomness" + +// Drop-in replacement for math/rand with crypto/rand backing +randomNumber := randomness.Intn(100) + +// Generate secure tokens +token, err := randomness.Alphanumeric(32) +verificationCode, err := randomness.VerificationCode(6) +``` + +[๐Ÿ“– Documentation](generator/randomness/README.md) + +### ๐Ÿ’พ I/O Operations + +#### `ioutil` + +Hardened I/O operations with size limits and timeouts. + +```go +import "github.com/DataDog/go-secure-sdk/ioutil" + +// Copy with size limit (prevents decompression bombs) +size, err := ioutil.LimitCopy(dst, src, 10 << 20) // Max 10MB + +// Reader with timeout protection +timeoutReader := ioutil.TimeoutReader(slowReader, 5*time.Second) +``` + +[๐Ÿ“– Documentation](ioutil/README.md) + +### ๐ŸŒ Networking + +#### `net/httpclient` + +SSRF-safe HTTP client implementation with request/response filtering. + +```go +import "github.com/DataDog/go-secure-sdk/net/httpclient" + +// Safe client blocks dangerous requests +client := httpclient.Safe() + +// This will be blocked (metadata service) +resp, err := client.Get("http://169.254.169.254/") +// Error: address is link local unicast + +// Customize with options +client = httpclient.Safe( + httpclient.WithTimeout(30*time.Second), + httpclient.WithFollowRedirect(true), +) +``` + +[๐Ÿ“– Documentation](net/httpclient/README.md) + +#### `net/tlsclient` + +TLS dialer with certificate pinning support. + +```go +import "github.com/DataDog/go-secure-sdk/net/tlsclient" + +// Pin to specific certificate fingerprint +dialer := tlsclient.PinnedDialer(tlsConfig, fingerprint) + +client := httpclient.Safe( + httpclient.WithTLSDialer(dialer), +) +``` + +[๐Ÿ“– Documentation](net/tlsclient/README.md) + +### ๐Ÿ“ Filesystem + +#### `vfs` + +Virtual filesystem with security constraints preventing path traversal. + +```go +import "github.com/DataDog/go-secure-sdk/vfs" + +// Create chrooted filesystem +fs, err := vfs.Chroot("/safe/base/path") + +// Path traversal attempts are blocked +err = fs.Mkdir("../../../etc", 0755) +// Returns: ConstraintError + +// Confirmed directories +tmpDir, err := vfs.NewTmpConfirmedDir() +safePath := tmpDir.Join("subdir/file.txt") +``` + +[๐Ÿ“– Documentation](vfs/README.md) + +## Security Best Practices + +This SDK is designed to help you follow security best practices: + +1. **Input Validation**: Always validate and sanitize user inputs before processing +2. **Size Limits**: Use the built-in size limits to prevent resource exhaustion +3. **Timeouts**: Apply reasonable timeouts to prevent hanging operations +4. **Least Privilege**: Use chrooted filesystems and network restrictions +5. **Defense in Depth**: Combine multiple security layers + +## Common Use Cases + +### Secure Archive Extraction + +```go +// Extract user-uploaded archive safely +func extractUpload(uploadPath, destPath string) error { + f, err := os.Open(uploadPath) + if err != nil { + return err + } + defer f.Close() + + // Create chrooted extraction directory + fs, err := vfs.Chroot(destPath) + if err != nil { + return err + } + + // Extract with all safety checks + return tar.Extract( + io.LimitReader(f, 100<<20), // Max 100MB + fs.Root(), + tar.WithMaxEntryCount(1000), + tar.WithMaxFileSize(10<<20), + ) +} +``` + +### SSRF-Safe HTTP Requests + +```go +// Fetch URL from user input safely +func fetchURL(userURL string) ([]byte, error) { + client := httpclient.Safe( + httpclient.WithTimeout(10*time.Second), + ) + + resp, err := client.Get(userURL) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + // Read with size limit + return io.ReadAll(io.LimitReader(resp.Body, 1<<20)) +} +``` + +### Secure File Hashing + +```go +// Verify file integrity with multiple algorithms +func verifyFile(path string, expected map[crypto.Hash]string) error { + root := os.DirFS(filepath.Dir(path)) + name := filepath.Base(path) + + hashes, err := hashutil.FileHashes(root, name, + crypto.SHA256, + crypto.SHA512, + ) + if err != nil { + return err + } + + for algo, hash := range hashes { + if hex.EncodeToString(hash) != expected[algo] { + return fmt.Errorf("hash mismatch for %s", algo) + } + } + return nil +} +``` + +## Testing + +Run the test suite: + +```bash +go test ./... +``` + +Run tests with race detection: + +```bash +go test -race ./... +``` + +Run tests with coverage: + +```bash +go test -cover ./... +``` + +## Contributing + +We welcome contributions! Please see our contributing guidelines for more details. + +Before submitting a pull request: + +1. Ensure all tests pass +2. Add tests for new functionality +3. Update documentation as needed +4. Follow the existing code style +5. Write clear commit messages + +## Security + +Security is our top priority. If you discover a security vulnerability, please follow our [Security Policy](SECURITY.md). + +**Please DO NOT file a public issue. Instead, send your report privately to security@datadoghq.com** + +We greatly appreciate security reports and will publicly thank you for it (with your permission). + +## License + +This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. + +Copyright 2024-present Datadog, Inc. + +## Acknowledgments + +This SDK is developed and maintained by Datadog to support our open-source projects and is made available to the broader Go community. + +## Related Projects + +- [DataDog/datadog-agent](https://github.com/DataDog/datadog-agent) - Datadog Agent +- [DataDog/go-libddwaf](https://github.com/DataDog/go-libddwaf) - Go bindings for libddwaf + +## Support + +- ๐Ÿ“– [Documentation](https://pkg.go.dev/github.com/DataDog/go-secure-sdk) +- ๐Ÿ› [Issue Tracker](https://github.com/DataDog/go-secure-sdk/issues) +- ๐Ÿ’ฌ [Discussions](https://github.com/DataDog/go-secure-sdk/discussions) + +--- + +**Built with โค๏ธ by Datadog** -* [vfs](./vfs): Package vfs extends the default Golang FS abstraction to support secured write operations. diff --git a/compression/archive/tar/extract.go b/compression/archive/tar/extract.go index d2bf715..23b06e6 100644 --- a/compression/archive/tar/extract.go +++ b/compression/archive/tar/extract.go @@ -115,7 +115,7 @@ func Extract(r io.Reader, outPath string, opts ...Option) error { } // Remove existing file - if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { + if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { if err := out.RemoveAll(targetPath); err != nil { return fmt.Errorf("unable to remove existing file %q: %w", targetPath, err) } diff --git a/compression/archive/zip/extract.go b/compression/archive/zip/extract.go index ba38307..7f72310 100644 --- a/compression/archive/zip/extract.go +++ b/compression/archive/zip/extract.go @@ -99,7 +99,7 @@ func Extract(r io.ReaderAt, size uint64, outPath string, opts ...Option) error { } // Remove existing file - if !(fi.IsDir() && zfi.IsDir()) { + if !fi.IsDir() || !zfi.IsDir() { if err := out.RemoveAll(targetPath); err != nil { return fmt.Errorf("unable to remove existing file %q: %w", targetPath, err) } diff --git a/crypto/keyutil/pem_test.go b/crypto/keyutil/pem_test.go index 9aea8e5..5778e88 100644 --- a/crypto/keyutil/pem_test.go +++ b/crypto/keyutil/pem_test.go @@ -92,8 +92,6 @@ v3EkmK5exXDRb//5YIOEN4M= pkixRsaPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApBxWZCOSmFLtd+6kkn9c\nvkNoe+dm10cAl6UzldKsSE+4Xfu06zgyiVhaao2Sw0YD/jUSyZtl1gYlyAH8aaNK\ne4b9s3SEqBrPQbYgqQT30BpvpLZQecURLAUsWDqG2jE0LkRMcWiQN10sDYBhZT9r\ntDeU+t/CoOjIViONhHcC80qt/KB9FBRwNNqB4AUE/LhyEBmg3ViHI6YrxBOhsqY3\nv5+lh07TXexG6HMqZyWjpezqcf4keYA0YU/uGMxh9VBZZSASI/rw4I69qcWhAOTx\nkeXthrBq7U5q9avboBX5xfLut+BYRMxs2ZFnVnzRR4Z+NhQ4AIyXK03RsJ6HVMDP\nuQIDAQAB\n-----END PUBLIC KEY-----\n" - rsaKeyCabinPEM = "-----BEGIN ENCRYPTED CABIN PRIVATE KEY-----\neyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInZlcnNpb24iOjEsInNhbHQiOiJ3WUtH\nOUtKR0RlQnE4MkZibEgwbzNRPT0ifSwiY2lwaGVyIjp7Im5hbWUiOiJkYXRhZG9n\nL2QyIn0sImNpcGhlcnRleHQiOiIwbjVMVXV2aEZGaG5kb1cvR2J6OGRqenAzWjdX\nN0h5UXNyMWJoOUFmRkoraGxLcXJYWnVTMFI2RDB6NDZxeXRBVGVITjY4T1ZtU1d1\nTUp2b1E3MUhCUytVNWluWGhIeDllMEI1aGkzY1BZWVBDK2VCT0o0TkwyS3RvdFh3\ncUg1RlAwRm9UckNNcDNtdG1vZlJOQ25KdHhCZzhVa3RtcTByc2k1ZDlxendsN2RP\nZmlmb3p0M0Y1MHRScWMrQXowaDJiNW9xRTZQcDBzZUQ3bUQ1T3A1M0NVQlQzUDEr\nWWJNTlRsZDhJQjB2M0Y4d0JNSkM1aTlzQno4OUVIaTFzUU50NWFEY20yeHMwMUIr\nb0syaTNkOVFyOXhSTmozUUExeWgxbzhJSzdNY2VHcm4vRnhwSWJ1d2dFb1VQa2Fu\nUms2RGhrYjhRQ3piajR4WTRIYUlkajFRMWgyYnk3UXVKeGV4YTN2RjBLalhzeFR2\nN1lTZStUV0NldGdVQUNXK2JhZlVXQ2V0dHhBK3lMRU9YdzFlZ016ZU9XTXJ0QVNi\nUkZNNzhRZWZENmlKYnBZYlRUOHBIcFdPckVYK2tETVZmUk5uRzM4VzMrNHlJUkRk\nK2MwV2dZQVdxL2REM1prU2FMQktsSVpUU1ZDSDlYVTR4eUlISXJGeTZKdTBaNVE1\nYkRhVWswQWtVR1hxdi9NN2VxckZ1U2xTbmFNY3lZQ3cyMUFmczB1ZVR5Mm52MldM\nOWptb2FETVQ5SFZxeU93UXd2OElpSnFub2krTUNmbHhmbHc2MDNrNFNORWpKVUxn\nMkFTOVpmU21BaG1Ld3A1T1pmMlNtSnQ0THMyZ0NidEQyVTdBTFJ0SndkNjI4dnY1\nQUNOZzV1dE1RN0ZKdGpLREFnNFRkYmVuMkdjUGttNGhXU2txUXRLbDBVdHY0d29s\nOU03cjdmZjE3Q2FNc0FSRytHamxXem5lN1R0cVViVlRZWVl6RzVZVThxbVRJZm1k\nWlU2L1ArK2djQnJrRU5Pb3orVDBCK0RIRGhQb3BSdlVVYy9qRlB1ZmtDUitwTTJV\nMjl5eWFtYXVwYkwrRjh0cW55N2hjay91SDB0NktmOG4vWnZCa3FhTnVWQ3BxWGFW\nbU1IRE9MTExKV29PQmk4ODRtaHZ6a0RkZzJZZUVORVpBbWpXQXlCR2gyT0VveDNu\ncGdRcDFIQ0VhbEpMaHA2b0tIL0xWTzkyc1R3ZjBnUzkxcnROV2JCK05sbCtBSS9I\nU042V2M1dU1KV1VqNzdhUG9oSjRaZGJ3NkhQMmhaMFl2QXEyMXVvZHpzV2dUTklV\nMXVZUVp5Y2lmMVlkMTBQKzZtdTU1MmJQVlFkMjU1R295djMybklCSlBIZ1ZhVWZU\neG91cG44UTlJaENkZjVLbmJkcm9aY2laQTRqRHBJNWhTckwrdDd4NnFVVTlxcnJt\nem5rY3BNYmJlVGxZdW9YeXJpN2VRdGhFU1FBM1BVZ0EwUk9DWEhIR01uRk9sNWMy\nejFGNmRDUDlPamIzY240RzNNTXFUNlI3NGVIeTlqcHZTcUhlc0VYRVVNLzVjVFhx\nN0lYalYrdnV2NFZmTU81ajIzMmhKTDhNK0RLOUJUY0p2MlVseVI5TWY4QUFJVkYr\nZTNKTmhJaXdRYXY4OWFQUnFiUmxYdUo4MEhHdkVWWlE4NEVMUXJHR05iek1EQXg2\nMXlHc3dNaXBLVnJMa3pBVnFYZThrc0hQTmlYVEgrV1FZQ2x3Nm4raERQcXdKb1F4\neFg4a3QyUFBjeTh4R3lHaW0zQld5QUpwNjZBSXRJVzZSQWtNam1ldm9LY3dQT1lp\nMFN2VnZwUkthdjZFbEZXZzF5MFlNaEVDWExGR2hzNVc4UTczMWZjSThheTdZanp1\ndlFTNG8wUUJJMS9VRGthQzFITk80aE5ZaEMzSFEzdW0vN3pFRmZqN3c1UFFUVnNs\ncldDYU94bUpUbVJ3TWtlT3JZQ3VWOXh2OVR4aUEzSElLVUMrd3FadVppMTlBRVpN\na1dyUDNmWloyU012WHRhVFYvQkpTWXFnMVpQa3ZKNkNOeVFVRWVIZVZFcGhTWGlN\nejBnaEN4MDl2bHZyUktyZ1JSYXpLbi9WdkZqc2xhdkpBZGJyd1J3SnFMOVRQY0hp\naWZQbzMzMC9Fb214Zk9JSmc1OTQvd2RuZkxlcjZFNS82V043VGF3QkUybTV0Umd0\nSzNDd2ZBbC9QUlVMdENZM2MveUQ1N0lDUHI4K1d5cmxEV2t3QWowPSJ9Cg==\n-----END ENCRYPTED CABIN PRIVATE KEY-----\n" - pkcs8EcKeyPEM = `-----BEGIN PRIVATE KEY----- MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDP4oetAITW/Ow3qtTH nFCq8hxc2Kwl5ZQyAebKpmIjhg== diff --git a/flake.lock b/flake.lock index 58e26fe..bba4020 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1742237028, - "narHash": "sha256-xlpHmgBxUnvHo8FNnju0sgnEyasb4gC607b+keqjmX8=", + "lastModified": 1762604901, + "narHash": "sha256-Pr2jpryIaQr9Yx8p6QssS03wqB6UifnnLr3HJw9veDw=", "owner": "nixos", "repo": "nixpkgs", - "rev": "9bc8a90931262245919a26f995c1f24c6c70d3fe", + "rev": "f6b44b2401525650256b977063dbcf830f762369", "type": "github" }, "original": { diff --git a/llm.txt b/llm.txt new file mode 100644 index 0000000..53233cc --- /dev/null +++ b/llm.txt @@ -0,0 +1,726 @@ +# Go Secure SDK - LLM Context + +## Project Overview + +The Go Secure SDK is a security-focused Go library developed by Datadog that provides secure-by-default implementations for common operations prone to security vulnerabilities. It's production-ready code used in Datadog's infrastructure. + +**Module**: github.com/DataDog/go-secure-sdk +**Go Version**: 1.24.0+ +**License**: Apache 2.0 + +## Key Principles + +- **Secure by Default**: All components implement security best practices automatically +- **Attack Prevention**: Built-in protection against zip-slip, SSRF, path traversal, and more +- **Production Ready**: Battle-tested in Datadog's production environments +- **Modular**: Use only the packages you need +- **Well Tested**: Comprehensive security-focused test coverage + +## Installation + +```go +go get github.com/DataDog/go-secure-sdk +``` + +## Package Index + +1. **compression/archive/tar** - Secure TAR archive operations +2. **compression/archive/zip** - Secure ZIP archive operations +3. **crypto/hashutil** - Multi-algorithm cryptographic hashing +4. **crypto/keyutil** - Cryptographic key management (PEM, JWK, generation) +5. **generator/randomness** - Cryptographically secure random generation +6. **ioutil** - Hardened I/O operations with limits and timeouts +7. **net/httpclient** - SSRF-safe HTTP client +8. **net/tlsclient** - TLS dialer with certificate pinning +9. **vfs** - Virtual filesystem with path traversal protection + +--- + +## 1. compression/archive/tar + +### Purpose +Secure TAR archive creation and extraction with comprehensive attack protection. + +### Security Features +- Zip-slip attack prevention +- Path traversal protection +- Archive bomb protection (size/count limits) +- Symbolic link recursion protection +- Chrooted extraction + +### Key Functions + +**Create(fileSystem fs.FS, w io.Writer, opts ...Option) error** +Creates a TAR archive from a filesystem. + +**Extract(r io.Reader, outPath string, opts ...Option) error** +Extracts TAR content with security controls. + +### Important Options +- `WithMaxArchiveSize(uint64)` - Limit total archive size +- `WithMaxEntryCount(uint64)` - Limit file count (default: prevents DoS) +- `WithMaxFileSize(uint64)` - Limit individual file size +- `WithMaxSymlinkRecursion(uint64)` - Limit symlink depth +- `WithIncludeFilter(FileInfoFilterFunc)` - Include only matching files +- `WithExcludeFilter(FileInfoFilterFunc)` - Exclude matching files +- `WithOverwriteFilter(FileInfoFilterFunc)` - Control overwrite behavior +- `WithHeaderRewritterFunc(HeaderProcessorFunc)` - Modify headers (e.g., reset times) +- `WithRestoreOwner(bool)` - Restore file ownership (default: false) +- `WithRestoreTimes(bool)` - Restore timestamps (default: false) +- `WithEmptyDirectories(bool)` - Include empty directories + +### Common Patterns + +```go +// Safe extraction from untrusted source +func extractUpload(uploadPath, destPath string) error { + f, err := os.Open(uploadPath) + if err != nil { + return err + } + defer f.Close() + + return tar.Extract( + io.LimitReader(f, 100<<20), // Max 100MB + destPath, + tar.WithMaxEntryCount(1000), + tar.WithMaxFileSize(10<<20), // 10MB per file + ) +} + +// Deterministic archive creation +err := tar.Create(root, writer, + tar.WithExcludeFilter(func(path string, fi fs.FileInfo) bool { + return strings.HasPrefix(path, ".git") + }), + tar.WithHeaderRewritterFunc(tar.ResetHeaderTimes()), +) +``` + +### Helper Functions +- `ResetHeaderTimes()` - Returns HeaderProcessorFunc to reset times for deterministic output + +--- + +## 2. compression/archive/zip + +### Purpose +Secure ZIP archive creation and extraction with similar protections as TAR. + +### Key Functions + +**Create(fileSystem fs.FS, w io.Writer, opts ...Option) error** +Creates a ZIP archive with security controls. + +**Extract(r io.Reader, outPath string, opts ...Option) error** +Extracts ZIP content safely. + +### Important Options +- `WithMaxArchiveSize(uint64)` - Limit total archive size +- `WithMaxEntryCount(uint64)` - Limit file count +- `WithMaxFileSize(uint64)` - Limit individual file size +- `WithCompressionLevel(int)` - Set compression level (flate constants) +- `WithIncludeFilter(FileInfoFilterFunc)` +- `WithExcludeFilter(FileInfoFilterFunc)` +- `WithPassword([]byte)` - Password-protect ZIP (creation only) + +### Common Patterns + +```go +// Create compressed archive excluding binaries +err := zip.Create(fileSystem, writer, + zip.WithCompressionLevel(flate.BestCompression), + zip.WithExcludeFilter(func(path string, fi fs.FileInfo) bool { + return strings.HasSuffix(path, ".exe") || strings.HasSuffix(path, ".zip") + }), +) +``` + +--- + +## 3. crypto/hashutil + +### Purpose +Efficient cryptographic hashing with support for computing multiple algorithms in a single pass. + +### Key Functions + +**FileHashes(root fs.FS, path string, algos ...crypto.Hash) (map[crypto.Hash][]byte, error)** +Computes multiple hash algorithms in one read. + +**FileHashMatches(root fs.FS, path string, algo crypto.Hash, expected []byte) (bool, error)** +Verifies a file against expected hash. + +**FromHex(algo crypto.Hash, value string) ([]byte, error)** +Converts hex string to hash bytes. + +**ToHex(algo crypto.Hash, value []byte) string** +Converts hash bytes to hex string. + +### Supported Algorithms +- crypto.SHA256 +- crypto.SHA384 +- crypto.SHA512 +- crypto.SHA3_256 +- crypto.SHA3_384 +- crypto.SHA3_512 + +### Common Patterns + +```go +// Verify file integrity with multiple algorithms +hashes, err := hashutil.FileHashes(root, "file.bin", + crypto.SHA256, + crypto.SHA384, + crypto.SHA512, +) +if err != nil { + return err +} + +// Check against expected +sha256Hex := hashutil.ToHex(crypto.SHA256, hashes[crypto.SHA256]) +``` + +--- + +## 4. crypto/keyutil + +### Purpose +Comprehensive cryptographic key management supporting multiple formats and operations. + +### Key Functions + +**GenerateKeyPair(algo KeyType) (crypto.PublicKey, crypto.PrivateKey, error)** +Generates cryptographic key pairs. + +**ToJWK(key interface{}) (*jose.JSONWebKey, error)** +Converts keys to JWK format. + +**FromJWK(jwk *jose.JSONWebKey) (interface{}, error)** +Converts JWK to Go crypto types. + +**ToEncryptedJWK(jwk *jose.JSONWebKey, password []byte) ([]byte, error)** +Encrypts JWK with password. + +**FromEncryptedJWK(data []byte, password []byte) (*jose.JSONWebKey, error)** +Decrypts password-protected JWK. + +**ToPEM(key interface{}) ([]byte, error)** +Converts keys to PEM format. + +**FromPEM(data []byte) (interface{}, error)** +Parses PEM-encoded keys. + +**Fingerprint(key crypto.PublicKey) (string, error)** +Computes SSH-style key fingerprint. + +### Key Types +- `RSA2048`, `RSA4096` - RSA keys +- `EC` - ECDSA P-256 +- `Ed25519` - EdDSA + +### Common Patterns + +```go +// Generate and save encrypted key +pub, priv, err := keyutil.GenerateKeyPair(keyutil.EC) +if err != nil { + return err +} + +jwk, err := keyutil.ToJWK(priv) +if err != nil { + return err +} + +encrypted, err := keyutil.ToEncryptedJWK(jwk, []byte("password")) +if err != nil { + return err +} + +// Compute fingerprint for verification +fp, err := keyutil.Fingerprint(pub) +``` + +--- + +## 5. generator/randomness + +### Purpose +Cryptographically secure random generation with math/rand-compatible API backed by crypto/rand. + +### Key Functions + +**Intn(n int) int** +Returns secure random int in [0,n). + +**Int63() int64** +Returns secure random int64. + +**Float64() float64** +Returns secure random float64 in [0.0,1.0). + +**Read(p []byte) (int, error)** +Fills p with secure random bytes. + +**Alphanumeric(length int) (string, error)** +Generates random alphanumeric string. + +**Numeric(length int) (string, error)** +Generates random numeric string. + +**VerificationCode(length int) (string, error)** +Generates numeric verification code. + +**StringFromCharset(length int, charset string) (string, error)** +Generates random string from custom charset. + +### Common Patterns + +```go +// Drop-in replacement for math/rand +import "github.com/DataDog/go-secure-sdk/generator/randomness" + +// Secure random number +n := randomness.Intn(100) + +// Generate tokens +apiToken, err := randomness.Alphanumeric(32) +sessionID, err := randomness.Alphanumeric(64) +pin, err := randomness.VerificationCode(6) + +// Custom charset +password, err := randomness.StringFromCharset(16, + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%") +``` + +### Thread Safety +All functions are thread-safe. Package provides both global functions and `*LockedRand` type for explicit instances. + +--- + +## 6. ioutil + +### Purpose +Hardened I/O operations with size limits and timeouts to prevent DoS attacks. + +### Key Functions + +**LimitCopy(dst io.Writer, src io.Reader, maxSize uint64) (int64, error)** +Copies data with maximum size limit. Returns error if limit exceeded. + +**TimeoutReader(r io.Reader, timeout time.Duration) io.Reader** +Wraps reader with timeout protection. + +### Common Patterns + +```go +// Prevent decompression bombs +size, err := ioutil.LimitCopy(dst, gzipReader, 10<<20) // Max 10MB +if err != nil { + return fmt.Errorf("file too large or copy failed: %w", err) +} + +// Protect against slow reads +timeoutReader := ioutil.TimeoutReader(networkReader, 30*time.Second) +data, err := io.ReadAll(timeoutReader) +``` + +--- + +## 7. net/httpclient + +### Purpose +SSRF-safe HTTP client that blocks requests to dangerous targets (metadata services, private IPs, etc.). + +### Key Functions + +**Safe(opts ...Option) *http.Client** +Returns HTTP client with SSRF protection enabled. + +**UnSafe(opts ...Option) *http.Client** +Returns HTTP client with default transport only (no SSRF protection). + +**NewClient(az Authorizer, opts ...Option) *http.Client** +Returns HTTP client with custom authorizer. + +### Default Protections +The safe client blocks: +- Link-local addresses (169.254.0.0/16, fe80::/10) +- Loopback addresses (127.0.0.0/8, ::1) +- Private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) +- IPv6 unique local addresses (fc00::/7) + +### Important Options +- `WithTimeout(time.Duration)` - Set client timeout +- `WithFollowRedirect(bool)` - Enable redirect following (default: false) +- `WithMaxRedirectionCount(int)` - Limit redirects +- `WithDisableKeepAlives(bool)` - Disable keep-alives +- `WithTLSClientConfig(*tls.Config)` - Custom TLS config +- `WithTLSDialer(func)` - Custom TLS dialer (for pinning) +- `WithDisableRequestFilter(bool)` - Disable SSRF request filter +- `WithDisableResponseFilter(bool)` - Disable SSRF response filter + +### Common Patterns + +```go +// Safe client blocks dangerous requests +client := httpclient.Safe( + httpclient.WithTimeout(30*time.Second), + httpclient.WithFollowRedirect(true), +) + +// This is blocked (metadata service) +resp, err := client.Get("http://169.254.169.254/latest/meta-data/") +// Returns: address is link local unicast + +// Fetch user-provided URL safely +func fetchURL(userURL string) ([]byte, error) { + client := httpclient.Safe() + resp, err := client.Get(userURL) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + // Also limit response size + return io.ReadAll(io.LimitReader(resp.Body, 1<<20)) +} +``` + +### Custom Authorizer + +Implement the `Authorizer` interface to create custom SSRF rules: + +```go +type Authorizer interface { + AuthorizeRequest(*http.Request) error + AuthorizeResponse(*http.Response) error + AuthorizeTCPDial(network, addr string) error +} +``` + +--- + +## 8. net/tlsclient + +### Purpose +TLS dialer with certificate pinning support. + +### Key Functions + +**PinnedDialer(config *tls.Config, fingerprint string) func(context.Context, string, string) (net.Conn, error)** +Creates TLS dialer that pins to specific certificate fingerprint. + +### Common Patterns + +```go +// Pin to specific certificate +fingerprint := "sha256:1234567890abcdef..." +dialer := tlsclient.PinnedDialer(tlsConfig, fingerprint) + +client := httpclient.Safe( + httpclient.WithTLSDialer(dialer), +) + +// Connection fails if certificate doesn't match +resp, err := client.Get("https://api.example.com") +``` + +--- + +## 9. vfs + +### Purpose +Virtual filesystem abstraction with security constraints preventing path traversal attacks. + +### Key Types + +**FileSystem interface** +Extends fs.FS with write operations (Mkdir, Remove, OpenFile, etc.). + +**ConfirmedDir string** +Type-safe directory path that's been validated as existing. + +**ConstraintError** +Error type for path constraint violations. + +### Key Functions + +**Chroot(root string) (FileSystem, error)** +Creates chrooted filesystem from OS path. + +**ChrootFS(root FileSystem, path string) (FileSystem, error)** +Creates chrooted filesystem from another filesystem. + +**OS() FileSystem** +Returns unrestricted OS filesystem (use with caution). + +**ConfirmDir(root FileSystem, path string) (ConfirmedDir, error)** +Validates directory exists and returns confirmed path. + +**NewTmpConfirmedDir() (ConfirmedDir, error)** +Creates temporary directory as ConfirmedDir. + +### FileSystem Methods +- `Open(name string) (fs.File, error)` - Open for reading +- `OpenFile(name string, flag int, perm fs.FileMode) (File, error)` - Open for writing +- `Mkdir(name string, perm fs.FileMode) error` - Create directory +- `MkdirAll(name string, perm fs.FileMode) error` - Create directory tree +- `Remove(name string) error` - Remove file/directory +- `RemoveAll(name string) error` - Remove recursively +- `Root() string` - Get filesystem root path + +### ConfirmedDir Methods +- `HasPrefix(path ConfirmedDir) bool` - Check if path is within directory +- `Join(path string) string` - Safely join paths +- `String() string` - Get path string + +### Common Patterns + +```go +// Create chrooted filesystem for safe extraction +fs, err := vfs.Chroot("/safe/base/path") +if err != nil { + return err +} + +// Path traversal attempts are blocked +err = fs.Mkdir("../../../etc", 0755) +// Returns: ConstraintError + +// Safe temporary directory +tmpDir, err := vfs.NewTmpConfirmedDir() +if err != nil { + return err +} + +// Check if path escapes chroot +safePath := tmpDir.Join("../../../etc/passwd") +if !tmpDir.HasPrefix(vfs.ConfirmedDir(safePath)) { + return errors.New("path traversal attempt detected") +} + +// Use with archive extraction +err = tar.Extract(reader, fs.Root(), + tar.WithMaxArchiveSize(100<<20), +) +``` + +--- + +## Security Best Practices + +### General Principles +1. **Input Validation**: Always validate user inputs before processing +2. **Size Limits**: Use built-in limits to prevent resource exhaustion +3. **Timeouts**: Apply reasonable timeouts to prevent hanging operations +4. **Least Privilege**: Use chrooted filesystems and network restrictions +5. **Defense in Depth**: Combine multiple security layers + +### Archive Operations +- Always use size limits when extracting untrusted archives +- Combine with chrooted filesystem for double protection +- Set reasonable file count limits to prevent zip bombs +- Use `io.LimitReader` before extraction functions + +### HTTP Requests +- Always use `httpclient.Safe()` for user-provided URLs +- Combine with response size limits using `io.LimitReader` +- Set appropriate timeouts +- Disable redirect following unless necessary + +### Filesystem Operations +- Use `vfs.Chroot()` when working with user-provided paths +- Validate paths with `ConfirmedDir` type +- Check for `ConstraintError` to detect traversal attempts + +### Random Generation +- Use `generator/randomness` instead of `math/rand` for security-sensitive operations +- Use `Alphanumeric()` for tokens and session IDs +- Use `VerificationCode()` for PIN codes +- Use `StringFromCharset()` for passwords with custom requirements + +--- + +## Complete Example: Secure Upload Handler + +```go +package main + +import ( + "fmt" + "io" + "net/http" + + "github.com/DataDog/go-secure-sdk/compression/archive/tar" + "github.com/DataDog/go-secure-sdk/generator/randomness" + "github.com/DataDog/go-secure-sdk/ioutil" + "github.com/DataDog/go-secure-sdk/vfs" +) + +func handleUpload(w http.ResponseWriter, r *http.Request) error { + // Generate secure session ID + sessionID, err := randomness.Alphanumeric(32) + if err != nil { + return fmt.Errorf("failed to generate session ID: %w", err) + } + + // Create temporary extraction directory + tmpDir, err := vfs.NewTmpConfirmedDir() + if err != nil { + return fmt.Errorf("failed to create temp dir: %w", err) + } + + // Create chrooted filesystem + fs, err := vfs.Chroot(tmpDir.String()) + if err != nil { + return fmt.Errorf("failed to create chroot: %w", err) + } + + // Extract with all safety checks + err = tar.Extract( + io.LimitReader(r.Body, 100<<20), // Max 100MB upload + fs.Root(), + tar.WithMaxEntryCount(1000), // Max 1000 files + tar.WithMaxFileSize(10<<20), // Max 10MB per file + ) + if err != nil { + return fmt.Errorf("extraction failed: %w", err) + } + + fmt.Fprintf(w, "Upload successful. Session: %s\n", sessionID) + return nil +} +``` + +--- + +## Error Handling + +### Common Error Types + +**tar/zip**: `ErrAbortedOperation`, `ErrNothingArchived` + +**vfs**: `ConstraintError` - Path traversal or constraint violation + +**httpclient**: Authorization errors for blocked requests + +### Pattern + +```go +// Check for constraint violations +err := fs.Mkdir("../etc", 0755) +var constraintErr *vfs.ConstraintError +if errors.As(err, &constraintErr) { + log.Printf("Path traversal attempt: %v", constraintErr) +} + +// Check for size violations +err := tar.Extract(reader, path, tar.WithMaxArchiveSize(1<<20)) +if errors.Is(err, tar.ErrAbortedOperation) { + log.Printf("Archive too large or too many files") +} +``` + +--- + +## Testing + +The SDK includes comprehensive test coverage. When writing tests: + +```go +// Use in-memory filesystems for testing +import "testing/fstest" + +root := fstest.MapFS{ + "test.txt": {Data: []byte("content")}, +} + +// Create temporary confirmed directories +tmpDir, err := vfs.NewTmpConfirmedDir() +require.NoError(t, err) + +// Use testdata directories +testFS := os.DirFS("./testdata") +``` + +--- + +## Dependencies + +Core dependencies: +- `github.com/go-jose/go-jose/v4` - JWK/JWE support +- `golang.org/x/crypto` - Extended cryptography +- `golang.org/x/net` - Extended networking + +Testing dependencies: +- `github.com/stretchr/testify` - Test assertions +- `github.com/google/go-cmp` - Deep comparisons +- `github.com/golang/mock` - Mocking + +--- + +## Performance Considerations + +1. **hashutil**: Computing multiple hashes in one pass is more efficient than multiple reads +2. **randomness**: Thread-safe global functions use locked instance; create `LockedRand` for isolated use +3. **httpclient**: Authorizer checks happen per-dial; consider caching for frequent requests +4. **vfs**: Path resolution happens on each operation; use ConfirmedDir for repeated access + +--- + +## Migration Guide + +### From archive/tar +```go +// Before +tar.NewReader(r) + +// After +import "github.com/DataDog/go-secure-sdk/compression/archive/tar" +tar.Extract(r, outPath, tar.WithMaxArchiveSize(100<<20)) +``` + +### From math/rand +```go +// Before +import "math/rand" +n := rand.Intn(100) + +// After +import "github.com/DataDog/go-secure-sdk/generator/randomness" +n := randomness.Intn(100) +``` + +### From net/http +```go +// Before +client := &http.Client{Timeout: 30*time.Second} + +// After +import "github.com/DataDog/go-secure-sdk/net/httpclient" +client := httpclient.Safe(httpclient.WithTimeout(30*time.Second)) +``` + +--- + +## Common Pitfalls + +1. **Forgetting size limits**: Always set appropriate limits for untrusted input +2. **Using UnSafe() incorrectly**: Only use for trusted, internal requests +3. **Ignoring ConstraintError**: Always check for path traversal attempts +4. **Not using io.LimitReader**: Combine with extraction/copy functions +5. **Reusing temp directories**: Use NewTmpConfirmedDir() for each operation + +--- + +## Related Resources + +- [pkg.go.dev Documentation](https://pkg.go.dev/github.com/DataDog/go-secure-sdk) +- [GitHub Repository](https://github.com/DataDog/go-secure-sdk) +- [Security Policy](https://github.com/DataDog/go-secure-sdk/blob/main/SECURITY.md) + +For security vulnerabilities, email: security@datadoghq.com (DO NOT file public issues) + diff --git a/net/httpclient/example_test.go b/net/httpclient/example_test.go index b870f88..d69cd44 100644 --- a/net/httpclient/example_test.go +++ b/net/httpclient/example_test.go @@ -22,7 +22,7 @@ func ExampleSafe() { resp, err := c.Do(r) if resp != nil { - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() } // Output: Get "http://169.254.169.254/latest/meta-data/": response filter round trip failed: request filter round trip failed: dial tcp 169.254.169.254:80: tcp4/169.254.169.254:80 is not authorized by the client: "169.254.169.254" address is link local unicast @@ -58,7 +58,7 @@ func ExampleUnSafe() { resp, err := c.Do(r) if resp != nil { - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() } // Output: Get "/": stopped after 2 redirects diff --git a/net/tlsclient/secure_dialer_test.go b/net/tlsclient/secure_dialer_test.go index b174450..81adb88 100644 --- a/net/tlsclient/secure_dialer_test.go +++ b/net/tlsclient/secure_dialer_test.go @@ -56,20 +56,22 @@ func generateAndSignCertificate(t *testing.T) ([]byte, []byte) { // pack as certificate certPEM := new(bytes.Buffer) - pem.Encode(certPEM, &pem.Block{ + err = pem.Encode(certPEM, &pem.Block{ Type: "CERTIFICATE", Bytes: certBytes, }) + require.NoError(t, err) // pack private key certPrivKeyRaw, err := x509.MarshalPKCS8PrivateKey(certPrivKey) require.NoError(t, err) certPrivKeyPEM := new(bytes.Buffer) - pem.Encode(certPrivKeyPEM, &pem.Block{ + err = pem.Encode(certPrivKeyPEM, &pem.Block{ Type: "PRIVATE KEY", Bytes: certPrivKeyRaw, }) + require.NoError(t, err) return certPEM.Bytes(), certPrivKeyPEM.Bytes() } @@ -121,7 +123,7 @@ func TestPinnedDialer(t *testing.T) { resp, err := client.Do(req) require.NoError(t, err) t.Cleanup(func() { - resp.Body.Close() + _ = resp.Body.Close() }) assert.Exactly(t, http.StatusOK, resp.StatusCode) diff --git a/vfs/confirmeddir_test.go b/vfs/confirmeddir_test.go index 1c22992..0020900 100644 --- a/vfs/confirmeddir_test.go +++ b/vfs/confirmeddir_test.go @@ -80,7 +80,7 @@ func TestNewTempConfirmDir(t *testing.T) { tmp, err := NewTmpConfirmedDir() require.NoError(t, err) - defer os.RemoveAll(string(tmp)) + defer func() { _ = os.RemoveAll(string(tmp)) }() delinked, err := filepath.EvalSymlinks(string(tmp)) require.NoError(t, err) diff --git a/vfs/example_test.go b/vfs/example_test.go index d2b378a..14a6884 100644 --- a/vfs/example_test.go +++ b/vfs/example_test.go @@ -57,8 +57,8 @@ func ExampleChrootFS() { // Try to open an out of chroot file will raise a ConstraintError. _, err = subRoot.Open("../etc/passwd") - switch { - case err == nil: + switch err { + case nil: // No error default: // Other error @@ -77,8 +77,8 @@ func ExampleOS() { // Try to open an out of chroot file will raise a ConstraintError. _, err = subRoot.Open("../passwd") - switch { - case err == nil: + switch err { + case nil: // No error default: // Other error