Repository navigation
Conversation
…oxy-route requests The native Windows defaults bound the plain-HTTP dashboard to 0.0.0.0:7070 and the installer opened it to the local subnet, so session cookies and the admin login travelled in clear text across the LAN. The proxy-to-server route on 0.0.0.0:7071 also served any request without the gateway secret as an ordinary client, which made it a second plain-HTTP copy of the dashboard. - default listen and the installer now use 127.0.0.1:7070; Docker Desktop still forwards host.docker.internal to the host's loopback - the installer removes the old 7070 firewall rule on upgrade - the 7071 listener refuses requests without the gateway secret header - the wake secret check is shared as requireHeaderSecret, with a test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
The 'wrong secret' case replaced the last two characters of a random client secret with "AA", which is the real secret whenever it already ends in AA. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
62 tests under web/lib were never run by CI. Add an npm test script that runs them and call it in the web job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
…y retry fails In the bearer branch, 'access, err :=' declared a new err, so the error from the retried request was assigned to that inner variable. The check after the switch saw the outer nil err and read resp.StatusCode on a nil response, which panics the image-update loop and with it aven-server. Adds a regression test that drops the connection on the retry; it panicked before this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
'wu, err :=' inside the password branch shadowed err, so a BeginLogin failure was never seen: the handler stored a challenge with null session data and returned options: null. Also drop a dead assignment to acct. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
The wake handler proxies the visitor's request to the app after waking it, and on Windows that request still carried X-Aven-Wake-Secret from Caddy. Strip the header once it has been checked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
Ran axe-core (WCAG 2 A/AA) over the dashboard in mock mode and fixed: - logs: row/cell roles had no table ancestor (critical). The header and the scroller are now wrapped in role=table, and the scroller is the rowgroup. - nodes: CPU/memory bars used role=meter without aria-valuenow when there is no reading (critical). With no reading the bar is now aria-hidden. - canvas: staged-change badges (new/edited/removed) drew the page background colour on the badge, 2.15:1. They now use the foreground colour. - nodes 'this server' badge, the selected dashboard tab, and the log header timezone/line count used colours below 4.5:1; moved one step up the scale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
The link pointed at sdk/docs/api-v1.md, which does not exist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
AVEN_DEBUG, AVEN_TCP_PORT_MIN/MAX, AVEN_PROXY_TRUSTED_PROXIES, AVEN_SELF_ROUTE and AVEN_DOCKER_BIN are read by aven-server but were not listed in the example env file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
RestorePITR copied the restore status for its response after starting the goroutine that updates that status under pitrJobs, so the copy raced with the first step update (seen by go test -race in TestPITRAPI). Copy it before the goroutine starts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
shortVal, remoteDriver.tcpRemove, edge.Rule.label, proxy.originHeaders and pathRule.routes are unused on every GOOS. originHeaders' work is done inline in hostCfg.handlers, and remote TCP proxies are removed through the generic driver Remove. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
An axe pass over the light theme found: - the shortcut hints (kbd) in the header on every page: light gray-500 was 4.3:1 on gray-100. Darken it slightly to #6e6c79 (4.6:1 on gray-100, 5.1:1 on white); it stays lighter than gray-600. - timestamps on the observability page, project cards, notification deliveries and the inbox used the decorative gray-400 (2.6:1 in light). They carry information, so use gray-500 like other secondary text. Mobile (iPhone 13) showed no violations and no horizontal overflow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
…r down No goroutine recovered from panics, so a bug in any of the 18 background loops (the image-update panic fixed earlier in this branch was one) killed the whole process: API, dashboard, proxy sync and deploys. When the panic repeats on every tick, systemd restarts into the same crash. goLoop runs each loop, logs a panic with its stack, and restarts that loop after 30s, doubling to at most 5 minutes. A loop that returns normally ends as before, and shutdown still waits for every loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
A panic while handling one server request stopped aven-agent, and with it every other request, the wake handler and the proxy on that node. Recover per operation, log the stack, and answer the request with an error, the way net/http isolates handlers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
TCP proxy ports are published Docker ports, which bypass ufw, so a database behind one was reachable from the whole internet. The docs listed AVEN_TCP_PORT_ALLOW_CIDRS as designed but not implemented. - Each TCP proxy can carry allow_cidrs (IPv4 addresses/CIDRs, normalized, at most 32). AVEN_TCP_PORT_ALLOW_CIDRS is the default for proxies without their own list, and ["0.0.0.0/0"] opts a proxy out of the default. - Enforcement is in the socat forwarder: with a list, socat runs a small check per connection (SOCAT_PEERADDR against the list) and only then connects to the service. Data reaches the check through the environment, not interpolation, and the agent re-validates the list. Without a list the forwarder command and its signature label are byte-for-byte unchanged, so existing proxies are not recreated on upgrade. - Remote nodes get the list in tcpproxy.ensure. An agent older than the field refuses it; that becomes ErrTCPAllowUnsupported (409 from the API) and is never retried as an open proxy. - POST/PATCH .../tcp-proxy accept allow_cidrs (PATCH may now send it without container_port); GET shows allow_cidrs, effective_allow_cidrs and default_allow_cidrs. Changes emit tcp_proxy.allowlist_updated and an audit entry. Environment duplication and HA edge moves carry the list so a copy never silently opens up. Verified against alpine/socat:1.8.0.3 in Docker: listed /32 and /24 clients connect (270 KB round trip intact), others are closed and logged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
The TCP proxy row now says who may connect: the open-to-everyone warning (with a pointer to Edit) or the ranges the proxy enforces, marked when they come from the server default. The edit panel gains an 'Allowed client IPs' box (one IPv4 address or CIDR per line) validated as you type, and saves only the fields that changed. Older servers that do not report allow_cidrs keep the previous form. Also runs components/panel/networking/validate.test.mjs in npm test; it was not run anywhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
API reference (allow_cidrs, effective/default lists, normalization, IPv4 only, old-agent 409, events), AVEN_TCP_PORT_ALLOW_CIDRS in aven.env.example, and a mention in both READMEs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
staticcheck SA4006 found tests that assigned a result and never looked at it: the volume reread in alerts, the volumeInstanceUpdate and serviceInstanceRedeploy GraphQL results, and the status code of the overlapping IPv6 pool request. Assert them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
Director is deprecated since Go 1.26: headers a Director sets can be removed again by a client's Connection header. The server and agent wake handlers now share proxy.WakeReverseProxy, built on Rewrite. It forwards Caddy's X-Forwarded-For/-Host/-Proto and appends the hop address to X-Forwarded-For exactly as before (SetXForwarded would overwrite X-Forwarded-Proto with the internal hop's scheme). A test sends the same request through the old Director proxy and the new one and compares what the app receives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
Sessions slid by 30 days on every use, so a session that kept being used, including a stolen cookie, never expired. Sessions now also end 90 days after login. Every refresh caps both the stored expiry and the cookie at created_at + max age, and a session that is past the cap, or was created before this change with a later expiry, is rejected and deleted. AVEN_SESSION_MAX_AGE sets the lifetime (2160h, 30d) or turns the cap off; an invalid value fails at startup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
gray-400 is used for faint text in about 130 places and was 3.4:1 on the dark page background. At 53% lightness it is 4.5:1 or more on the page and panel backgrounds and still dimmer than gray-500 (57%). An axe pass over every route in the dark theme now reports no violations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
Raw-string regexp, two append(...) simplifications, unused struct fields removed, and an intentional NewTag() == NewTag() comparison annotated. staticcheck.conf drops ST1005 (error strings here start with proper nouns such as Docker and Aven API) and S1016. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
- gofmt and staticcheck in the go job. The tree is clean for both, and staticcheck is what found the two shadowed-err bugs fixed on this branch. - A separate go-race job (go test -race -timeout 30m). -race found the PITR restore data race that plain go test missed. internal/api takes about ten minutes under -race, past the default test timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
The new gofmt CI step flagged the constant block changed for the session lifetime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
This PR adds a client IP allowlist for TCP proxies, caps login sessions at an absolute lifetime, and adds stricter CI checks. It also carries an audit pass over the backend, the dashboard, the installers and the docs. Each commit stands on its own.
New: TCP proxy client allowlist
TCP proxy ports are published Docker ports. Docker's publish rules bypass ufw, so a database behind a TCP proxy was reachable from the whole internet. The docs listed
AVEN_TCP_PORT_ALLOW_CIDRSas designed but not implemented.allow_cidrs, a list of IPv4 addresses or CIDRs (at most 32). The server normalizes the list.AVEN_TCP_PORT_ALLOW_CIDRSapplies to proxies that have no list of their own. A list containing0.0.0.0/0opts a proxy out of the default.SOCAT_PEERADDRagainst the list before it connects to the service.allow_cidrs. The API then answers409, and the proxy is never reopened without the list.POST/PATCH/GET …/tcp-proxyhandle the list. The TCP proxy row shows who may connect and has an editor that validates as you type. Changes emit an event and an audit entry.alpine/socat:1.8.0.3in Docker):400.New: absolute session lifetime
AVEN_SESSION_MAX_AGEtakes2160h,30doroff. An invalid value fails at startup.Security (Windows host)
0.0.0.0:7070, and the installer opened it to the local subnet. It now listens on127.0.0.1:7070, and the installer removes the old firewall rule on upgrade.0.0.0.0:7071served requests without the gateway secret. It now refuses them.Bugs
imageupdate: a shadowederrleftrespnil and panicked aven-server. A regression test is included.webauthn: a shadowederrmeantBeginLoginfailures were never checked.deploy: data race inRestorePITR, found by-race.TestOAuthClientAuthAndPKCEfailed about 1 run in 4096.Resilience
ReverseProxy.Director, deprecated in Go 1.26. They now share aRewrite-basedproxy.WakeReverseProxy. A test sends the same request through the old and the new proxy and checks that the app receives identical headers.Dashboard accessibility
gray-400) text in the light theme.gray-400faint-text token (3.4:1 → 4.5:1).CI, cleanup and docs
go-racejob (-timeout 30m;internal/apitakes about 10 minutes under-race). Wired up the web unit tests that never ran (74).staticcheck.confdropping ST1005 and S1016. Removed dead code.aven.env.exampleand the READMEs are updated, and the SDK link is fixed.How
internal/node/tcpforward.go,internal/deploy/tcpproxy.go,internal/agent/extra.goandTcpProxyRow.tsx.sessionExpiryandsessionMaxAgeininternal/api/auth.go.cmd/aven-server/loops.go,Handler.handleRecoveringandinternal/proxy/wakeproxy.go.host.docker.internalto the host's loopback. I could not test this on a Windows host.Checklist
go test ./...,go test -race ./...(all 45 packages), staticcheck,GOOS=windowsbuild and vet,cd web && npm run typecheck && npm run i18n:check && npm test && npm run buildAVEN_SESSION_MAX_AGE=offrestores the old behavior).AVEN_LISTEN=0.0.0.0:7070until it is edited.🤖 Generated with Claude Code
https://claude.ai/code/session_01CJyoeEBvvbpiXsP7oR67cb