https://github.com/hyp3rd/sectools
A go package to help hardening libraries and projects.
https://github.com/hyp3rd/sectools
converters hotp jwt mfa otp paseto password sanitizer security token validation validator
Last synced: 6 days ago
JSON representation
A go package to help hardening libraries and projects.
- Host: GitHub
- URL: https://github.com/hyp3rd/sectools
- Owner: hyp3rd
- License: gpl-3.0
- Created: 2025-12-20T17:00:41.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-01-17T20:03:28.000Z (about 1 month ago)
- Last Synced: 2026-01-18T15:17:35.605Z (about 1 month ago)
- Topics: converters, hotp, jwt, mfa, otp, paseto, password, sanitizer, security, token, validation, validator
- Language: Go
- Homepage:
- Size: 429 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Codeowners: CODEOWNERS
- Security: SECURITY.md
Awesome Lists containing this project
README
# sectools
[](https://github.com/hyp3rd/sectools/actions/workflows/lint.yml) [](https://github.com/hyp3rd/sectools/actions/workflows/test.yml) [](https://github.com/hyp3rd/sectools/actions/workflows/security.yml)
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.
## Features
- 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
## Requirements
- Go 1.25.5+ (see `go.mod`)
## Installation
```bash
go get github.com/hyp3rd/sectools
```
## Usage
### Secure file read
```go
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()
data, err := client.ReadFile(filepath.Base(path))
if err != nil {
panic(err)
}
_ = data
}
```
### Secure buffer
```go
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()
}
```
### Safe integer conversions
```go
package main
import (
"fmt"
"github.com/hyp3rd/sectools/pkg/converters"
)
func main() {
value, err := converters.SafeUint64FromInt64(42)
fmt.Println(value, err)
}
```
### Secure file write
```go
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)
}
}
```
### JWT sign/verify
```go
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{})
}
```
### MFA (TOTP)
```go
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
}
```
```go
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)
}
```
### Password hashing
```go
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
}
```
### Input validation
```go
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")
}
```
### Tokens
```go
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)
}
```
### Encoding
```go
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{})
}
```
### Parsing limits
```go
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)
}
}
```
### Secrets
```go
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)
}
```
### TLS config
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.
```go
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
}
```
```go
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
}
```
### Sanitization
```go
package main
import (
"github.com/hyp3rd/sectools/pkg/sanitize"
)
func main() {
htmlSanitizer, err := sanitize.NewHTMLSanitizer()
if err != nil {
panic(err)
}
safeHTML, _ := htmlSanitizer.Sanitize("hello")
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
}
```
## Security and behavior notes
- `ReadFile` only permits relative paths under `os.TempDir()` by default. Use `NewWithOptions` with `WithAllowAbsolute` to allow absolute paths or alternate roots.
- Paths containing `..` are rejected to prevent directory traversal.
- `ReadFile` has no default size cap; use `WithReadMaxSize` when 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.OpenRoot` on the resolved root when symlinks are disallowed. When symlinks are
allowed, files are opened via resolved paths after symlink checks. See the Go `os.Root` docs for platform-specific
caveats.
- `WriteFile` uses atomic replace and fsync by default; set `WithWriteDisableAtomic` or `WithWriteDisableSync` only if you accept durability risks. Set `WithWriteSyncDir` to 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`/`WithOwnerGID` on Unix platforms.
- `SecureBuffer` zeroizes memory on `Clear()` and uses a finalizer as a best-effort fallback; call `Clear()` when done.
## Documentation
- Detailed usage and behavior notes: [Usage](docs/usage.md)
- A quick reference for teams using sectools in production: [Security checklist](docs/security-checklist.md)
## Development
### Make Targets (high level)
- `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-lint
- `test` / `test-race` / `bench`
- `vet`, `sec`, `proto`, `run`, `run-container`, `update-deps`, `update-toolchain`
## Contribution Notes
- Tests required for changes; run `make lint test` before PRs.
- Suggested branch naming: `feat/`, `fix/`, `chore/`.
- Update docs when altering tooling, Make targets, or setup steps.
Follow the [contributing guidelines](./CONTRIBUTING.md).
### Code of Conduct
Make sure you [observe the Code of Conduct](CODE_OF_CONDUCT.md).
## License
GPL-3.0. See [LICENSE](./LICENSE) for details.