Fix #54: add HTTP server timeouts #60
No reviewers
Labels
No labels
nagonag
nagonag/ignore
bug
doc
duplicate
enhancement
help wanted
invalid
question
security
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
IUS/once!60
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/54-timeouts"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #54
Implements configurable HTTP server timeouts and the related preflight/test coverage.
Security fan-out review (4 parallel agents).
In-scope (introduced by this PR) — see inline comments
.golangci.yamlcollides with the.golangci.ymlalready on master.euidvars will be tripped by the newunusedlinter.fmt.Errorfintemplates.gouses%vinstead of%wand has a trailing\n.templates.Initchanges startup semantics.Upload→uploadrename leaves the handler-naming convention inconsistent.logout.gocomments-out instead of deleting.Pre-existing — out of scope for this PR but worth a follow-up issue
X-Forwarded-Protoinserve.go:checkTLSwhenTrustXForwarded=trueand the server is reachable directly. Trust should be tied to a CIDR/peer check, not a config flag.http.MaxBytesReaderaround uploads (upload.go):ParseMultipartForm(2<<20)only bounds the in-memory portion; the request body itself is unbounded. Same neighbourhood as #54.upload.go):f.Open()returns amultipart.File(io.ReadCloser) but the loop assigns to a localio.Readerand never closes it.FindFile(key)filepath.Joins an arbitrary key into the store dir — path-traversal surface; the download handler strips a URL prefix and passes the rest askey.upload.go):filepath.Join(basename, f.Filename)with attacker-controlledf.Filename. Stored, not extracted server-side, so impact is on whoever unzips it later.MaxHeaderBytesnot explicitly set inbuildHTTPServer()— defense-in-depth nit.Stats (serve.go,templates.go): cosmetic at init time, but worthfilepath.EvalSymlinksif the paths are user-supplied.Verdict
Nothing critical introduced. The lint-config collision and dead
euidvars are the only real blockers (CI will fail once the new linters run). After fixing those two, the rest is style/cleanup.review (co)authored by ai:gpt-5.1
@ -0,0 +1,6 @@linters:[med] Collides with the existing
.golangci.yml(already on master, golangci-lint v2 schema withversion: "2"). After merge both files exist with conflicting settings —wslhere vs.wsl_v5there, plusgosec/unusedonly in this one and noversionfield. Pick one config and delete the other.@ -0,0 +4,4 @@import "golang.org/x/sys/unix"var euid = unix.Geteuid()[med]
euidis dead code:main.go:88callsunix.Geteuid()directly. With theunusedlinter just enabled by this same PR, this will fail CI. Either drop botheuid_*.gofiles or wireeuidinto the root check inmain.go.@ -6,3 +6,1 @@import "net/http"func Logout(w http.ResponseWriter, r *http.Request) {/* not in use currently[nit] Commenting out a function leaves dead source. Either delete the file or guard it behind
//go:build neverso the compiler still type-checks it without linking.@ -118,1 +122,4 @@go func() {defer wg.Done()if cf.Global.TemplatesDir != "" {[low] Wrapping
templates.Initinif cf.Global.TemplatesDir != ""changes behaviour: previouslyInit("")was always called and parsed the embeddedhtml/*.htmlset eagerly; nowttstays nil until the first request andExecuteTemplatedoes it in-band on a hot path. If that's intentional, fine — but the pre-flight goroutine becomes a no-op for the default config, which defeats the framing. Suggest callingtemplates.Init(cf.Global.TemplatesDir)unconditionally and lettingInitdecide.@ -104,2 +104,4 @@if dir != "" {if fi, err := os.Stat(dir); err != nil {return nil, fmt.Errorf("templates dir: %v", err)[low] Use
%wto wrap the underlying error:fmt.Errorf("templates dir: %w", err).%vflattens the error and breakserrors.Is(err, fs.ErrNotExist)for callers.@ -106,0 +106,4 @@if fi, err := os.Stat(dir); err != nil {return nil, fmt.Errorf("templates dir: %v", err)} else if !fi.IsDir() {return nil, fmt.Errorf("templates dir: not a directory: %q\n", dir)[nit] Drop the trailing
\nfrom theErrorfformat string —go vetflags it.@ -32,3 +32,3 @@const FormFilename = ".file"func Upload(w http.ResponseWriter, r *http.Request) {func upload(w http.ResponseWriter, r *http.Request) {[nit]
Upload→uploadwhileDownload,Remove,Userstay exported. None of them are used outsidepackage main, so lowercasing all four would be the consistent move.@ -159,3 +160,3 @@zipW := zip.NewWriter(zipfile)defer zipW.Close()defer zipW.Close() //nolint:errcheck // closed explictly later[nit] Typo:
explictly→explicitly.Security Review: Privilege & Authentication ✓
Excellent restoration! The cross-platform root check is solid:
Build Tags & Isolation:
euid_linux.goandeuid_windows.gouse mutually exclusive build tags (//go:build linuxand//go:build windows), ensuring clean platform separation. No risk of accidental mixed builds.Init-Time Evaluation: Both implementations correctly initialize at package init time—Linux via
unix.Geteuid()(fromgolang.org/x/sys/unix), Windows via sentinel-1. This is evaluated beforemain()runs, guaranteeing the check at line 86 operates on a valid value.Privilege Check: The guard
if euid == 0 && !cf.Global.RootOkis well-positioned early in main() and properly gates root execution with a documented config override. Clear failure message helps operators.Windows Compatibility: No issues—euid = -1 on Windows means the check safely short-circuits (never matches
euid == 0). ✓No Dead Code Issues: The
euidvariable is actively referenced on line 86; linters should be satisfied.Minor note: The
rootOkoverride is appropriately guarded by config, not a flag—good security posture.Review by privilege & auth security agent
Security Review: Filesystem & Input Handling ✓
✓ Security posture maintained — filesystem & input handling remain sound:
Pre-flight checks: Both
serve.goandtemplates.goproperly validate directory existence and type at startup viaos.Stat(). This prevents runtime surprises and ensures the store/template directories are legitimate directories, not symlinks to untrusted locations.Error wrapping fix: The change from
%vto%wintemplates.gois the correct Go practice for error chain preservation. Also cleaned up an unnecessary newline in error messages.Handler naming: Lowercasing
Download/Removetodownload/removeis pure Go idiom (unexported functions) — no security implications since registration viahttp.HandlerFunc()remains identical.Path traversal protection: Handlers properly sanitize untrusted input:
remove.go: Usespath.Base()to extract keysdownload.go: Validates path containment before extractionupload.go: Validates exact path matchingstore.go: Usesfilepath.Base()inCreateFileConcurrent download safety: The
Claim()method's atomicos.Rename()prevents TOCTOU vulnerabilities.UUID-based storage: Files stored under UUIDs, not user filenames — excellent defense-in-depth against enumeration.
No new security concerns introduced. ✓
Review by filesystem & input security agent
Security Review: HTTP Server Hardening ✓
Excellent hardening work! The timeout implementation is solid:
Strengths:
checkTLS()is strict by default (rejects non-TLS unlessInsecureOk=true); supportsX-Forwarded-Protofor proxies.upload,download,remove) wrapped with TLS checks.Pre-existing issue (not blocking):
Request body is unbounded—no explicit
MaxBytesReaderlimit. The 30sReadTimeoutprovides some DoS mitigation, but consider adding an explicit body size limit (e.g.,io.LimitReader) in upload handlers for defense-in-depth.Overall: Well-executed timeout hardening with strong testing. Ready to merge.
Review by HTTP & DoS security agent
Security Review: Error Handling & Code Quality ✓
All key areas check out well:
✅ Lint Configuration: Removed
.golangci.yamlcorrectly (eliminated collision). No conflicting lint config files remain, allowing clean default behavior.✅ Dead Code Removal:
logout.gowas properly deleted—confirmed it was unused and marked "does not work yet."✅ Error Handling:
templates.goproperly uses%wfor error wrapping without trailing newlines. Error messages to clients stay secure (logged server-side only, returning only HTTP status codes).✅ Resource Management: Password file pre-flight check uses
_ = fh.Close()appropriately. Multipart upload handlers use explicit//nolint:errcheckwith clear comments where close errors are non-critical (files already synced before close).✅ No Information Disclosure: All handlers (download, upload, remove, user) sanitize responses—credentials/paths stay internal, clients see only safe status codes or "Not Authorized" messages.
✅ Cross-platform Support: euid checks now use build-tagged files (
euid_linux.go/euid_windows.go) instead of Unix-only imports. Better for portability.✅ Code Quality: Function naming normalized (public→private). Removed unnecessary linter exceptions. Store package simplified. All whitespace/formatting improvements.
Zero security concerns identified. This is a solid cleanup with proper attention to error handling and information disclosure.
Review by error handling & code quality security agent
dcdd7d04ffto0062226563