Security and how we ship
sealup holds the logins of every site it deploys. This page sums up how it keeps them and your files safe, how a release reaches you, and how to check that what you run came from sealup’s repository. The full policy, with how to report a flaw, is SECURITY.md.
Your logins
Section titled “Your logins”- Secrets come only from environment variables:
${NAME}insealup.yml,sealup.envon your computer, secrets in CI. A secret written in the config is refused (secret-in-config). - sealup never prints a secret, in any form: plain, base64, URL-encoded, JSON-escaped, hex, or each line of a key. In GitHub Actions each one is masked before the first line of output.
- No secret goes on a command line: there is no
--passwordflag, and sealup starts no local process or shell. - The server is checked before your login is sent: the host key over SFTP and SSH, the TLS certificate over FTPS. FTPS never falls back to plain FTP.
A public repository
Section titled “A public repository”Anyone can read the sealup.yml of a public repository. The password is only a variable’s name
there, but the server line gives away a login name and the server to try passwords on, and a
host key can be looked up on search engines that index them, which finds the server again.
So sealup init asks “Is this repository public?” (no by default). On yes, sealup.yml reads
the server and the host key from variables, their values go to sealup.env, the workflow passes
each one from a secret, and init’s last screen names every secret to add:
server: ${SEALUP_SERVER}password: ${SEALUP_PASSWORD}hostKey: ${SEALUP_HOST_KEY}upload: dist| Variable | What it holds |
|---|---|
${SEALUP_SERVER} |
The whole server URL, user and folder included |
${SEALUP_HOST_KEY} |
The host key line, over SFTP and SSH |
${SEALUP_TLS_FINGERPRINT} |
A pinned FTPS certificate’s SHA-256, as tlsFingerprint. init never pins a certificate, so this one is for a file you write yourself |
Without a terminal, init asks nothing and keeps the server in the file. To switch a project
by hand, see sealup.yml.
- In GitHub Actions, they are masked like a password: the server, its user and its host,
the host key line and its fingerprint. The target line reads like
sftp://***@***:22/home/site, and the job summary names the variable (${SEALUP_SERVER}) instead of the server. On your computer, the target line stays whole. - GitHub masks a value wherever it appears in the log. A user named like a common word,
such as
deployorweb, hides that word in every other line too, however short it is. Give the site a distinct deploy user, its own: the least-privilege advice anyway. keyscannever writes over a variable. When the server’s key changes, it prints the new line and names the variable to update, insealup.envand in CI (key-in-variable). If you did not save the host key duringinit, write the reference yourself before you run it (how).
Hiding the server takes away a stranger’s head start; a strong password or a
private key is still what stops the guessing. It does not stop a person who
can push: in the same commit, they can replace ${SEALUP_SERVER} and ${SEALUP_HOST_KEY} with
their own server and its key, and the password goes there. What stops that is on GitHub’s side:
keep the password in a GitHub environment that only main may use (its deployment branches),
and protect main with required review, so no change reaches it unseen.
Your files
Section titled “Your files”- sealup writes and deletes only inside the folder you gave it. Every path, from your config,
the server or sealup’s own records, goes through one strict rule: no
.., no absolute path, no control character. - It never writes through a symlink on the server, and deletes never follow one.
- It deletes only files it uploaded, unless you turn on the full mirror, which still never deletes excluded paths.
- Each file arrives under a temporary name and is renamed into place, so visitors never see half a file. One deploy at a time per target.
- A file written from a secret never touches your disk, gets mode
0600before its first byte, and is refused in a folder that looks served.
How a release is made
Section titled “How a release is made”- Changes land on
mainafter review and green checks. A release pull request collects them, with the next version and its changelog. - Merging it exports the code to the public repository as one commit, tagged
vX.Y.Z. The export copies an allow-list of paths and stops on any private name. - On that tag, GitHub Actions runs the full checks on Linux and Windows, builds sealup, and attests the npm package and the action’s bundle (build provenance).
- The package is staged on npm through trusted publishing: no npm token exists anywhere. It reaches npm, with provenance, once the maintainer approves it with 2FA.
- The GitHub release gets the npm tarball,
dist/action.mjsand aSHA256SUMSfile, and is published immutable: its assets can never change. - This site is built from the same tag, so it matches the release.
The npm package is one bundled file with no runtime dependencies and no install scripts. In
the repository, dependencies are pinned to exact versions, installed with --ignore-scripts,
and updated by Dependabot only after 7 days. Every workflow pins its actions to full commit
SHAs and is checked by actionlint, zizmor and a weekly OpenSSF Scorecard.
Verify a release
Section titled “Verify a release”For sealup 0.2.1:
npm audit signatures # in a project that installed sealupgh release verify v0.2.1 --repo sealup/sealupgh release verify-asset v0.2.1 sealup-0.2.1.tgz --repo sealup/sealupgh attestation verify sealup-0.2.1.tgz --repo sealup/sealupgh attestation verify action.mjs --repo sealup/sealupsha256sum -c SHA256SUMS # beside the downloaded assetsnpm audit signatures checks the registry’s signatures and provenance of every package your
project installed, sealup included. The gh commands check the release, its tarball and the
build attestations against sealup’s repository. sha256sum checks the downloaded assets.
Known limits
Section titled “Known limits”- Plain FTP (
ftp://) sends your password in clear, and gives no way to check the server. sealup warns on every such run. Useftps://orsftp://where the host offers them. - vsftpd gives coarse file dates. Its listing’s dates are to the minute (to the day for
older files):
--check-servercan miss a same-size edit within that window, andsealup pullcan take such a file for the one it already has. - Anyone who can push a workflow can change what it does, including sending secrets elsewhere. Protect production with GitHub environments and branch rules.
- Isolation between accounts on a shared host is the host’s job. sealup cannot protect files from another user or process on the same server.
- A pinned host key or certificate is only as good as your first check. Compare the
fingerprint
sealup keyscanshows with the one your host gives.
What you can do
Section titled “What you can do”- Pin the action to a release’s full version, as
sealup initwrites it: releases are immutable, so the tag cannot move (see the GitHub Action). - Give each site its own deploy user, limited to the smallest folder it needs.
- Prefer
sftp://orssh://with a private key over FTP. - Keep production secrets in a GitHub environment that only
mainmay use, and require review onmain: that is what keeps a changedsealup.ymlfrom sending your password to another server, whether the server is written in it or read from a variable. - In a public repository, keep the server and the host key out of
sealup.yml(A public repository). - Report a flaw privately through GitHub’s security advisories, never in a public issue.