Skip to content

v0.4.0

Latest

Choose a tag to compare

@github-actions github-actions released this 11 Oct 16:00
· 21 commits to main since this release
c01c3f9

Security

  • An open websocket outlived a revoked session or a suspended account (#72). A long-lived
    connection through the proxy is re-authorized every two minutes, but the check reused who the
    person was when the connection opened, so signing them out everywhere or suspending them (R-048,
    R-049) left it open. It now authenticates the connection's session or token again each time, and
    closes it with a policy-violation close frame when that no longer works.

  • BuildKit no longer runs with seccomp unconfined (#130). Pando starts BuildKit itself, as
    pando-buildkit on the Docker it runs apps on (R-111), under a seccomp profile compiled into Pando:
    Docker's default plus only the calls rootless BuildKit needs, creating namespaces, mounting inside
    them, and each build step's keyring and hostname. Builds can no longer load BPF programs, read
    performance counters, load kernel modules, set the clock or reboot. AppArmor stays unconfined.
    BuildKit is v0.33.1 (from v0.17.2), pinned by digest, and Postgres is pinned by digest too, which
    Dependabot now keeps current. The compose file no longer has a buildkit service;
    PANDO_BUILDKIT_ADDRESS still points Pando at a BuildKit you run instead.

  • Setting up a new installation needs the setup token from Pando's log (#130). Before, whoever
    reached the console first became the administrator. Pando now prints a one-time setup_token on
    the startup warning that the installation is not set up yet, and the setup form asks for it.
    pando admin setup-token, run where Pando runs, makes a new one if the log line is gone.
    PANDO_ADMIN_PASSWORD installs are unaffected.

  • The bundled Postgres no longer defaults to the password pando (#130, R-401). A one-shot
    secrets service generates one on the first start and keeps it in the new pando-secrets volume;
    Postgres and Pando read it from there as a file, through POSTGRES_PASSWORD_FILE and the new
    PANDO_DATABASE_PASSWORD_FILE. POSTGRES_PASSWORD set before the first start is used instead.

  • On Linux, run Pando on rootless Docker (#130, R-402). Pando holds the Docker socket, so on
    rootful Docker a compromise of Pando is root on the host. docs/rootless.md
    sets up rootless Docker for it, and the compose file mounts whichever socket
    PANDO_DOCKER_SOCKET names. Installation, Capacity shows whether the runtime is rootless.

  • Pando refuses to deploy where Docker would drop an app's CPU or memory limits (#130, R-240).
    Docker accepts limits its host cannot apply and starts the container without them, which rootless
    Docker does unless the cpu cgroup controller is delegated to its user. The refusal says which
    limit and how to fix it. Before, the app ran with the whole host.

  • An app is held to its disk limit (#130, R-403). Every app has one (10 GiB unless set), and
    until now it was only counted against the host's disk. Pando now measures each running app's
    containers and volumes every 10 minutes, tells its owner at 90% and when it goes over, and stops
    it if the next reading is still over. Its data is kept; starting it clears the stop. The console
    shows "Stopped over disk limit", and a visitor is told why.

Added

  • The audit log can be streamed to a SIEM and exported (#129, design 12). GET /audit/stream,
    pando audit tail --follow and the pando_read_audit_stream MCP tool read every event in commit
    order from a cursor, and resume from it after a restart, an upgrade or with several replicas without
    missing one. Audit sinks are a new adapter category, audit_sink: syslog (RFC 5424 over TLS)
    and https, with presets for Splunk HEC, Datadog, Elastic, Sumo Logic and Azure Monitor (Microsoft
    Sentinel), sending Pando's native JSON or OCSF 1.3.0 (docs/audit-formats.md). Each sink's
    backlog, last error and any gap are on the Audit log screen, GET /audit/sinks and
    pando audit sinks; one that keeps failing for a day is turned off and its administrators are told.
    A month is not archived while an enabled sink has not been sent it. GET /audit/export and
    pando audit export download any range of the live log. Sending the log off the installation needs
    the new install.audit.export permission, which the Administrator role holds.

  • Every audit event now records its outcome (success, denied or failed), the client's address and
    user agent, and the acting person's name and email. Behind a proxy, list the proxy in
    PANDO_SERVER_TRUSTED_PROXIES for the client's own address to be recorded rather than the proxy's.

  • More is audited: the end of a terminal session (app.exec.end), every deploy's outcome
    (deploy.finish), what a rollback rolled back from, which fields a policy or spec change touched,
    opening an app's logs (app.logs.read), and reading the audit log itself (audit.read,
    audit.export, audit.archive.download).

  • An install image registry (#72). PANDO_REGISTRY_URL and its credential name a registry that
    builds are pushed to, by digest, for a runtime that pulls rather than imports; runtimes and the
    scanner pull with the registry's credential, and a deleted app's images are deleted from it.
    docker-compose.registry.yml adds a registry to the Compose topology. A single-host install needs
    none and keeps importing builds directly, unless PANDO_REGISTRY_ALWAYS is set. See
    docs/reference.md for the settings. The registry is an adapter category of its own
    (image_registry, kinds oci and ecr, #153), configured like any adapter from the console, the
    API (POST /adapters), pando adapter add and MCP, and used from the moment it is saved; the
    password is stored encrypted and never shown. PANDO_REGISTRY_* declares one, read-only while it is
    set.

  • A DR bundle now includes uploaded source (#72). The registry's images are not in it, so an uploaded
    app's archive is what it is rebuilt from after a restore.

  • disable_anonymous_denial_audit host policy setting (#72), also settable as
    PANDO_POLICY_DISABLE_ANONYMOUS_DENIAL_AUDIT. Off by default: a visitor who is not signed in being
    refused a private app is still written to the audit log as authz.denied. Turned on, those rows
    stop; refusals of signed-in people and tokens are always recorded.

  • Event subscriptions (#50). Subscribe to what happens in Pando — deploys started, succeeded and failed,
    app state and health, security scores, shares and grants, backups, sign-ins, policy, adapter health,
    upgrades — on one app or the whole installation, from the console's new Events screen,
    pando subscriptions, /api/v1/subscriptions or MCP. Each is sent to a webhook, as JSON signed with a
    per-subscription key (Pando-Signature), or through a notification adapter. Deliveries are retried for
    about a day, every attempt is recorded and can be sent again, and an endpoint that fails everything for
    a day is turned off and its owner told. The catalog is docs/events.md. New
    permission install.events.manage, held by Administrator, for install-wide subscriptions; anyone who
    can see an app can subscribe to it. Webhooks cannot reach private, loopback or link-local addresses
    unless host policy's new allow_private_webhooks is on.

  • Notification adapters for email (SMTP, which covers SendGrid, Mailgun and Amazon SES), Slack,
    Microsoft Teams, Discord and ntfy. Email reaches people at the address on their account; the others
    post to a channel and are sent to only by subscriptions.

  • Notification preferences, under Settings: which of Pando's own notifications reach you, on the
    console and by email. "An app was shared with you" is off by default; the launcher tile is still the
    notification.

  • A notifications inbox: a bell beside Settings with the number unread, pando notifications list and
    GET /api/v1/me/notifications. Console notifications were recorded before and shown nowhere.

  • Each app has an Events tab: its recent events, and its subscriptions. Also pando events --app and
    GET /api/v1/apps/{id}/events.

  • A webhook can send the request its receiver expects: method, content type, headers of its own (stored
    encrypted, for an API key the receiver needs) and a body template. Deliveries, notifications and the
    inbox link to the app when external_url is set.

  • A failed deploy tells the app's owner and whoever started it; a failed backup tells the owner and
    whoever manages backups. An account token can own a subscription, within its own grants.

  • In-place upgrades (#53). Pando can upgrade itself from the Updates screen, POST /upgrade or
    pando upgrade: it verifies the new image's signature against Pando's release workflow, takes a full
    backup (or records that you skipped it), and starts a helper that replaces Pando's container, waits
    for the new version to be ready, and puts the previous version and its database back if it is not.
    Off until the deployment turns it on: set the image to a moving tag such as trypando/pando:latest
    and PANDO_POLICY_UPGRADE_IN_PLACE: "true", or use Let Pando upgrade itself on the Policy screen.
    New permission install.upgrade, held by Administrator and denied to agent tokens by default. Host
    policy gains upgrade_in_place, auto_upgrade_patches and maintenance_window for automatic patch
    upgrades. pando upgrade last and GET /upgrade/last say how the last one went.

  • pando self-update replaces a CLI installed from a release archive or with go install, after
    checking the release's signature. Holders of install.upgrade are notified once of each new release.

  • Update checks (#53). Pando asks GitHub at startup and every six hours whether a newer release is
    out, sending only its version. The new Updates screen, GET /updates, pando updates and MCP's
    pando_get_updates show the version running, the latest release, and the changelog of every version
    in between, with security fixes and breaking changes marked, then the command that upgrades this
    installation. Host policy gains disable_update_check, which stops the check and every request it
    sends, and update_channel (stable or prerelease), on the Policy screen under Updates.
    The CLI warns when it and the server differ in major or minor version, and names the command that
    upgrades it the way it was installed. Upgrading from inside Pando is not part of this release.

  • Pando refuses to start against a database a newer version of Pando migrated, and says to run that
    version or restore the backup taken before the upgrade. It used to fail with "Database migration
    failed." and restart in a loop, leaving every app unreachable.

  • Audit log retention (#60). The live audit log keeps three months by default; an older month is
    archived as gzipped JSON lines with a manifest (row count, first and last event, time range, SHA-256),
    read back and checked, and only then removed — and the removal is itself an audit event. Archives are
    kept under /var/lib/pando/audit-archives (server.audit_archive_dir) or exported to a backup
    destination. Host policy gains audit_retention_months (at least 3), audit_archive (keep,
    export or off) and audit_archive_destination, on the Policy screen under Audit log.
    Archived months are listed and downloaded from the Audit log screen, GET /audit/archives,
    pando audit archives and pando audit archives download, and listed by MCP's
    pando_list_audit_archives. The account Pando serves traffic as still cannot change or remove any
    audit event: a month is removed by a separate database role, through a function that refuses one
    under three months old or without a verified archive.

  • Single sign-on and provisioning (#51). Connect an identity provider over OpenID Connect or
    SAML 2.0 on the new Sign-in screen, with presets for Okta, Microsoft Entra ID, Google
    Workspace, Keycloak and Authentik; test it with a real sign-in that shows every claim the provider
    sent and what Pando would do with it; then turn it on. Providers are added and changed without a
    restart. SCIM 2.0 per provider creates, updates and suspends accounts and sets group membership
    at once; deprovisioning suspends and never deletes. A provider's groups become synced groups that
    you give roles and app access, or link to an existing Pando group. Accounts are made at a first
    sign-in only when a provider is set to (off by default), and an administrator links an identity to
    an existing account — email linking is opt-in and only on a verified email. Host policy gains
    Turn off password sign-in and Don't create accounts at first sign-in. The CLI has
    pando identity-provider (pando idp); setup per provider is in docs/identity-providers.md.

  • Waiting for detection (#80). GET /apps/{id}/detection?wait=30 answers as soon as detection
    reaches a new stage or finishes, and while it runs says which stage it is on and for how long.
    pando app detection <app> --wait prints each stage and exits 0 when the app is ready to accept,
    2 when there are questions to answer and 1 when detection failed or was blocked; pando app add --wait does the same. MCP's pando_get_detection takes wait_seconds. Apps in GET /apps,
    GET /apps/{id} and pando app list say where their detection is, so a draft says why.

  • Who used an app is in the audit log: app.use, once per visit (a browser session, or a token's use
    within twelve hours), with the first page visited. Visitors who are not signed in are recorded too,
    with the address Pando saw; host policy's Don't record visits from people who aren't signed in
    (disable_anonymous_use_audit) turns that off. Audit search can now answer "who accessed this app".

  • AI functions beyond detection (#74): drafting roles and groups, drafting host policy, searching the
    audit log with a question, and answering "How can I…" from the API, CLI and MCP reference. Each
    proposes and a person applies. Each AI function is assigned to one AI adapter, optionally on a model
    of its own, chosen when editing the adapter.

  • OpenAI and local-model AI adapters, beside Anthropic. The local adapter talks to any server that
    speaks the OpenAI API, such as Ollama, LM Studio, llama.cpp's server or vLLM, and sends nothing to a
    provider.

  • Adapters, and the AI functions each handles, can be declared in the config file's adapters:
    section, with credentials named by environment variable or file. Declared ones are read-only in the
    console, API, CLI and MCP.

  • A security scan now shows while it runs: in the app's Security section and in the app list's
    Security column, wherever the scan was started (a deploy, detection, the CLI, MCP or Scan
    now
    ). The API reports it as scanning_since on GET /apps/{id}/security and
    security_scanning on each app in GET /apps.

  • Egress rules (#79). Host policy sets where apps may connect out to: anywhere, anywhere except
    a denylist, or only an allowlist, plus a separate switch that blocks private addresses (the local
    network, loopback, the cloud metadata address). An app can add or remove entries, or keep a list of
    its own that narrows the installation's. Changes that loosen the installation's rules are allowed by
    permission, only after a deploy approval, or never, as policy says. The plan and the app's
    Settings tab show the merged rules and where each came from. On Docker, a restricted app runs
    on a network with no route out and reaches the internet only through a per-app egress gateway, as
    HTTP or HTTPS via HTTP_PROXY/HTTPS_PROXY; an app with no restriction runs exactly as before.

  • Deploy approval (#39). Policy can require approval for every app's deploys or for chosen apps,
    and an app can require it of itself. A deploy then waits until enough people holding
    install.deploys.approve (administrators) or app.deploy.approve (no built-in role) approve it,
    with a configurable count and expiry (default one approval, seven days). Rolling back to a revision
    that ran needs no approval; auto-deploy is refused while approval is required. The console has an
    Approvals screen; the API, pando approvals and three MCP tools do the same. Agent tokens
    cannot approve by default.

  • Permissions on every app, one at a time (#81). Each app permission has an installation-wide
    counterpart that grants it on every app, including apps added later: install.apps.deploy,
    install.apps.logs.read, install.apps.secrets.read and so on. A group gets "read every app's logs"
    once instead of a grant on each app. Three new built-in installation roles: App viewer (see every
    app and its logs), App manager (Owner on every app, without approving deploys) and Auditor
    (every app's view and logs, and the audit log). A change allowed through one of these, rather than a
    grant on the app, is written to the audit log as authz.install_wide, naming the grant.

  • An OpenAPI 3.1 document for the API, docs/openapi.json (R-261). Every endpoint with its
    parameters, request body, responses and the error envelope with every error code, and which
    credential each takes. It is generated from the code that serves the API by make reference, and
    the build fails when it is out of date.

Changed

  • Apps with no deployment instructions are planned by Railpack instead of nixpacks (#172).
    nixpacks is in maintenance mode, and Railpack is its maintainers' replacement. The plan Pando
    shows and stores for such an app is now Railpack's JSON build plan (each step's commands, the
    images it runs on, the start command) rather than a Dockerfile, and it is still editable before
    it runs. It is built through Railpack's frontend image inside Pando's own rootless BuildKit, with
    no privileged container and no runtime socket. Railpack's images are pinned by digest. Railpack
    defaults Node to the current LTS and Python to 3.13, reads .node-version, .ruby-version and the
    Gemfile's ruby line itself, starts a Mix project from its release, and builds C sources with
    the compiler in its builder image. Laravel, Jekyll, static sites, Node servers, Go, .NET and JVM
    apps are still planned by Pando's own Dockerfiles, as before. Planning now looks language versions
    up from the package indexes, so an installation that cannot reach the internet cannot plan these
    apps; building already needed it. The image is about 110 MB larger, for the mise binary Railpack
    resolves versions with, and now carries git and bash, which mise needs to list Python
    releases. nixpacks is no longer in the image.

  • API tokens are stored as HMAC-SHA-256 under a key rather than argon2id (#93). A token is 256 random
    bits Pando made, so the slow hash cost 64 MiB and tens of milliseconds on every request a token made
    for no benefit. The key is a new file, /var/lib/pando/token.key (PANDO_SERVER_TOKEN_KEY_PATH),
    created on first start and kept out of the database so that a database dump alone cannot be used to
    check a guessed token. Every replica must read the same file, and a DR bundle includes it. Existing
    tokens keep working and are rewritten on their next use; passwords are still argon2id. A token's
    last-used time is now recorded to the minute rather than written on every request.

  • The proxy reuses its connections to an app instead of opening one per request, finds the app a
    request is for with one indexed lookup instead of reading every app's spec, and the app screen's
    permission check reads grants and policy once instead of once per permission (#72).

  • install.apps.manage is gone; the App manager role holds its thirteen verbs. install.apps.view
    now means seeing every app only, and every role that held it also holds install.apps.logs.read, so
    what it allowed is unchanged.

  • An install has at most one AI adapter per provider, and an AI adapter has no default: each AI
    function is off until assigned. The existing AI adapter keeps plan repair, answering detection's
    questions and plan revision on upgrade; the new functions start off.

  • Pando sets a pando_visit_<app> cookie on responses from apps, to recognize a visit. Like every
    cookie named pando_…, it never reaches the app.

  • Pando moved to the trypando GitHub organization. The repository is
    github.com/trypando/pando, the Go module path is github.com/trypando/pando, and the Homebrew tap
    is trypando/tap/pando. Old github.com/bemeek-io/... URLs redirect.

  • app.egress.override is now app.egress.loosen, and a new app.egress.tighten (Owner and
    Operator) changes an app's egress within the installation's rules. An app's own allowlist now
    narrows the installation's instead of replacing it.

Fixed

  • A build refused by the security scan or the port check could still run (#72, R-146). Builds were
    loaded as pando/<app>:latest and deployments recorded that tag, so a refused build moved it, and a
    container the reconciler recreated afterwards ran the refused image. Each build is now tagged with
    its deployment and recorded by its image ID. Older tags of an app's builds are removed past the
    newest eleven.
  • The identity assertion and X-Pando-Email header for somebody signed in with a session carried no
    email or name (R-054). They now do.
  • An app that failed did not tell its owner (#50). The reconciler wrote the notification and had no
    notifier wired to send it. It now goes to the owner on the console, and by email where that is set up.
  • A security-score warning reached only the console (#50). It now reaches the owner on every channel
    that reaches people, as their notification preferences allow.
  • Redeploying an unchanged source ran the security scan again (#84). A deploy of a commit,
    image or uploaded archive that already has a successful scan by the configured scanner uses that
    scan and says so in the deploy log ("Using the security scan of … (source unchanged)"). That
    includes a new revision of the same source, which used to be refused as never scanned where a
    minimum score was set, and every pando deploy . of an unchanged directory. A scan that failed,
    or one by a different scanner, is not reused. "Scan now" in app settings always scans.
  • A Laravel app failed to build. Laravel 13 needs PHP 8.3, the build installed an older PHP, and
    the build log said only syntax error, unexpected token "{" (#58). A Laravel app is now built on
    the newest PHP its composer.json allows, with the extensions it requires, its Vite assets built,
    and served by Apache from public/; it runs its migrations before it starts. When a dependency
    needs a PHP version or extension the build does not have, the build fails saying which. Deploying
    one now asks for APP_KEY, which the app cannot serve a page without; php artisan key:generate --show prints one. Detect a Laravel app again to pick this up.
  • pando deploy <dir> printed blank lines where detection's questions belonged. It read a field
    the server never sends. The questions are now printed exactly as Pando wrote them.
  • A static site's pages redirected to the wrong port. Opening a folder without its trailing slash
    (/solutions) on an app reached at an address with a port, such as localhost:9001, sent the
    browser to the same path on port 80, where nothing answers (#67). The redirect now keeps the
    address the browser used. Redeploy a static site to pick this up.
  • A Jekyll site was served as its source. A GitHub Pages site deployed and looked healthy, but its
    Markdown pages showed as raw Markdown or failed to load (#67). Pando now builds a Jekyll site with
    Jekyll before serving it. A GitHub Pages site (no Gemfile, or one naming github-pages) is built
    the way GitHub builds it, with the current github-pages gem; any other Jekyll site with its own
    Gemfile, without its development and test gems. Either is served at the root of the app's address
    whatever baseurl the site sets for GitHub. Detect the app again to get the new build plan.
  • Daily app backups could silently not happen (#87). Two apps that each kept a volume with the
    same name (data is common) shared one record of it: the second app was never backed up, the
    first app's backups copied the second app's data, and the second app lost the protection against
    recreating a lost volume empty. Each app now has its own record, and Pando repairs existing records
    from what the runtime reports for each running app within a minute of starting. An app whose configuration was saved without
    a backup count is now backed up with the default of 7 rather than skipped. A whole-installation
    backup names each app's volumes separately; older backups still restore.
  • Every daily backup attempt is recorded, and the last one — taken, skipped or failed, with why and
    what to do — is shown on the app's overview and on Backups. The server log says "took a rolling
    backup" only when one was taken.

Upgrade notes

  • Existing apps keep the nixpacks plan they were deployed with (#172). A plan is part of the
    spec (R-020), so rebuilding an app replays its stored nixpacks Dockerfile unchanged, and it still
    builds. To move an app to Railpack, re-run its detection (R-022) and accept the new proposal.

  • On Ubuntu 23.10 and later, builds need the host to allow unprivileged user namespaces. Rootless
    BuildKit cannot start otherwise, and this was as true of the compose file's buildkit service as
    it is of Pando's own. As root: sysctl -w kernel.apparmor_restrict_unprivileged_userns=0, and the
    same line in a file under /etc/sysctl.d/. The build service's health check now says so.

  • The compose file's buildkit service is gone; Pando starts pando-buildkit instead. After
    docker compose up -d with the new file, remove the old one with
    docker compose up -d --remove-orphans. The old buildkit-cache volume is no longer used and can
    be removed with docker volume rm once the new build cache has filled. With
    docker-compose.registry.yml, take the new copy of it too: it gives Pando the registry's CA for its
    BuildKit, rather than configuring a buildkit service.

  • An installation upgraded before anyone set it up prints its setup token on the first start of the
    new version. Scripts that call POST /api/v1/setup must now send setup_token; take it from
    pando admin setup-token, which prints the token alone on stdout.

  • An installation made with the old compose file keeps its database password only if you say what
    it is.
    Its Postgres was created with pando, or with your POSTGRES_PASSWORD, and the new
    compose file would otherwise generate a different one that the database does not accept. Before
    the first docker compose up -d with the new file, put that password in a .env file beside it
    (POSTGRES_PASSWORD=pando if you never set one); the secrets service writes it to the
    password file, and later starts read it from there. Back up the pando-secrets volume with
    postgres-data from then on.

  • If Docker on your host cannot apply CPU or memory limits, deploys are now refused until it can;
    the refusal says what to change. To check before upgrading, run docker info and look for
    warnings about CPU quotas, memory limits or cgroups. After upgrading, Installation, Capacity shows
    cpu_limits and memory_limits under Show details.

  • Migration 000067 adds columns to audit_events without rewriting the rows already there; events
    written before it have no outcome, source or actor name, and the stream reads them first, in the
    order they were written. The audit log grows faster than before, because reading it, opening an
    app's logs and the end of each terminal session are now recorded.

  • Audit events older than three months leave the live log at the first daily pass after
    upgrading, archived under /var/lib/pando/audit-archives first. To keep everything in the live
    log, start the upgraded server with PANDO_POLICY_AUDIT_ARCHIVE=off (or audit_archive: off in
    host policy) and nothing is archived or removed. Migration 000041 partitions audit_events by
    month by copying it, which takes a while on a large log, and adds a second restricted database
    role, pando_audit_archiver; an external database must let Pando's account create it, as it
    already creates pando_app.

  • Migration 000040 replaces install.apps.manage with its thirteen install.apps.* verbs in every role
    that held it, custom roles included, adds install.apps.logs.read wherever install.apps.view was,
    and adds the App viewer, App manager and Auditor roles. A custom role already named one of those is
    renamed with " (custom)". Scripts that create a role with install.apps.manage need the new verbs,
    or a grant of role_app_manager.

  • Migration 000039 renames app.egress.override to app.egress.loosen in every role that held it,
    custom roles included, and in host policy's disabled-verb lists. Scripts that name the old verb need
    the new one.

  • An install that set egress_allowlist is now held to it. That setting was never enforced
    before; after upgrading, apps reach only what it lists. Clear it, or set egress_mode, before
    upgrading if that is not what you want. Rules take effect at each app's next deploy.

  • Restricted egress on Docker needs the gateway image: Pando uses its own container's image, or the
    runtime adapter's egress_gateway_image. Pando run directly on a host has neither, so a deploy with
    a restriction is refused with a message saying why.

  • Migration 000037 adds the external identity tables and the effective_group_members view, which
    authorization now reads for group membership. No existing behavior changes until a provider is
    added. To use identity providers behind a proxy, set PANDO_SERVER_EXTERNAL_URL: the addresses you
    register with a provider are built from it.

  • pando admin enable-password-sign-in is new: the way back in from the host if password sign-in is
    off and no provider works.

  • An install with more than one AI adapter of the same provider must remove all but one before
    upgrading; the migration stops with a message naming the provider otherwise.

  • go install github.com/bemeek-io/pando/cmd/pando@latest keeps installing the last version
    released before the move. Use go install github.com/trypando/pando/cmd/pando@latest.

  • A Homebrew install from the old tap: brew untap bemeek-io/tap && brew install trypando/tap/pando.

  • cosign verification in docs/releasing.md accepts both the old and the new signing identity, so
    releases from before the move still verify.