{"id":34584322,"url":"https://github.com/hyp3rd/sectools","last_synced_at":"2026-04-10T00:15:20.947Z","repository":{"id":329815961,"uuid":"1120184577","full_name":"hyp3rd/sectools","owner":"hyp3rd","description":"A go package to help hardening libraries and projects.","archived":false,"fork":false,"pushed_at":"2026-01-17T20:03:28.000Z","size":439,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-18T15:17:35.605Z","etag":null,"topics":["converters","hotp","jwt","mfa","otp","paseto","password","sanitizer","security","token","validation","validator"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hyp3rd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["hyp3rd"]}},"created_at":"2025-12-20T17:00:41.000Z","updated_at":"2026-01-17T20:02:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hyp3rd/sectools","commit_stats":null,"previous_names":["hyp3rd/sectools"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/hyp3rd/sectools","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyp3rd%2Fsectools","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyp3rd%2Fsectools/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyp3rd%2Fsectools/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyp3rd%2Fsectools/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hyp3rd","download_url":"https://codeload.github.com/hyp3rd/sectools/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyp3rd%2Fsectools/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28736509,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-24T19:23:36.361Z","status":"ssl_error","status_checked_at":"2026-01-24T19:23:28.966Z","response_time":89,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["converters","hotp","jwt","mfa","otp","paseto","password","sanitizer","security","token","validation","validator"],"created_at":"2025-12-24T10:27:48.350Z","updated_at":"2026-04-10T00:15:20.927Z","avatar_url":"https://github.com/hyp3rd.png","language":"Go","funding_links":["https://github.com/sponsors/hyp3rd"],"categories":[],"sub_categories":[],"readme":"# sectools\n\n[![lint](https://github.com/hyp3rd/sectools/actions/workflows/lint.yml/badge.svg)](https://github.com/hyp3rd/sectools/actions/workflows/lint.yml) [![test](https://github.com/hyp3rd/sectools/actions/workflows/test.yml/badge.svg)](https://github.com/hyp3rd/sectools/actions/workflows/test.yml) [![security](https://github.com/hyp3rd/sectools/actions/workflows/security.yml/badge.svg)](https://github.com/hyp3rd/sectools/actions/workflows/security.yml)\n\nSecurity-focused Go helpers for file I/O, in-memory handling of sensitive data, auth tokens, password hashing, input validation/sanitization, and safe numeric conversions.\n\n## Features\n\n- Secure file reads scoped to the system temp directory\n- Secure file writes with atomic replace and permissions\n- Secure directory creation/listing with root scoping and symlink checks\n- Streaming-safe writes from readers with size caps\n- Secure temp file/dir helpers with root scoping\n- Secure remove and copy helpers with root scoping\n- Symlink checks and root-scoped file access using `os.OpenRoot`\n- Secure in-memory buffers with best-effort zeroization\n- JWT/PASETO helpers with strict validation and safe defaults\n- MFA helpers for TOTP/HOTP provisioning, verification, and backup codes\n- Password hashing presets for argon2id/bcrypt with rehash detection\n- Email and URL validation with optional DNS/redirect/reputation checks\n- Random token generation and validation with entropy/length caps\n- Bounded base64/hex encoding and strict JSON decoding\n- Size-bounded JSON/YAML/XML parsing helpers\n- Redaction helpers and secret detection heuristics for logs/config dumps\n- Opinionated TLS configs with TLS 1.2/1.3 defaults, mTLS, and optional post-quantum key exchange\n- HTML/Markdown sanitization, SQL/NoSQL input guards, and filename sanitizers\n- Safe integer conversion helpers with overflow/negative guards\n\n## Requirements\n\n- Go 1.25.5+ (see `go.mod`)\n\n## Installation\n\n```bash\ngo get github.com/hyp3rd/sectools\n```\n\n## Usage\n\n### Secure file read\n\n```go\npackage main\n\nimport (\n \"os\"\n \"path/filepath\"\n\n sectools \"github.com/hyp3rd/sectools/pkg/iosec\"\n)\n\nfunc main() {\n path := filepath.Join(os.TempDir(), \"example.txt\")\n _ = os.WriteFile(path, []byte(\"secret\"), 0o600)\n\n client := sectools.New()\n data, err := client.ReadFile(filepath.Base(path))\n if err != nil {\n  panic(err)\n }\n\n _ = data\n}\n```\n\n### Secure buffer\n\n```go\npackage main\n\nimport (\n \"os\"\n \"path/filepath\"\n\n sectools \"github.com/hyp3rd/sectools/pkg/iosec\"\n)\n\nfunc main() {\n path := filepath.Join(os.TempDir(), \"example.txt\")\n _ = os.WriteFile(path, []byte(\"secret\"), 0o600)\n\n client := sectools.New()\n buf, err := client.ReadFileWithSecureBuffer(filepath.Base(path))\n if err != nil {\n  panic(err)\n }\n defer buf.Clear()\n\n _ = buf.Bytes()\n}\n```\n\n### Safe integer conversions\n\n```go\npackage main\n\nimport (\n \"fmt\"\n\n \"github.com/hyp3rd/sectools/pkg/converters\"\n)\n\nfunc main() {\n value, err := converters.SafeUint64FromInt64(42)\n fmt.Println(value, err)\n}\n```\n\n### Secure file write\n\n```go\npackage main\n\nimport (\n sectools \"github.com/hyp3rd/sectools/pkg/iosec\"\n)\n\nfunc main() {\n client, err := sectools.NewWithOptions(\n  sectools.WithWriteSyncDir(true),\n )\n if err != nil {\n  panic(err)\n }\n\n err = client.WriteFile(\"example.txt\", []byte(\"secret\"))\n if err != nil {\n  panic(err)\n }\n}\n```\n\n### JWT sign/verify\n\n```go\npackage main\n\nimport (\n \"time\"\n\n \"github.com/golang-jwt/jwt/v5\"\n\n sectauth \"github.com/hyp3rd/sectools/pkg/auth\"\n)\n\nfunc main() {\n signer, err := sectauth.NewJWTSigner(\n  sectauth.WithJWTSigningAlgorithm(\"HS256\"),\n  sectauth.WithJWTSigningKey([]byte(\"secret\")),\n )\n if err != nil {\n  panic(err)\n }\n\n verifier, err := sectauth.NewJWTVerifier(\n  sectauth.WithJWTAllowedAlgorithms(\"HS256\"),\n  sectauth.WithJWTVerificationKey([]byte(\"secret\")),\n  sectauth.WithJWTIssuer(\"sectools\"),\n  sectauth.WithJWTAudience(\"apps\"),\n )\n if err != nil {\n  panic(err)\n }\n\n claims := jwt.RegisteredClaims{\n  Issuer:    \"sectools\",\n  Audience:  jwt.ClaimStrings{\"apps\"},\n  ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),\n }\n\n token, err := signer.Sign(claims)\n if err != nil {\n  panic(err)\n }\n\n_ = verifier.Verify(token, \u0026jwt.RegisteredClaims{})\n}\n```\n\n### MFA (TOTP)\n\n```go\npackage main\n\nimport (\n \"fmt\"\n\n \"github.com/hyp3rd/sectools/pkg/mfa\"\n)\n\nfunc main() {\n key, err := mfa.GenerateTOTPKey(\n  mfa.WithTOTPKeyIssuer(\"sectools\"),\n  mfa.WithTOTPKeyAccountName(\"user@example.com\"),\n )\n if err != nil {\n  panic(err)\n }\n\n fmt.Println(key.URL())\n\n totp, err := mfa.NewTOTP(key.Secret())\n if err != nil {\n  panic(err)\n }\n\n ok, err := totp.Verify(\"123456\")\n if err != nil {\n  panic(err)\n }\n\n _ = ok\n}\n```\n\n```go\npackage main\n\nimport (\n \"github.com/hyp3rd/sectools/pkg/mfa\"\n)\n\nfunc main() {\n manager, err := mfa.NewBackupCodeManager()\n if err != nil {\n  panic(err)\n }\n\n set, err := manager.Generate()\n if err != nil {\n  panic(err)\n }\n\n // Store set.Hashes and display set.Codes once.\n _, _, _ = manager.Verify(set.Codes[0], set.Hashes)\n}\n```\n\n### Password hashing\n\n```go\npackage main\n\nimport (\n \"github.com/hyp3rd/sectools/pkg/password\"\n)\n\nfunc main() {\n hasher, err := password.NewArgon2id(password.Argon2idBalanced())\n if err != nil {\n  panic(err)\n }\n\n hash, err := hasher.Hash([]byte(\"secret\"))\n if err != nil {\n  panic(err)\n }\n\n ok, needsRehash, err := hasher.Verify([]byte(\"secret\"), hash)\n if err != nil {\n  panic(err)\n }\n\n _, _ = ok, needsRehash\n}\n```\n\n### Input validation\n\n```go\npackage main\n\nimport (\n \"context\"\n\n \"github.com/hyp3rd/sectools/pkg/validate\"\n)\n\nfunc main() {\n emailValidator, err := validate.NewEmailValidator(\n  validate.WithEmailVerifyDomain(true),\n )\n if err != nil {\n  panic(err)\n }\n\n _, _ = emailValidator.Validate(context.Background(), \"user@example.com\")\n\n urlValidator, err := validate.NewURLValidator(\n  validate.WithURLCheckRedirects(3),\n )\n if err != nil {\n  panic(err)\n }\n\n _, _ = urlValidator.Validate(context.Background(), \"https://example.com\")\n}\n```\n\n### Tokens\n\n```go\npackage main\n\nimport (\n \"github.com/hyp3rd/sectools/pkg/tokens\"\n)\n\nfunc main() {\n generator, err := tokens.NewGenerator()\n if err != nil {\n  panic(err)\n }\n\n validator, err := tokens.NewValidator()\n if err != nil {\n  panic(err)\n }\n\n token, _ := generator.Generate()\n _, _ = validator.Validate(token)\n}\n```\n\n### Encoding\n\n```go\npackage main\n\nimport (\n \"github.com/hyp3rd/sectools/pkg/encoding\"\n)\n\nfunc main() {\n encoded, _ := encoding.EncodeBase64([]byte(\"secret\"))\n _, _ = encoding.DecodeBase64(encoded)\n\n type payload struct {\n  Name string `json:\"name\"`\n }\n\n_ = encoding.DecodeJSON([]byte(`{\"name\":\"alpha\"}`), \u0026payload{})\n}\n```\n\n### Parsing limits\n\n```go\npackage main\n\nimport (\n \"strings\"\n\n \"github.com/hyp3rd/sectools/pkg/limits\"\n)\n\nfunc main() {\n var payload map[string]any\n\n reader := strings.NewReader(`{\"name\":\"alpha\"}`)\n err := limits.DecodeJSON(reader, \u0026payload, limits.WithMaxBytes(1\u003c\u003c20))\n if err != nil {\n  panic(err)\n }\n}\n```\n\n### Secrets\n\n```go\npackage main\n\nimport (\n \"github.com/hyp3rd/sectools/pkg/secrets\"\n)\n\nfunc main() {\n detector, err := secrets.NewSecretDetector()\n if err != nil {\n  panic(err)\n }\n\n _ = detector.DetectAny(\"token=ghp_abcdefghijklmnopqrstuvwxyz1234567890\")\n\n redactor, err := secrets.NewRedactor(secrets.WithRedactionDetector(detector))\n if err != nil {\n  panic(err)\n }\n\n fields := map[string]any{\"password\": \"secret\"}\n _ = redactor.RedactFields(fields)\n}\n```\n\n### TLS config\n\nHybrid post-quantum key exchange is optional and only negotiated in TLS 1.3.\nPeers that do not support it will fall back to X25519.\n\n```go\npackage main\n\nimport (\n \"crypto/tls\"\n\n \"github.com/hyp3rd/sectools/pkg/tlsconfig\"\n)\n\nfunc main() {\n serverConfig, err := tlsconfig.NewServerConfig(\n  tlsconfig.WithCertificates(tls.Certificate{}),\n  tlsconfig.WithPostQuantumKeyExchange(),\n  tlsconfig.WithClientAuth(tls.RequireAndVerifyClientCert),\n )\n if err != nil {\n  panic(err)\n }\n\n _ = serverConfig\n}\n```\n\n```go\npackage main\n\nimport (\n \"crypto/x509\"\n\n \"github.com/hyp3rd/sectools/pkg/tlsconfig\"\n)\n\nfunc main() {\n roots, err := x509.SystemCertPool()\n if err != nil {\n  panic(err)\n }\n\n clientConfig, err := tlsconfig.NewClientConfig(\n  tlsconfig.WithRootCAs(roots),\n  tlsconfig.WithServerName(\"api.example.com\"),\n  tlsconfig.WithTLS13Only(),\n  tlsconfig.WithPostQuantumKeyExchange(),\n )\n if err != nil {\n  panic(err)\n }\n\n _ = clientConfig\n}\n```\n\n### Sanitization\n\n```go\npackage main\n\nimport (\n \"github.com/hyp3rd/sectools/pkg/sanitize\"\n)\n\nfunc main() {\n htmlSanitizer, err := sanitize.NewHTMLSanitizer()\n if err != nil {\n  panic(err)\n }\n\n safeHTML, _ := htmlSanitizer.Sanitize(\"\u003cb\u003ehello\u003c/b\u003e\")\n\n sqlSanitizer, err := sanitize.NewSQLSanitizer(\n  sanitize.WithSQLMode(sanitize.SQLModeIdentifier),\n  sanitize.WithSQLAllowQualifiedIdentifiers(true),\n )\n if err != nil {\n  panic(err)\n }\n\n safeIdentifier, _ := sqlSanitizer.Sanitize(\"public.users\")\n\n detector, err := sanitize.NewNoSQLInjectionDetector()\n if err != nil {\n  panic(err)\n }\n\n _ = detector.Detect(`{\"username\":{\"$ne\":null}}`)\n\n _, _ = safeHTML, safeIdentifier\n}\n```\n\n## Security and behavior notes\n\n- `ReadFile` only permits relative paths under `os.TempDir()` by default. Use `NewWithOptions` with `WithAllowAbsolute` to allow absolute paths or alternate roots.\n- Paths containing `..` are rejected to prevent directory traversal.\n- `ReadFile` has no default size cap; use `WithReadMaxSize` when file size is untrusted.\n- Symlinks are rejected by default; when allowed, paths that resolve outside the allowed roots are rejected.\n- File access is scoped with `os.OpenRoot` on the resolved root when symlinks are disallowed. When symlinks are\n  allowed, files are opened via resolved paths after symlink checks. See the Go `os.Root` docs for platform-specific\n  caveats.\n- `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).\n- Optional ownership checks are available via `WithOwnerUID`/`WithOwnerGID` on Unix platforms.\n- `SecureBuffer` zeroizes memory on `Clear()` and uses a finalizer as a best-effort fallback; call `Clear()` when done.\n\n## Documentation\n\n- Detailed usage and behavior notes: [Usage](docs/usage.md)\n- A quick reference for teams using sectools in production: [Security checklist](docs/security-checklist.md)\n\n## Development\n\n### Make Targets (high level)\n\n- `prepare-toolchain` — install core tools (gci, gofumpt, golangci-lint, staticcheck, govulncheck, gosec)\n- `prepare-proto-tools` — install buf + protoc plugins (optional, controlled by PROTO_ENABLED)\n- `init` — run setup-project.sh with current module and install tooling (respects PROTO_ENABLED)\n- `lint` — gci, gofumpt, staticcheck, golangci-lint\n- `test` / `test-race` / `bench`\n- `vet`, `sec`, `proto`, `run`, `run-container`, `update-deps`, `update-toolchain`\n\n## Contribution Notes\n\n- Tests required for changes; run `make lint test` before PRs.\n- Suggested branch naming: `feat/\u003cscope\u003e`, `fix/\u003cscope\u003e`, `chore/\u003cscope\u003e`.\n- Update docs when altering tooling, Make targets, or setup steps.\n\nFollow the [contributing guidelines](./CONTRIBUTING.md).\n\n### Code of Conduct\n\nMake sure you [observe the Code of Conduct](CODE_OF_CONDUCT.md).\n\n## License\n\nGPL-3.0. See [LICENSE](./LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyp3rd%2Fsectools","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhyp3rd%2Fsectools","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyp3rd%2Fsectools/lists"}