Repository navigation
Releases: trypando/pando
Release list
v0.3.0
Pando is now installed from a prebuilt, signed image rather than built on the host. An existing
installation, whether it moves to the image or keeps building from a clone, needs its data volume's
owner changed once, because the server now runs as a different user; a clone's build also needs a
Docker account now. Both are in the upgrade notes.
Security
No new advisories. The server image is new in this release: it is built on Docker Hardened Images,
runs the server as a non-root user, and is scanned with Trivy before it is published.
Added
- A prebuilt server image,
trypando/pandoon Docker Hub (#52). Installing no longer means
cloning the repository and building on the host: download the release'sdocker-compose.ymland
rundocker compose up -d. Built on Docker Hardened Images, with no package manager in the image
and the server running as the base's non-root user; forlinux/amd64andlinux/arm64; signed keyless with cosign,
with an SBOM and SLSA provenance attached, and scanned with Trivy before it is pushed. Tagged with
the exact version, the minor line, andlatestfor a stable release. Each release runs the
published image from its own compose file and deploys an app on it before it counts as done.
Verifying the image is indocs/releasing.md. - The release carries a
docker-compose.ymlthat pins the image to that version.
Upgrade notes
- An installation started from a clone of the repository can move to the published image: download
the release'sdocker-compose.ymlinto the same directory, so the compose project and its named
volumes stay the same, and rundocker compose up -d. Keep thePOSTGRES_PASSWORDit was started
with. The server now runs as UID 65532 rather than 10001, so an existingpando-datavolume needs
its owner changed once:docker compose run --rm --user 0 --entrypoint chown pando -R 65532:65532 /var/lib/pando. - Building the image from source needs
docker login dhi.iowith a Docker account first, because its
base images are Docker Hardened Images.
v0.2.0
Detection stops asking for things the repository already told it, reads the build instructions an app
carries rather than inferring them, and plans more kinds of app itself. This release also adds a
security score for every app, optional AI screening of detection proposals, account, group and sharing
management in the console, and a launcher each person can arrange. Several changes alter what Pando
does with apps you already run — a forced delete now destroys the app's volumes, every deploy is
scanned, and administrators can manage every app — so read the upgrade notes before upgrading.
Security
No new advisories. The six open against github.com/docker/docker are unchanged and remain accepted
with their reasoning in .github/govulncheck-allowlist.txt:
all six are Moby daemon vulnerabilities, and go list -deps on the runtime adapter resolves to
api/…, client and pkg/stdcopy with no daemon/… or plugin/… package in the binary. The
module has no fixed release and will not get one.
- An open redirect in the console's sign-in page.
returnToaccepted anextparameter after
checking it began with one/and not two — but/\evil.examplepasses that and the URL parser
still reads it as//evil.example, because where an authority may begin a backslash and a slash
mean the same thing. Following a crafted link, signing in on the real hostname with a real
password, and landing on somebody else's site was a working attack.nextis now resolved against
the current document and accepted only when the origins match, which agrees with what the browser
will do by construction and turns awayjavascript:anddata:in the same breath. - An app created from a published image read the Pando server's own files as its source. Such
an app has no checkout, and its source view was rooted at an empty path, which resolved against the
filesystem root of the Pando process. Detectors read from there, so what they found could be quoted
in the app's proposal. The app now has an empty source, and the source scan and AI screening skip
it. Present in 0.1.x. - Deleting an app left its data on disk. R-204 has a delete either keep a final backup or discard
the app's storage, and both answers removed Pando's record of the volumes while leaving the volumes
themselves in place, where nothing could reach or reclaim them. The app's uploaded source was also
kept. A delete now destroys the volumes once the backup, if asked for, has been taken, and removes
the upload (R-204, R-224). Volumes left by earlier deletes are not removed by the upgrade; see the
upgrade notes. - A custom role could carry a built-in role's name. Role names were unique case-sensitively, and
the built-ins are stored lowercase and shown capitalized, so a custom role called "Administrator"
appeared in every role picker beside the real one, looking identical. Names are now unique ignoring
case and surrounding spaces, built-ins included (R-082); migration 000028 renames existing clashes.
Added
- Pando builds an app the way its repository says to. Where a repository states its build — a
.github/workflowsbuild job, aMakefile,Taskfile.ymlorjustfiletarget, aProcfile's
web process — the plan runs those commands instead of inferring them from the language. R-094's
confidence ladder always ranked "the maintainer's own build commands" above convention-matching;
this implements it. - A client that builds into a directory the binary embeds is built first. Where a bundler config
names an output directory and a//go:embeddirective names the same one, the ordering is stated
by the repository rather than guessed, and the client build runs ahead of the binary's. Read from
Vite, Astro, Vue CLI, Angular, Next.js (static exports), webpack and SvelteKit, or from an
--outDir-style flag in the build script. - Pando plans more builds itself, on official images (R-095). Static sites (Astro, Vite, Angular,
Create React App, Gatsby) are built withnodeand served with nginx; Node servers, Go programs
with onemainpackage, Gradle and Maven projects, plain Java sources and .NET projects each get a
plan on their language's official image, with the version read from the repository where it is
stated. Other languages stay on nixpacks. Detection also reads aContainerfile, a Dockerfile in a
subdirectory and a site indocs/; asks for a DockerfileARGthe build refuses to run without;
runs Rails in production with a requiredSECRET_KEY_BASE; and refuses a library with an
explanation rather than deploying it (R-021). All [P] defaults are recorded in
docs/design/notes-deploy-qa-issue-55.md. - An app created from a published image is proposed as that image, rather than sent through
repository detection over an empty checkout. The trial run finds its port, paths the image declares
withVOLUMEget a volume, and an image that serves only a database or mail protocol is refused with
the reason (R-097, R-200). - A security score for every app (R-310 – R-320). A number from 0 to 100 from scanning the image
an app deploys and the source it was built from, weighted by severity (25 per critical, 10 high,
3 medium, 1 low), shown on the app's overview and in the apps list with the findings behind it,
worst first. Scanning is a new adapter category; the Trivy adapter runs in a container pinned by
digest, with no container runtime socket. A new app is scanned when it is detected, every deploy is
scanned, and Scan now works on an app never built. Host policy can refuse deploys below a
minimum score (min_security_score,PLAN_SECURITY_BELOW_THRESHOLD), and for a running app that
falls below it, notify the owner and optionally stop it after a grace period (insecure_action,
insecure_grace_hours) — never delete it.ignore_unfixable_findingsscores only what can be fixed.
Nothing is enforced until an administrator sets a threshold. - AI screening of detection proposals (R-106, R-330 – R-339). AI is a new adapter category; the
first adapter uses Anthropic's API (default modelclaude-opus-5-5). A screener reads the
repository and the proposal and returns amendments from a closed set — no policy, isolation,
routing, resources, egress, grants or secrets — and Pando refuses any without a reason or evidence
in the repository, any that overwrite what a person set, and a port the trial run observed. Any
failure leaves the proposal as detection made it. The review marks each change "Suggested by AI",
lists what was refused, and each run is audited asdetection.screenwith the files read. No AI
adapter is configured by default; host policy'sdisable_ai_screeningforbids it. Configured, it
sends the repository files it reads to the provider. - Adapter credentials are stored encrypted (R-190).
POST /adapterstakes a write-only
credentialsobject, sealed by the installation's secrets adapter into its own table; the database
refuses acredentialskey in an adapter's plain configuration, andGET /adaptersnames the
credentials set, never their values. - Adapters can be added and changed from the console and the CLI, not only the API
(GET /adapters/kinds,pando adapter list | kinds | add, credentials prompted rather than typed).
Pando can restart itself to load them:POST /api/v1/restart,pando restartand a button on
the Adapters screen finish the requests in flight and re-execute the binary in the same process.
Behindinstall.adapters.manageand audited asinstall.restart.GET /adapterssays which
adapters are waiting for a restart. - Stop, start and restart an app from the console, the CLI (
pando app stop | start | restart)
and MCP. The API had these endpoints and no client called them, so the only way to take an app down
was to delete it. A stopped app stays stopped across a restart of Pando. - Each part of an app has its own status, logs and resource use.
GET /apps/{id}/statuslists
every workload with whether it is running, restarting and how often, its health and exit code;
pando app statusprints it. Logs take a workload (pando logs --workload), and the console's new
Logs tab holds the app's output and every deploy's build log.GET /apps/{id}/usage,
pando app usageand an In use section show each part's CPU, memory and disk against its
limits, and each volume's size — a reading, not a history (R-245). - Accounts, groups and roles are managed in the console. Each account has a page with its
details, groups, role, apps and audit history; an administrator can edit an account, reset its
password, and add it to or remove it from groups. Groups hold an installation role and app roles
that every member holds while in the group (R-078). Custom groups and roles can be deleted. A
generated password (POST /passwords/generate) is offered wherever an administrator sets one, and
by default must be changed at the next sign-in. CLI:pando user create | update | reset-password | apps,pando group list | add-member | remove-member | role | apps,pando grant role | remove. - A built-in Creator role (R-081): one verb,
app.create. A creator manages the apps they made,
as their owner, and nothing else. - Administrators manage every app. Two installation verbs,
install.apps.viewand
install.apps.manage, cover every app, and the Administrator role holds both (R-080, R-081).
Managing an app does not grant using it (R-087).GET /apps/{id}returns the caller's verbs, and the
console hides controls the caller cannot use rather than letting them fail. - Sharing picks people and groups, and can make an app public behind a passcode (R-075a). The
passcode is stored as an argon2id digest; a visitor's unlock lasts a day, r...
v0.1.1
Security
- Base images, GitHub Actions and the two scanners CI installs are pinned by digest or exact version
rather than by a mutable tag. containerd/v2to 2.3.5 (GHSA-7jxh-36q5-gcqv) andmoby/go-archiveto 0.3.0 (GO-2026-6253, a
crafted tar writing outside the extraction directory).- The console's
viteto 8.3.0, with@vitejs/plugin-react6.1.1 alongside it, clearing six
high-severity dev-server advisories. - A malformed stored credential hash no longer crashes the sign-in path or verifies against an
arbitrary password.argon2.IDKeypanics rather than returning an error on a zero time cost or
zero parallelism, and an empty key field compared equal to an empty candidate, so a corrupted or
hand-edited row could take the process down or accept anything. Both are now rejected during
decoding. Found by fuzzing; covered byTestR042_AMalformedStoredHashDeniesRatherThanPanics. - Session cookies are marked
Securebehind a TLS-terminating reverse proxy. Pando sees plain HTTP
in that topology, so it could not tell an encrypted browser connection from an unencrypted one and
sent the cookie without the attribute; one plaintext request to the hostname put a live session on
the wire. SetPANDO_SERVER_EXTERNAL_URLto the address browsers use. Upgrade note: an
installation behind a proxy should set it — unset keeps the previous behavior, which is correct
only when Pando serves TLS itself or runs on localhost. (O-19) - The API server sets
ReadHeaderTimeoutandIdleTimeout. Without them a client dribbling header
bytes held a connection open indefinitely. The proxy's per-app listeners already did this; the API
server was the one that did not. Found bygosec.
Added
- Security policy, coordinated disclosure process and documented security model
(SECURITY.md). - Checksums signed with cosign on every release, and the verification procedure that goes with them
(docs/releasing.md). - CodeQL,
gosec,govulncheck,gitleaksand OpenSSF Scorecard in continuous integration. - Fuzz targets over the parsers that see untrusted input, run in continuous integration.
- Issue and pull request templates, a code of conduct, Dependabot, and a reference index of the
external interfaces (docs/reference.md).
Fixed
- The Docker image ships with the console in it.
docker compose up -dbuilt an image whose binary
had no UI embedded, so it served the API and returned 404 for every console route.
v0.1.0
Changelog
- 871a4f9: A console you can actually sign in to (Ben Meeker ben@bemeek.io)
- 709ad69: A crash-looping container is Running and Restarting at the same time (Ben Meeker ben@bemeek.io)
- 11045fe: A detected repository can now actually be deployed (Ben Meeker ben@bemeek.io)
- 65019a3: A modal that covered one screenful of a scrolling page (Ben Meeker ben@bemeek.io)
- 1c5105b: A rejected password kept rejecting a password you had already replaced (Ben Meeker ben@bemeek.io)
- 8a2d6a8: Accepting a proposal left the console showing a photograph of the app (Ben Meeker ben@bemeek.io)
- f0c7750: Add AGPL-3.0 with a commercial exception, before going public (Ben Meeker ben@bemeek.io)
- d92e35a: An install could create an administrator nobody could ever sign in as (Ben Meeker ben@bemeek.io)
- c835650: Build the thirteen endpoints design 04 documented and nobody wrote (Ben Meeker ben@bemeek.io)
- 99fbf13: CI: use the org CODECOV_TOKEN, and build on main (Ben Meeker ben@bemeek.io)
- 22ac8a6: Close O-15: a host port is an allocation, and Traefik closed the revisit (Ben Meeker ben@bemeek.io)
- 8dbe52e: Close eight open decisions and three unlisted fragilities (Ben Meeker ben@benmeeker.com)
- 71afbca: Close the four design gaps (Ben Meeker ben@benmeeker.com)
- a574f49: Close the last four: rolling backups on demand, and their tests (Ben Meeker ben@bemeek.io)
- f1d8a80: Compose builds as compose, one image per service that needs one (Ben Meeker ben@bemeek.io)
- 4f48899: Correct a stale gap note and the exec usage line (Ben Meeker ben@bemeek.io)
- b9244b1: Design system: make the on-demand fetch instructions match reality (Ben Meeker ben@benmeeker.com)
- 15fc5e4: Endpoints the install verbs were supposed to gate (Ben Meeker ben@bemeek.io)
- 0066564: Environment variables, and the reason every other edit did nothing (Ben Meeker ben@bemeek.io)
- f25a617: Finish phase 8: exec, and a console that needs no internet (Ben Meeker ben@bemeek.io)
- 83af6fb: Fix three acceptance tests I wrote against a label nothing sets (Ben Meeker ben@bemeek.io)
- 276f43c: Give slots, volumes, groups and roles the screens they never had (Ben Meeker ben@bemeek.io)
- 8e9ee7d: Give the last-administrator guard a requirement (R-088) (Ben Meeker ben@bemeek.io)
- e8416b4: Green CI, and "What is Pando?" (Ben Meeker ben@bemeek.io)
- 60b1c3f: Import the Pando design system as a repo-local skill (Ben Meeker ben@benmeeker.com)
- 2aeb8ec: Install-level authorization: an administrator is a grant (O-17) (Ben Meeker ben@bemeek.io)
- 0164aff: Let the acceptance suite take a password instead of scraping the log (Ben Meeker ben@bemeek.io)
- 984e0b9: MYSQL_PWD broke every first MySQL deploy, and I had to run it to find it (Ben Meeker ben@bemeek.io)
- 8941d53: Make "provision one for me" actually provision one (Ben Meeker ben@bemeek.io)
- fb12ac3: Make a fresh clone build, which it did not (Ben Meeker ben@bemeek.io)
- 57ac524: Make the retry backoff configurable, so the suite takes 6 minutes not 43 (Ben Meeker ben@bemeek.io)
- c913d07: Make the teardown test assert teardown, not the runner's environment (Ben Meeker ben@bemeek.io)
- e1b07e2: Merge pull request #2 from bemeek-io/worktree-release-packaging (Ben Meeker ben@bemeek.io)
- 98084b5: Merge pull request #3 from bemeek-io/release-dispatch (Ben Meeker ben@bemeek.io)
- d76abba: Move to Go 1.27.1 (Ben Meeker ben@benmeeker.com)
- cde4a77: No builder implements compose, so every compose app that built was refused (Ben Meeker ben@bemeek.io)
- 48fa188: O-16: bound what logs are promised, not what they accumulate (Ben Meeker ben@bemeek.io)
- cb8ed54: O-17 is a live privilege escalation, not a missing screen (Ben Meeker ben@bemeek.io)
- 7e8826e: Phase 0 verified end to end; correct the audit-immutability mechanism (Ben Meeker ben@benmeeker.com)
- 0fdd8fe: Phase 0: skeleton, with the audit log's immutability actually enforced (Ben Meeker ben@benmeeker.com)
- 6f43c0d: Phase 10: MCP over the same API, with idempotent retries (Ben Meeker ben@bemeek.io)
- 60dc702: Phase 10: Traefik, notifications, and three bugs it exposed (Ben Meeker ben@bemeek.io)
- fb9c4a5: Phase 10: the CLI, and
pando deploy ./(Ben Meeker ben@bemeek.io) - d38b12e: Phase 1: identity and authorization (Ben Meeker ben@benmeeker.com)
- 4f42f9d: Phase 2: spec and state (Ben Meeker ben@benmeeker.com)
- 75d99ab: Phase 3: adapters and the planner (Ben Meeker ben@benmeeker.com)
- 33990d4: Phase 4: build and deploy — Sequence B passes (Ben Meeker ben@benmeeker.com)
- 7fb1775: Phase 5: the proxy — Sequence C passes (Ben Meeker ben@benmeeker.com)
- a097a28: Phase 6 complete: Sequence A passes against real repositories (Ben Meeker ben@bemeek.io)
- 6bcac42: Phase 6: compose import, and what a rejection has to reach the user as (Ben Meeker ben@bemeek.io)
- 00ab07c: Phase 6: detection, and the three defects the corpus found (Ben Meeker ben@bemeek.io)
- 7ee8513: Phase 6: the trial run — watch the app, don't ask the user (Ben Meeker ben@bemeek.io)
- e1064b7: Phase 7: the reconciler, and a give-up threshold that could never be reached (Ben Meeker ben@bemeek.io)
- 2d9fbb0: Phase 8: the console, and a requirement with nothing behind it (Ben Meeker ben@bemeek.io)
- 4587160: Phase 9: Sequence D passes, and a console screen for it (Ben Meeker ben@bemeek.io)
- d7ab053: Phase 9: backup becomes the eighth adapter category (Ben Meeker ben@bemeek.io)
- c3748ca: Phase 9: backup, verify and restore work end to end (Ben Meeker ben@bemeek.io)
- 0584f22: README and LICENSING for the people who actually read them (Ben Meeker ben@bemeek.io)
- 47344ed: README and LICENSING: plainer language (Ben Meeker ben@bemeek.io)
- a58b0d0: README for people using Pando; CONTRIBUTING for people building it (Ben Meeker ben@bemeek.io)
- e638d9a: README: address teams and organizations as much as individuals (Ben Meeker ben@bemeek.io)
- 9403bb5: README: an open source product readme (Ben Meeker ben@bemeek.io)
- dfc3744: README: say which platforms the Homebrew line covers (Ben Meeker ben@bemeek.io)
- 2e23f8a: Re-detecting kept the repository's answer and threw away yours (Ben Meeker ben@bemeek.io)
- 33b2f47: Reconciler: detect an image that changed, and record the log-retention gap (Ben Meeker ben@bemeek.io)
- c2e6a70: Record an app's storage, and keep a final backup when it is deleted (Ben Meeker ben@bemeek.io)
- b5df7a2: Record design gaps; fix compound requirement refs in the index generator (Ben Meeker ben@benmeeker.com)
- 8b2f15f: Record that Pando stopping does not stop apps (Ben Meeker ben@benmeeker.com)
- d5b1990: Record what phase 10 shipped and what running it found (Ben Meeker ben@bemeek.io)
- 2fed6d6: Record what running the tests found that reading them did not (Ben Meeker ben@bemeek.io)
- d6596d3: Record what the debt pass found, including a phase 9 claim that was false (Ben Meeker ben@bemeek.io)
- 1b1adde: Release: cut one from main without tagging by hand (Ben Meeker ben@bemeek.io)
- 0b2fea1: Release: tag-driven builds, a Homebrew cask, and Linux packages (Ben Meeker ben@bemeek.io)
- 40f08b1: Remove a stray compiled binary (Ben Meeker ben@bemeek.io)
- cca94c2: Resolve O-11: the install topology supplies Postgres (Ben Meeker ben@benmeeker.com)
- e41e0d9: Revise README to clarify app deployment purpose (Ben Meeker ben@bemeek.io)
- cf7d749: ...