Alpine 3.14 reached end of life in May 2023 and has received no security
fixes for roughly three years. nginx is installed from the Alpine repos
rather than pinned, so the base bump also moves nginx 1.20.2 -> 1.30.4,
certbot 1.16.0 -> 5.6.0 and supervisor 4.2.2 -> 4.3.0.
Alpine 3.24 carries security support until June 2028.
Verified by building and running this Dockerfile on 3.14, 3.23 and 3.24 and
comparing: the served index.html is byte-identical across all three, the CSP
response header hashes identically, `nginx -t` passes on the existing config,
and supervisord reaches the same state. /etc/crontabs/root and
/etc/nginx/http.d/ still exist on 3.24, so the `rm /etc/crontabs/root` step and
the config COPY paths are unaffected. Every certbot flag used by init_cert.sh
is still accepted by certbot 5.x.
Certificate issuance itself could not be exercised without a real domain, so
the certbot 1.x -> 5.x jump should get one staging issuance before rollout.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: upgrade to Next.js 16.3
Turbopack is now the default for both `next dev` and `next build`, so the
separate `turbo` script is redundant and has been dropped.
`next build` rewrites tsconfig `moduleResolution` to "bundler", which Next 16
expects.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: migrate ESLint to flat config
`next lint` was removed in Next 16, so `npm run lint` had been failing with
"Invalid project directory provided, no such directory: ./lint" since that
upgrade. eslint-config-next 16 also ships flat config only, which the old
.eslintrc.json could not consume.
Ignores .claude/ so stray worktree copies of the repo are not linted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: replace ip-cidr with ip-address to clear CVE advisories
ip-cidr pulled a vulnerable ip-address (GHSA-mwp4-54f8-5fhr, high: leading-zero
octets decoded as decimal while resolvers read them as octal, enabling SSRF;
GHSA-v2v4-37r5-5v8g, moderate: XSS in Address6 HTML methods). npm reported "no
fix available" because even the latest ip-cidr pins ip-address ^9.0.5, still
inside the vulnerable range.
ip-address 10.x was already a direct dependency and covers every use: its
isValid rejects leading-zero octets and validates mask ranges natively, and
isInSubnet replaces the one `new cidr().contains()` call site.
The new validators were differential-tested against the real ip-cidr over a
67-input corpus (134 checks) before removal, with no divergences; containment
was checked across 14 v4/v6/boundary/cross-family cases. `npm audit` now
reports 0 vulnerabilities.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: replace next-themes with an in-repo ThemeProvider
next-themes renders an inline <script> to set the theme before paint. It was
loaded through dynamic(ssr: false), so that script rendered on the client, where
React 19 never executes script tags and warns:
Encountered a script tag while rendering React component.
Upgrading would not have helped; 0.4.6 still renders the script
unconditionally. The script was also useless here: injected after hydration, it
could not prevent a flash, and postbuild.js hoists inline scripts out of the
export anyway.
The replacement keeps the same `netbird-theme` storage key and three-mode
support, and sets the theme class on <html> in the layout instead, so the
static export ships the correct theme in its first byte with no script at all.
Dropping the dynamic(ssr: false) wrapper removed an implicit prerender bailout
for the whole provider tree, which exposed isNetBirdCloud() reading
window.location during the build-time render; it is now guarded. The exported
HTML is byte-identical either way, since the page already bails to client
rendering.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: key the router children slot in the dashboard layout
React logged "Each child in a list should have a unique key prop. Check the
render method of DashboardPageContent." The App Router's segment element for
`children` arrives with a null key, which React flags once it is reconciled
alongside the Navigation sibling. Wrapping it in a keyed Fragment resolves it.
Verified in the browser both ways: with the Fragment the console is clean,
without it the warning returns.
Also applies the pending simple-import-sort fix for this file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: match IPv4-mapped IPv6 hosts against IPv4 CIDRs
Regression from replacing ip-cidr. Its contains() normalised an IPv4-mapped
IPv6 address to the embedded v4 before the subnet check, so
contains("::ffff:10.0.0.5") on 10.0.0.0/24 returned true; isHostInCIDR returned
false. Verified against ip-cidr@3.1.0 directly.
This is reachable: isValidIP accepts "::ffff:10.0.0.5", so a mapped host entered
in ReverseProxyAddressInput against an IPv4 resource CIDR was wrongly rejected.
An IPv6 network still rejects a mapped host, so families do not leak. Checked
across 20 cases (6 mapped-host, 14 pre-existing), with the mapped expectations
taken from measured ip-cidr output.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: derive skeleton colors from the resolved theme
SkeletonTheme had the dark palette hardcoded, so a light theme would render
dark skeletons. The light values are the ones already defined on the
feature-light-theme branch rather than new invented colors.
resolvedTheme is tracked in state and updated from the existing apply(), so the
palette also follows the OS preference under "system".
Latent today, since DarkModeToggle disables both Light and System.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: center icons in collapsed navigation rail
Nav items have 24px of left padding (px-3 on the li plus px-3 on the
button), so with 16px icons the icon center lands at 32px. The 70px rail
centered at 35px, leaving icons 3px off. Narrow the rail to 64px so the
two line up, and match the content offset in PageContainer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: use one plus icon across network tab Add buttons
The Resources and Routing Peers Add buttons used tabler's IconCirclePlus
while the Services tab used lucide's PlusCircle. Both were size 16, but
the two icon sets draw at different inner scales, so the glyphs read as
different sizes. Standardize on lucide.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adopts the new Agent Network settings contract:
- The management API replaced the implicit settings bootstrap — a
`bootstrap_cluster` hint on the first provider create — with an
explicit POST /agent-network/settings carrying the proxy cluster
address. The settings response now carries endpoint +
proxy_address + dedicated instead of cluster + subdomain.
- The provider wizard keeps its UX (auto-picking a validated cluster,
gating the first create on having one) but now bootstraps the
settings row with the explicit POST right before the first provider
create. A bootstrap failure keeps the wizard open and surfaces the
error — previously a failed bootstrap was swallowed server-side and
the account silently ended up with a provider but no endpoint. A 409
(concurrent bootstrap won) is treated as success, since the row
exists either way.
- Types updated accordingly; bootstrapAgentNetworkSettings is exposed
on the providers context. The onboarding provider step reuses the
same wizard, so it is covered by the same change.
Without this, the dashboard against a new management silently stops
bootstrapping accounts: provider create succeeds (the old hint is an
ignored unknown field), but no endpoint is ever assigned.
Self-hosted management versions can carry a numeric build suffix
(e.g. 0.76.3-31256681241) that overflows the sidebar box. Show the
semver only and move the full string into the hover tooltip alongside
the latest version. Pre-release labels like -rc.1 are preserved.
The onboarding survey opened a HubSpot booking modal for accounts with
50+ planned users. Removes the modal, its onboarding-call experiment
variants, the trigger in Onboarding.tsx, and the now-unused avatar.
Keeps src/cloud/cloud-hooks/useExperiment.ts, which this was the only
consumer of, for a future experiment.
* Handle agent-network settings defaults response
The management API now answers the settings GET with a defaults object
before the account is bootstrapped (empty cluster/subdomain/endpoint)
instead of a JSON null body. Normalize that shape to the existing null
settings signal so every consumer keeps a single not-bootstrapped check,
while still tolerating the null body and 404 from older backends.
* Pin the settings wire-shape normalization in a Playwright spec
The providers page must render the connect-first empty state for every
unbootstrapped settings shape the management API has answered with over
time (defaults object with an empty endpoint, JSON null body, 404) and
the endpoint badge once bootstrapped. The settings route is mocked per
test so the spec is independent of the backend build and of account
state left behind by other suites.
* Address review: no stale settings on 404, close context on setup failure
useFetchApi keeps the previous SWR data alongside the error, so a later
404 could expose stale settings through the memo; it now yields null
when the error is a 404 while still riding out transient failures on
the kept data. The wire-shapes spec closes its browser context when a
setup step throws before the close callback is handed to the test.
Zypper ignores the gpgkey= entry of a .repo file, so the yum snippet would
leave the repository key uninstalled on openSUSE. The new distro entry adds
the repository with GPG checking enabled and imports the key on refresh.
* Cover RPM-based distros and desktop app dependencies in the Linux tab
The manual-install section of the add-peer modal only covered Ubuntu via
APT. Replace the single Ubuntu accordion with a distro selector covering
Debian/Ubuntu (APT), Fedora (DNF), RHEL/AlmaLinux/Rocky (DNF), and Amazon
Linux (YUM).
The desktop app links GTK 4.10+ and WebKitGTK 6.0 but the released
netbird-ui packages do not declare them, so each desktop-app install line
names the packages explicitly and a callout states the minimum release
(Ubuntu 24.04, Debian 13, Fedora 43, RHEL 10 with EPEL). Distros without
the required stack get CLI-only instructions.
* Add e2e coverage for the Linux distro selector
* Add Kimi (Moonshot AI) provider support to Agent Network UI
Pairs with the management catalog's new kimi_api entry:
- kimi_api in the AIProviderId union and the access-log provider-id
normalization list.
- Provider modal: api.moonshot.ai host placeholder, upstream-URL help
explaining the OpenAI-shape (/v1) vs Anthropic-shape (/anthropic)
path choice, sk-... key placeholder.
- Configure Your Agent: Kimi backend option in the Claude Code tab
(JSON + shell modes), a Kimi variant in the Codex tab using
wire_api="chat", and a new Kimi CLI tab with the ~/.kimi/config.toml
provider setup. All Kimi surfaces render only when a kimi_api
provider is connected, and pre-select themselves when Kimi is the
only provider of its shape.
- Onboarding defaults to the Claude Code tab for Kimi providers.
* Add Kimi provider e2e spec
Walks the Kimi lifecycle against the real backend: select kimi_api from
the catalog (prefilled host, catalog model pricing), connect it, then
assert the Kimi-gated Configure Your Agent surfaces (Kimi CLI tab,
Claude Code kimi-k3 config, Codex wire_api=chat variant) and clean up
via new agent-network API helpers. Skips when the management build
under test has no kimi_api catalog entry, so it can merge ahead of the
backend and activate once the management-cloud image carries it.
* Remove Codex option for Kimi and align Claude Code config with Moonshot guide
Codex has no clear support against Moonshot's upstream, so the Kimi
variant is dropped from the Codex tab. The Claude Code Kimi snippet now
pins all default model tiers and the subagent model to kimi-k3 and
disables tool search, per Moonshot's Claude Code guide.
* Carry the /anthropic shape prefix in the agent base URL for Kimi
The Kimi provider keeps its default https://api.moonshot.ai upstream;
Claude Code and Kimi CLI select the Anthropic shape by appending
/anthropic to the endpoint base URL, which rides through to Moonshot.
One provider now serves both API shapes.
* Kimi CLI uses the bare endpoint base URL
Only Claude Code carries the /anthropic prefix; Kimi CLI's anthropic
provider type points at the bare endpoint.
* Kimi tooltip
* Make the Claude Code provider dropdown width flexible and tidy Kimi notes
* Make the Claude Code provider dropdown width flexible and tidy Kimi notes
* Restored the note on the Kimi CLI tab
---------
Co-authored-by: braginini <bangvalo@gmail.com>
When creating a network resource and adding a policy with a single peer
(sourceResource) as the source, the payload included both an empty
`sources: []` array and `sourceResource`, causing the backend to reject
it with "specify either sources or source resources not both".
`createPolicyForResource` always computed `sources` as an array while the
`...rule` spread carried `sourceResource` through. This mirrors the
existing destinations handling: gate `sources` on `rule.sourceResource`
so it becomes undefined (and is dropped during serialization) when a
source resource is set.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Make Agent Network menu available via dashboard_features.agent_network
Wire the new dashboard_features account setting into the dashboard at the API
level: add it to the Account settings interface (so settings updates round-trip
it) and have useAgentNetworkMode treat dashboard_features.agent_network === true
as enabling the Agent Network menu alongside the full dashboard. Unlike
agent_network_only it does not focus the dashboard, and it does not trigger the
onboarding flow. No settings-menu UI yet; the field is set via the API.
* Add e2e coverage for dashboard_features.agent_network menu availability
* Set dashboard_features for existing netbird.ai accounts
The signup-source flow only handled new accounts (agent_network_only) and
discarded the source for existing accounts, so nothing set dashboard_features.
Branch on account existence: new accounts get the focused view, existing
accounts get the Agent Network menu made available via
dashboard_features.agent_network. Adds e2e coverage for the existing-account
write.
* Also enable Agent Network menu for new netbird.ai accounts
New accounts got agent_network_only (focused) but not
dashboard_features.agent_network, so turning the focused view off later would
remove the Agent Network menu entirely. Set both on new accounts so disabling
focus degrades to menu-available rather than losing access.
* Match badged nav labels by substring in dashboard_features e2e
* Treat agent network deployment config as a floor over account setting
* Add agent network env x signup-source e2e matrix with a config override
Accounts that sign up through netbird.ai are focused on the Agent Network
surface and taken through the Agent Network onboarding. Reversible by the user
and a no-op for every other account.
- Capture ?signup_source=netbird.ai pre-auth (survives the OIDC redirect); once
authenticated, mark new accounts with settings.agent_network_only, awaiting
/accounts revalidation before clearing the source so the view never flips.
- useAgentNetworkMode() resolves the mode with nullish semantics: an explicit
account setting (true or false) wins over deployment config; when absent, a
pending netbird.ai signup or NETBIRD_AGENT_NETWORK_ONLY/_ENABLED decides.
Purely additive — unchanged behavior when the setting is absent.
- Navigation, the agent-network route guard, control-center, and fetch gates
read the resolver instead of the config flags.
- Onboarding: the form is chosen from signup_source synchronously, so netbird.ai
arrivals see the Agent Network signup form (now shown on cloud) and never the
regular form, even under a slow backend.
- Client Settings gains an "Agent Network focused view" opt-out toggle.
- E2E coverage for focused-view menu/routes and onboarding form selection.
* feat: default billing to EUR only, preserve USD for active subscribers
Removes the USD/EUR currency selector from Plans & Billing. New
subscriptions, MSP tenants, and distributor customers now default to
EUR; any existing active USD subscription keeps its real currency so
its price stays accurate.
Also fixes a latent bug where MSP/distributor per-tenant pricing was
computed off the admin's own account currency instead of each
tenant's, pins AWS Marketplace checkout to USD since it's a separate
billing rail, and removes an unused dead TotalCost stub from the MSP
Tenants table.
* move currency helper into provider
* force usd for aws
* fix flaky setup key test
---------
Co-authored-by: Eduard Gert <kontakt@eduardgert.de>
* Show management API request id in error notifications
Newer management servers return an X-Request-Id header on every response,
including errors. Surface it in the error toast so it can be quoted in
support tickets.
- api.tsx: read X-Request-Id from the response and attach it to the
rejected ErrorResponse (both the JSON-body and status-text paths).
- Notification.tsx: render a copyable "Request ID" line whenever an error
carries one, covering the promise-based mutation notifications.
- ErrorBoundary.tsx: thread the request id through for GET/fetch errors.
The line is only shown when the header is present, so older servers that
do not send it are unaffected. Adds an e2e spec covering both cases.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016B2yLBeDK3kvX2FvhqcWk8
* docs: add request-id error toast screenshots for PR
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016B2yLBeDK3kvX2FvhqcWk8
* test: expose X-Request-Id via CORS in e2e mock
The dashboard reads the request id from a cross-origin response, so the
browser only surfaces the header to JS when the response lists it in
Access-Control-Expose-Headers. Mirror a CORS-enabled management server in
the mock so the test exercises the dashboard logic rather than the header
being filtered out cross-origin.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016B2yLBeDK3kvX2FvhqcWk8
* test: assert error toast via data-testid instead of text
Add a notification-title testid to the toast and target it with
getByTestId in the request-id spec, following the repo convention of
selecting by data-testid rather than brittle visible text.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add Metrics settings tab with metrics push toggle
Add a dedicated Metrics tab to Settings with a toggle for enabling
anonymous performance metrics collection. Moves metrics out of the
Clients tab into its own tab with description and docs link.
* Update docs link
* fix(docker): reload nginx after patching CSP header
nginx is started by supervisord (priority 100) before init_react_envs.sh
(priority 201) and is never reloaded afterwards, so it keeps serving the
static default.conf CSP that has no connect-src. Over plain HTTP this blocks
OIDC discovery and the management API, breaking browser SSH/RDP auth.
Reload nginx at the end of the init script so the dynamically built CSP
header takes effect.
* Update docker/init_react_envs.sh
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* fix(docker): log nginx -t output on failed CSP reload
When the reload never succeeds, run nginx -t before exiting so the logs show
whether the patched config is invalid or nginx never came up.
* conditionally emit `upgrade-insecure-requests` in CSP
---------
Co-authored-by: Viktor Liu <17948409+lixmal@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: mlsmaycon <mlsmaycon@gmail.com>
Gate premium endpoints so the open-source dashboard stops calling
endpoints the open-source management server does not serve:
- MSP/Distributor: only fetch /integrations/msp, /integrations/msp/switcher
and /integrations/msp/reseller on NetBird Cloud
- EDR bypass: only fetch /peers/edr/bypassed when licensed
- Event Streaming: gate /integrations/event-streaming GETs on the license
in addition to the existing permission check
- useIsLicensed: cache the licensed-only probe result (per API origin, 1h
TTL) so it no longer re-fires on every page load
- Team > Users: show the Identity Provider Sync tile in open-source mode so
IdP sync remains discoverable
Brings the unified dashboard into the open-source repo. Premium features
ship in the open code, gated at runtime via NETBIRD_CLOUD and
NETBIRD_LICENSED, with upgrade prompts for unlicensed self-hosted
deployments. Adds the cloud-only feature areas (billing, integrations,
MSP, traffic events, notifications) and the Playwright e2e suite.
Loaded 20 of 673 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.