Security-focused Go helpers for file I/O, in-memory handling of sensitive data, auth tokens, password hashing, input validation/sanitization, and safe numeric conversions.
- Secure file reads scoped to the system temp directory
- Secure file writes with atomic replace and permissions
- Secure directory creation/listing with root scoping and symlink checks
- Streaming-safe writes from readers with size caps
- Secure temp file/dir helpers with root scoping
- Secure remove and copy helpers with root scoping
- Symlink checks and root-scoped file access using
os.OpenRoot - Secure in-memory buffers with best-effort zeroization
- JWT/PASETO helpers with strict validation and safe defaults
- MFA helpers for TOTP/HOTP provisioning, verification, and backup codes
- Password hashing presets for argon2id/bcrypt with rehash detection
- Email and URL validation with optional DNS/redirect/reputation checks
- Random token generation and validation with entropy/length caps
- Bounded base64/hex encoding and strict JSON decoding
- Size-bounded JSON/YAML/XML parsing helpers
- Redaction helpers and secret detection heuristics for logs/config dumps
- Opinionated TLS configs with TLS 1.2/1.3 defaults, mTLS, and optional post-quantum key exchange
- HTML/Markdown sanitization, SQL/NoSQL input guards, and filename sanitizers
- Safe integer conversion helpers with overflow/negative guards
- Go 1.25.5+ (see
go.mod)
go get github.com/hyp3rd/sectoolspackage main
import (
"os"
"path/filepath"
sectools "github.com/hyp3rd/sectools/pkg/io"
)
func main() {
path := filepath.Join(os.TempDir(), "example.txt")
_ = os.WriteFile(path, []byte("secret"), 0o600)
client := sectools.New()
data, err := client.ReadFile(filepath.Base(path))
if err != nil {
panic(err)
}
_ = data
}package main
import (
"os"
"path/filepath"
sectools "github.com/hyp3rd/sectools/pkg/io"
)
func main() {
path := filepath.Join(os.TempDir(), "example.txt")
_ = os.WriteFile(path, []byte("secret"), 0o600)
client := sectools.New()
buf, err := client.ReadFileWithSecureBuffer(filepath.Base(path))
if err != nil {
panic(err)
}
defer buf.Clear()
_ = buf.Bytes()
}package main
import (
"fmt"
"github.com/hyp3rd/sectools/pkg/converters"
)
func main() {
value, err := converters.SafeUint64FromInt64(42)
fmt.Println(value, err)
}package main
import (
sectools "github.com/hyp3rd/sectools/pkg/io"
)
func main() {
client, err := sectools.NewWithOptions(
sectools.WithWriteSyncDir(true),
)
if err != nil {
panic(err)
}
err = client.WriteFile("example.txt", []byte("secret"))
if err != nil {
panic(err)
}
}package main
import (
"time"
"github.com/golang-jwt/jwt/v5"
sectauth "github.com/hyp3rd/sectools/pkg/auth"
)
func main() {
signer, err := sectauth.NewJWTSigner(
sectauth.WithJWTSigningAlgorithm("HS256"),
sectauth.WithJWTSigningKey([]byte("secret")),
)
if err != nil {
panic(err)
}
verifier, err := sectauth.NewJWTVerifier(
sectauth.WithJWTAllowedAlgorithms("HS256"),
sectauth.WithJWTVerificationKey([]byte("secret")),
sectauth.WithJWTIssuer("sectools"),
sectauth.WithJWTAudience("apps"),
)
if err != nil {
panic(err)
}
claims := jwt.RegisteredClaims{
Issuer: "sectools",
Audience: jwt.ClaimStrings{"apps"},
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
}
token, err := signer.Sign(claims)
if err != nil {
panic(err)
}
_ = verifier.Verify(token, &jwt.RegisteredClaims{})
}package main
import (
"fmt"
"github.com/hyp3rd/sectools/pkg/mfa"
)
func main() {
key, err := mfa.GenerateTOTPKey(
mfa.WithTOTPKeyIssuer("sectools"),
mfa.WithTOTPKeyAccountName("user@example.com"),
)
if err != nil {
panic(err)
}
fmt.Println(key.URL())
totp, err := mfa.NewTOTP(key.Secret())
if err != nil {
panic(err)
}
ok, err := totp.Verify("123456")
if err != nil {
panic(err)
}
_ = ok
}package main
import (
"github.com/hyp3rd/sectools/pkg/mfa"
)
func main() {
manager, err := mfa.NewBackupCodeManager()
if err != nil {
panic(err)
}
set, err := manager.Generate()
if err != nil {
panic(err)
}
// Store set.Hashes and display set.Codes once.
_, _, _ = manager.Verify(set.Codes[0], set.Hashes)
}package main
import (
"github.com/hyp3rd/sectools/pkg/password"
)
func main() {
hasher, err := password.NewArgon2id(password.Argon2idBalanced())
if err != nil {
panic(err)
}
hash, err := hasher.Hash([]byte("secret"))
if err != nil {
panic(err)
}
ok, needsRehash, err := hasher.Verify([]byte("secret"), hash)
if err != nil {
panic(err)
}
_, _ = ok, needsRehash
}package main
import (
"context"
"github.com/hyp3rd/sectools/pkg/validate"
)
func main() {
emailValidator, err := validate.NewEmailValidator(
validate.WithEmailVerifyDomain(true),
)
if err != nil {
panic(err)
}
_, _ = emailValidator.Validate(context.Background(), "user@example.com")
urlValidator, err := validate.NewURLValidator(
validate.WithURLCheckRedirects(3),
)
if err != nil {
panic(err)
}
_, _ = urlValidator.Validate(context.Background(), "https://example.com")
}package main
import (
"github.com/hyp3rd/sectools/pkg/tokens"
)
func main() {
generator, err := tokens.NewGenerator()
if err != nil {
panic(err)
}
validator, err := tokens.NewValidator()
if err != nil {
panic(err)
}
token, _ := generator.Generate()
_, _ = validator.Validate(token)
}package main
import (
"github.com/hyp3rd/sectools/pkg/encoding"
)
func main() {
encoded, _ := encoding.EncodeBase64([]byte("secret"))
_, _ = encoding.DecodeBase64(encoded)
type payload struct {
Name string `json:"name"`
}
_ = encoding.DecodeJSON([]byte(`{"name":"alpha"}`), &payload{})
}package main
import (
"strings"
"github.com/hyp3rd/sectools/pkg/limits"
)
func main() {
var payload map[string]any
reader := strings.NewReader(`{"name":"alpha"}`)
err := limits.DecodeJSON(reader, &payload, limits.WithMaxBytes(1<<20))
if err != nil {
panic(err)
}
}package main
import (
"github.com/hyp3rd/sectools/pkg/secrets"
)
func main() {
detector, err := secrets.NewSecretDetector()
if err != nil {
panic(err)
}
_ = detector.DetectAny("token=ghp_abcdefghijklmnopqrstuvwxyz1234567890")
redactor, err := secrets.NewRedactor(secrets.WithRedactionDetector(detector))
if err != nil {
panic(err)
}
fields := map[string]any{"password": "secret"}
_ = redactor.RedactFields(fields)
}Hybrid post-quantum key exchange is optional and only negotiated in TLS 1.3. Peers that do not support it will fall back to X25519.
package main
import (
"crypto/tls"
"github.com/hyp3rd/sectools/pkg/tlsconfig"
)
func main() {
serverConfig, err := tlsconfig.NewServerConfig(
tlsconfig.WithCertificates(tls.Certificate{}),
tlsconfig.WithPostQuantumKeyExchange(),
tlsconfig.WithClientAuth(tls.RequireAndVerifyClientCert),
)
if err != nil {
panic(err)
}
_ = serverConfig
}package main
import (
"crypto/x509"
"github.com/hyp3rd/sectools/pkg/tlsconfig"
)
func main() {
roots, err := x509.SystemCertPool()
if err != nil {
panic(err)
}
clientConfig, err := tlsconfig.NewClientConfig(
tlsconfig.WithRootCAs(roots),
tlsconfig.WithServerName("api.example.com"),
tlsconfig.WithTLS13Only(),
tlsconfig.WithPostQuantumKeyExchange(),
)
if err != nil {
panic(err)
}
_ = clientConfig
}package main
import (
"github.com/hyp3rd/sectools/pkg/sanitize"
)
func main() {
htmlSanitizer, err := sanitize.NewHTMLSanitizer()
if err != nil {
panic(err)
}
safeHTML, _ := htmlSanitizer.Sanitize("<b>hello</b>")
sqlSanitizer, err := sanitize.NewSQLSanitizer(
sanitize.WithSQLMode(sanitize.SQLModeIdentifier),
sanitize.WithSQLAllowQualifiedIdentifiers(true),
)
if err != nil {
panic(err)
}
safeIdentifier, _ := sqlSanitizer.Sanitize("public.users")
detector, err := sanitize.NewNoSQLInjectionDetector()
if err != nil {
panic(err)
}
_ = detector.Detect(`{"username":{"$ne":null}}`)
_, _ = safeHTML, safeIdentifier
}ReadFileonly permits relative paths underos.TempDir()by default. UseNewWithOptionswithWithAllowAbsoluteto allow absolute paths or alternate roots.- Paths containing
..are rejected to prevent directory traversal. ReadFilehas no default size cap; useWithReadMaxSizewhen file size is untrusted.- Symlinks are rejected by default; when allowed, paths that resolve outside the allowed roots are rejected.
- File access is scoped with
os.OpenRooton the resolved root when symlinks are disallowed. When symlinks are allowed, files are opened via resolved paths after symlink checks. See the Goos.Rootdocs for platform-specific caveats. WriteFileuses atomic replace and fsync by default; setWithWriteDisableAtomicorWithWriteDisableSynconly if you accept durability risks. SetWithWriteSyncDirto fsync the parent directory after atomic rename for stronger durability guarantees (may be unsupported on some platforms/filesystems).- Optional ownership checks are available via
WithOwnerUID/WithOwnerGIDon Unix platforms. SecureBufferzeroizes memory onClear()and uses a finalizer as a best-effort fallback; callClear()when done.
- Detailed usage and behavior notes: Usage
- A quick reference for teams using sectools in production: Security checklist
prepare-toolchain— install core tools (gci, gofumpt, golangci-lint, staticcheck, govulncheck, gosec)prepare-proto-tools— install buf + protoc plugins (optional, controlled by PROTO_ENABLED)init— run setup-project.sh with current module and install tooling (respects PROTO_ENABLED)lint— gci, gofumpt, staticcheck, golangci-linttest/test-race/benchvet,sec,proto,run,run-container,update-deps,update-toolchain
- Tests required for changes; run
make lint testbefore PRs. - Suggested branch naming:
feat/<scope>,fix/<scope>,chore/<scope>. - Update docs when altering tooling, Make targets, or setup steps.
Follow the contributing guidelines.
Make sure you observe the Code of Conduct.
GPL-3.0. See LICENSE for details.