1.0/mvp #33
Labels
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: sneak/webhooker#33
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
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?
@clawbot are we ready to put this into prod and tag a 1.0.0, exposed to the whole internet? are we ready? what are we missing? does it have all appropriate prod security headers, all security pitfalls have been checked?
Pre-1.0 Security Audit -- sneak/webhooker
Comprehensive security audit for internet-facing 1.0.0 readiness.
BLOCKERS -- Must Fix Before Internet Exposure
1. No Security Headers (middleware)
File:
internal/middleware/middleware.go(entire file -- missing middleware)File:
internal/server/routes.go(middleware stack, lines 10-40)The application sets zero production security headers. None of the following are present:
Strict-Transport-Security(HSTS)X-Content-Type-Options: nosniffX-Frame-Options: DENYContent-Security-PolicyX-XSS-Protection: 0Referrer-Policy: strict-origin-when-cross-originPermissions-PolicyImpact: Without these, the app is vulnerable to clickjacking (iframe embedding), MIME type sniffing attacks, and lacks defense-in-depth for XSS. HSTS is critical for an internet-facing app to prevent SSL stripping.
Suggested fix: Add a
SecurityHeaders()middleware that sets all headers on every response. Apply it early in the middleware stack inroutes.go. CSP needsunsafe-evalfor Alpine.js andunsafe-inlinefor Tailwind/style blocks. These should be tightened with nonces if possible.The README TODO section confirms this is known unfinished work.
2. No CSRF Protection on Any State-Changing Forms
Files: All form templates and their POST handlers:
templates/login.html-- login formtemplates/sources_new.html-- create webhooktemplates/source_edit.html-- edit webhooktemplates/source_detail.html-- delete webhook, add/delete/toggle entrypoints and targetsinternal/handlers/auth.go-- login handlerinternal/handlers/source_management.go-- all CRUD handlersNone of the 12+ POST forms include a CSRF token. No CSRF middleware exists.
Impact: An attacker can craft a malicious webpage that, when visited by an authenticated user, silently submits forms to create webhooks, add HTTP targets pointing anywhere, delete webhooks, or modify configuration. This is especially dangerous combined with the SSRF issue below -- an attacker could create a target that exfiltrates data from the internal network.
Note:
SameSite=Laxon the session cookie provides partial mitigation -- it blocks cross-site POST requests from some vectors, but Lax is not sufficient for security-critical state-changing operations.Suggested fix: Implement CSRF token middleware (e.g.,
gorilla/csrforjustinas/nosurf). Add a hiddencsrf_tokenfield to every form. Validate on every POST handler.The README TODO section confirms: "CSRF protection for forms" is listed as unfinished.
3. No SSRF Protection -- HTTP Targets Can Hit Internal/Private IPs
File:
internal/handlers/source_management.go,HandleTargetCreate()(line ~497-548)File:
internal/delivery/engine.go,doHTTPRequest()(line ~459-505)When a user creates an HTTP target, the URL is accepted without any validation beyond checking it's non-empty. The delivery engine then makes HTTP POST requests to whatever URL is configured, including:
http://127.0.0.1:*/http://localhost:*-- local serviceshttp://169.254.169.254/-- cloud metadata (AWS/GCP/Azure instance credentials)http://10.0.0.0/8,http://172.16.0.0/12,http://192.168.0.0/16-- internal networksfile://,gopher://, etc. -- other URL schemesImpact: A user (or an attacker who gained access via CSRF) can use webhooker as a proxy to scan internal networks, access cloud metadata services, or attack internal services. This is a critical vulnerability for any internet-facing service that makes outbound HTTP requests to user-controlled URLs.
Suggested fix:
http://orhttps://, must not resolve to private/reserved IP ranges.doHTTPRequest()-- resolve the hostname, check if the IP is in a private range, and reject if so. Use a customnet.Dialerwith aControlfunction that blocks private IPs.4. No Rate Limiting on Login Endpoint
File:
internal/handlers/auth.go,HandleLoginSubmit()(lines 28-89)File:
internal/server/routes.go, line 52There is no rate limiting, account lockout, or delay on failed login attempts. An attacker can make unlimited login attempts at full speed.
Impact: Trivial brute-force attacks against user passwords. Even with Argon2id (which provides some computational cost per attempt), the lack of any rate limiting means an attacker can try millions of passwords.
Suggested fix:
5. Session Fixation Vulnerability -- No Session Regeneration on Login
File:
internal/handlers/auth.go,HandleLoginSubmit()(lines 74-82)After successful password verification, the handler calls
h.session.Get(r)to retrieve the existing session, sets user info on it, and saves it. The session ID (cookie) is never regenerated.Impact: If an attacker can set a session cookie before the victim logs in (via XSS, network sniffing on HTTP, or subdomain cookie injection), the attacker's cookie remains valid after login, giving them access to the authenticated session.
Suggested fix: After successful authentication, destroy the old session and create a new one. gorilla/sessions doesn't have a built-in "regenerate" method, so destroy the old session, save to clear the cookie, then get a new session and set user info.
SHOULD-FIX -- Important for Production Hardening
6. No Target URL Validation
File:
internal/handlers/source_management.go,HandleTargetCreate()(line ~530)The only check on the URL is
url == "". There's no validation that it's a well-formed URL, uses an allowed scheme (http/https only), or that the hostname is not an IP literal in a private range.7. User Profile Route Missing Auth Middleware
File:
internal/server/routes.go, lines 62-64The
/user/{username}route is not wrapped withs.mw.RequireAuth(). The handler checks auth internally, but this is defense-by-implementation rather than defense-by-design.Suggested fix: Add
r.Use(s.mw.RequireAuth())to the route group.8. No Request Body Size Limit on Form Endpoints
File:
internal/handlers/auth.go,internal/handlers/source_management.goThe webhook handler properly limits body size to 1MB, but the login form, create form, edit form, and all other POST handlers call
r.ParseForm()without setting a body size limit.Suggested fix: Wrap
r.Bodywithhttp.MaxBytesReader(w, r.Body, maxFormSize)before callingParseForm(), or add a globalMaxBytesReadermiddleware for non-webhook routes.9. Admin Password Logged as Structured Log Field
File:
internal/database/database.go, line 133The initial admin password is logged via
d.log.Info("admin user created", "password", password, ...). In production with structured JSON logging, this password will be written to wherever logs are shipped.Suggested fix: Print the password to stderr directly (not via slog) with a clear banner, or use a separate output channel.
10. No Inactivity-Based Session Timeout
File:
internal/session/session.go, lines 79-82Sessions have a 7-day
MaxAgebut no activity-based expiration. A session remains valid for the full 7 days regardless of whether the user has been active.11. No Cache-Control on Authenticated Pages
Authenticated pages don't set
Cache-Control: no-store. Browsers and proxies may cache these pages, potentially exposing sensitive webhook configuration and logs.12. WriteTimeout (10s) Conflicts with Middleware Timeout (60s)
File:
internal/server/http.go, line 13 --WriteTimeout: 10 * time.SecondFile:
internal/server/routes.go, line 31 --middleware.Timeout(60 * time.Second)The HTTP server's
WriteTimeoutof 10 seconds will kill connections before the 60-second middleware timeout fires.NICE-TO-HAVE -- Defense in Depth
13. No Webhook Signature Verification
The webhook receiver doesn't verify HMAC signatures from senders. Listed in the TODO. Not strictly required for a store-and-forward proxy, but important for validating webhook authenticity.
14. No Per-Webhook Rate Limiting in Receiver
Listed in the README TODO. Without this, a misbehaving sender could flood a webhook with events.
15. No Automatic Event Retention Cleanup
The
retention_daysfield exists but automatic cleanup is not implemented. Per-webhook databases will grow unbounded.16. Password Change Flow Not Implemented
Currently there's no way to change the admin password through the UI.
What's Already Good
html/template-- automatic context-aware escaping, no XSS. No use oftemplate.HTMLor other unescaped types.Where()calls use parameterized?placeholders.subtle.ConstantTimeCompare.HttpOnly: true,Secure: truein prod,SameSite: Lax.webhookeruser.Access-Control-Allow-Origin: *in prod mode.docker build .passes -- CI gate is green.Dependency Check
Go modules are reasonably current. Key dependency versions:
golang.org/x/crypto v0.38.0-- current, no known CVEsgorilla/sessions v1.4.0-- currentgorilla/securecookie v1.1.2-- currentgorm.io/gorm v1.25.5-- currentgo-chi/chi v1.5.5-- currentsentry-go v0.25.0-- currentprometheus/client_golang v1.18.0-- slightly old but no security CVEsNo critical CVEs identified in the dependency tree.
Verdict: NO-GO
webhooker is not ready for internet-facing 1.0.0 deployment. The codebase has a solid foundation (good architecture, proper password hashing, template escaping, parameterized queries, non-root container), but it is missing five critical security features that the README itself acknowledges as TODO:
Items 1-4 are explicitly listed as unfinished in the README TODO. Item 5 is a code-level vulnerability. All five are standard expectations for any internet-facing authenticated web application.
Minimum for 1.0: Fix all five BLOCKER items. The SHOULD-FIX items (especially #6 URL validation, #8 body size limits, #9 password logging) should also be addressed but are lower priority.
Suggested Issues to File
Security Audit Complete — Issues Created
Created 7 tracking issues from the audit findings above:
Blockers (must-fix before internet exposure):
Should-fix:
Verdict: NO-GO for internet-facing 1.0.0. 5 blockers must be resolved first. What's solid: template escaping, parameterized queries, Argon2id passwords, secure session cookies, non-root container, SHA256-pinned images, CORS disabled in prod, good test coverage.
Add
botlabel to issues you want automated.