New Features
Session Access Policy Type
A new SessionAccess policy type controls when selected users or groups
may use a desktop session — for example a school computer lab usable only
during school hours, or a kiosk restricted to business hours.
- Weekly schedules per user or group: allowed time windows by day, with
node-local times and midnight-crossing windows (e.g.22:00–02:00). - Two enforcement layers:
- Layer 1 (logind, always on) — as an allowed window ends the agent warns
the user (default 30/15/5 minutes, the final warning bypassing
do-not-disturb) and then locks the screen (apps keep running) or
logs off the session, per rule. A re-lock watchdog keeps a session
locked outside its allowed hours. - Layer 2 (
pam_time, optional) — blocks fresh logins outside the allowed
hours by managing a marked block in/etc/security/time.confand enabling
pam_timein the account stack. Optionally extends to SSH. The block
encodes the full weekly schedule, so it keeps enforcing even while the
agent is not running.
- Layer 1 (logind, always on) — as an allowed window ends the agent warns
- Distro-aware PAM: on Debian/Ubuntu the agent enables
pam_time
automatically viapam-auth-update; on authselect-managed Fedora/RHEL it
reports a degraded compliance state with a documented one-time admin step
while Layer 1 still enforces. - Lockout guard rails:
rootis never restricted, thetime.confblock is
written atomically with a self-check and is tamper-protected, and unbinding
the policy removes all PAM artifacts fail-open. - Web UI: a dedicated editor with per-rule user/group lists, a day-and-time
window builder, lock/log-off selection, warning thresholds, the PAM and SSH
switches, and a node-requirements info box.
Known limitation, by design: on GNOME and KDE the screen unlock is enforced
by the Layer-1 watchdog, not PAM — the desktop lockers do not honour the PAM
account phase on unlock. Use the Log off end action with PAM for the
strictest restriction. See docs/session-access.md for full behaviour, the
node requirements, and break-glass recovery.
Flatpak Policy Type and Application Catalog
A new Flatpak policy type manages Flatpak remotes and applications on
managed desktops, with a server-side application catalog.
- Remotes: configure any number of Flatpak remotes per policy (URL, GPG
key, subset such as Flathub'sverified, allow-/deny-list filters,
priority, enabled state). Definitions are written to/etc/bor/flatpak/
and tamper-protected; existing remotes such as a distribution-provided
flathubare adopted rather than duplicated and restored on unbind. - Applications: desired state Present, Latest or Absent per app,
with remote, branch, optional and delete data flags. The agent drives
theflatpakCLI idempotently, off the policy stream, with per-operation
timeouts (default 30 minutes), and reports one compliance item per remote
and per application. - Updates: optional periodic
flatpak updateand unused-runtime cleanup. - Not applicable when Flatpak is absent: nodes without
flatpakreport
the policy as Inapplicable, like other unsupported subsystems. - Catalog (Settings → Flatpak repositories): Flathub is indexed out of
the box from its AppStream feed; additional repositories can be added from
a.flatpakrepoURL or manually, refreshed on a schedule or by uploading
anappstream.xml.gzfor air-gapped servers. The policy editor searches
the catalog and copies remote definitions into the policy, so policies stay
self-contained. New permissionsflatpak_repo:*(backfilled onto every
role that holdssettings:manage); new server settings
BOR_FLATPAK_CATALOG_REFRESH,BOR_FLATPAK_CATALOG_MAX_DOWNLOAD_MBand
BOR_FLATPAK_CATALOG_ALLOW_PRIVATE_NETWORKS. - Agent config: optional
flatpak:section (binary,state_dir,
state_file,proxy_url).
See docs/flatpak.md.
Instance-Hosted Agent Packages and Deploy Wizard
Every Bor server now ships and serves the agent packages itself, so managed
nodes never need internet access to install or upgrade the agent.
- The
bor-serverpackages and the container image embed thebor-agent
packages for all formats (deb, rpm, apk, Arch) and architectures (amd64,
arm64, ppc64le) under/usr/share/bor/agent-repo, assembled and
GPG-signed at release build time. - The server serves them at
/agent/*: a signed flat apt repository
(/agent/deb), a signed dnf/zypper repository (/agent/rpm), direct
downloads for Alpine and Arch, the repository signing key
(/agent/repo-key.asc), the internal CA certificate (/agent/ca.crt) and a
static install page at/agent/. Download endpoints are public by design
(package managers cannot authenticate); files are served through a rooted
filesystem with no directory listings. - Deploy agent wizard (download icon in the masthead, or the button on the
Nodes page): pick the platform (Debian/Ubuntu, RHEL/Fedora, SUSE, Alpine,
Arch) and architecture and get a single copy-paste script that trusts the
server CA, sets up the signed repository or downloads the package, and
optionally generates a one-time enrollment token inline so the same script
enrolls and starts the agent. A Direct downloads tab lists every package. - New setting
BOR_AGENT_REPO_DIR(agent_repo.dir); when the directory
holds no manifest the feature is off and/agent/*returns 404. The server
logs a warning at startup when the packaged agent version differs from its
own. - New Prometheus counter
bor_agent_package_downloads_total{format,arch}
(aggregate labels only, no client identifiers).
Policy Export and Import
Policies can be exported and imported as bor.dev/v1 YAML bundles
(schema in proto/export/export.proto), for backups, migration between
servers and keeping policies in version control.
- Format: Kubernetes-style envelope — one
PolicyorPolicyBinding
document per YAML document, withmetadata.name/displayName/
descriptionand aspecholding the policy type and its canonical
content. Bindings reference policies by slug or display name and node
groups by name, never by UUID, so bundles are portable across servers. - Export from the Policies list: a single policy from its kebab menu, or
select several and export them as one bundle with their bindings. The API
(GET /api/v1/policies/export) also supportsformat=json. - Import (
POST /api/v1/policies/import): uploading a bundle first runs a
dry run and shows a preview table of what would be created, updated,
skipped or rejected; nothing is written until confirmed. Conflict modes
for an existing policy name: fail the import (default), skip that policy,
or update the existing draft (released policies are never overwritten).
A bundle with any invalid document is rejected as a whole, and bindings
to node groups that do not exist on the server are reported as errors. - Export requires
policy:view, import requirespolicy:create; both are
audited (policy.export,policy.import).
Policy Creation Wizard
/policies/newis now a four-step wizard: Type (tiles grouped into
Browsers / Desktop / System, each with a tagline and a description panel),
Details (name, description), Configuration (the same editor used on
the edit page) and Review. A contextual help drawer is available on
every step after the first.- The success screen offers the two things people do next — Release now
(with confirmation) and Assign to a node group, which opens the binding
form with the new policy pre-selected — plus open the editor, create another
or back to the list. Changing the type after configuring or leaving with
unsaved work asks for confirmation. - Policy types now have a single registry (labels, taglines, descriptions,
icons) used by the wizard, the Policies list, the dashboard and the
bindings page; Firefox, Chrome, Thunderbird, KDE and Flatpak show their
brand marks (CC0-licensed) next to the type name. - Internally the monolithic policy editor was split into shared per-type
editors (tree editors for the browser catalogues, KConfig, JSON-backed
types), so the wizard and the edit page cannot drift apart.
Readable Sidebar and Masthead in Every Theme
- The sidebar and masthead are now styled through per-theme semantic
properties, so navigation group titles, the current item, the footer and
links stay readable in the light, dark and high-contrast themes (group
titles 7.9:1, links 9.4:1, footer 6.6:1 in the light theme; previously as
low as 1.65:1). Fixes #138. - De-emphasized text (policy descriptions, meta rows) is standardized at
14 px with a theme-aware subtle token instead of hand-rolled small gray
text.
Security
- Flatpak catalog SSRF hardening (CodeQL
go/request-forgery): admin-
supplied repository URLs are validated with a strict https-only pattern
(host name or IPv4 literal, no credentials); loopback, unspecified,
link-local, multicast and broadcast addresses are always refused and
private/CGNAT ranges are refused unless
BOR_FLATPAK_CATALOG_ALLOW_PRIVATE_NETWORKS=true; the transport resolves
names itself and dials only approved addresses (DNS pinning), TLS 1.2
minimum. - Agent package downloads are served through an
os.Root-scoped
filesystem (path traversal and symlink escapes are structurally
impossible), never list directories, setX-Content-Type-Options: nosniff
and cap manifest reads. Only public material (CA certificate, signing
public key) is exposed. - Release signing: the agent repository signing key may now be
passphrase-protected; the passphrase is consumed from the environment
only and never passed on a command line. - Dependabot: all open alerts resolved — gRPC-Go HTTP/2 DATA-frame
memory exhaustion (GHSA-vp52-pcj8-j9qc), fivefast-urihost-confusion /
SSRF advisories, twobrowserslistadvisories,brace-expansion,
js-yaml,nanoidandpostcss-selector-parser.
Bug Fixes
- Dashboard: nodes without a group and the per-group online/offline tiles
are computed from the many-to-manynode_group_idsarray; previously every
node was reported as unassigned and the group tiles stayed empty. A node in
several groups is now counted in each. - Policies: the Polkit editor no longer crashes on rules created through the
API, where protojson omits emptyaction_ids/action_prefixes/
action_conditionsarrays; sparse rules are normalized on load. - Agent: the systemd unit sets
TimeoutStopSec=120so long-running
enforcement steps (Flatpak installs, package transactions) can wind down
cleanly on stop instead of being killed. - Compose:
BOR_VERSIONbuild argument sets the version shown in the sidebar
and/api/v1/version.
Platform & Dependency Updates
- Server + agent:
google.golang.org/grpc1.82.1 → 1.83.2,
golang.org/x/crypto0.52.0 → 0.55.0,golang.org/x/net0.55.0 → 0.58.0,
golang.org/x/text0.39.0 → 0.41.0,golang.org/x/sys0.45.0 → 0.47.0.
Server:github.com/go-ldap/ldap/v33.4.12 → 3.4.14. Agent:
google.golang.org/protobuf1.36.11 → 1.36.12. - Frontend:
react-router8.3.0 → 8.3.1 plus the transitivefast-uri,
browserslist,postcss-selector-parser,js-yaml,brace-expansion
andnanoidbumps. - CI:
actions/checkoutv7; the pipeline gained an Assemble Agent Repo
stage between the agent and server package builds, and the container image
embeds the assembled repository. Dependabot ignores ESLint major updates
untileslint-plugin-jsx-a11ysupports ESLint 10. - Metrics: new
bor_flatpak_catalog_apps_total,
bor_flatpak_catalog_last_success_timestamp,
bor_flatpak_catalog_refresh_totaland
bor_agent_package_downloads_total; seedocs/metrics.md.
Notes
- Agents must be upgraded to v0.9.0 to enforce the new SessionAccess and
Flatpak policy types; older agents ignore policy types they do not
understand. The new deploy wizard is the easiest way to roll the upgrade
out from the server itself. - Database migration
000029_flatpak_catalogruns automatically at server
start: it adds theflatpak_repositories,flatpak_catalog_appsand
flatpak_catalog_iconstables, theflatpak_repo:*permissions and the
seeded Flathub repository. - The protobuf policy schema gained
session_access.protoand
flatpak.proto(policy fields 20 and 21) and a new
proto/export/export.proto; regenerate any external tooling built against
proto/. - New server settings:
BOR_AGENT_REPO_DIR,BOR_FLATPAK_CATALOG_REFRESH
(set tofalseon air-gapped servers; catalog uploads still work),
BOR_FLATPAK_CATALOG_MAX_DOWNLOAD_MBand
BOR_FLATPAK_CATALOG_ALLOW_PRIVATE_NETWORKS. See.env.exampleand
server/server.yaml.example. - Servers built from source or from a container build that did not run the
release pipeline contain no agent repository; the deploy wizard and
/agent/*stay off until a packaged build is installed. - SessionAccess Layer 2 (
pam_time) requiressystemd-logindand PAM on the
node; on authselect-managed Fedora/RHEL a one-time admin step is needed and
the agent reports the policy as degraded until it is done.