Skip to content

Installer security

The Parako.ID installer is a 3000-line bash script that runs with root privileges (for system-wide installs). Identity providers are crown-jewel systems — compromise means full identity compromise of every relying party. This page documents the installer’s threat model and the trust chain that lets you verify, byte for byte, that the script you piped to bash is the one the maintainer published.

ArtifactVerified againstMechanism
install.sh itselfMaintainer-published SHA256sha256sum install.sh compared to the value in the GitHub Release notes
Release tarballSHA256SUMSsha256sum
Release tarballSigstore transparency logcosign verify-blob with identity bound to release.yml
Cosign binary (auto-installed)Inline SHA256 constant in install.shFirst-time chain-of-trust bootstrap

The installer assumes:

  1. TLS is intact between the operator and get.parako.id and between the operator and GitHub.
  2. The Sigstore transparency log is intact at https://rekor.sigstore.dev.
  3. GitHub’s OIDC identity service is intact at https://token.actions.githubusercontent.com.

Under these assumptions, the installer protects against:

  • A compromised release tarball published to GitHub Releases (cosign + Sigstore catches a tarball that wasn’t built by the release.yml workflow on main).
  • An MITM attack on the tarball download path (TLS 1.2+ enforced; HTTP downloads refused).
  • An MITM attack on the mirror download path (TLS 1.2+ enforced; non-HTTPS mirror URLs are refused).
  • A maintainer who tries to push a release outside CI (cosign-binding to release.yml means manually-built tarballs cannot pass verification).
  • A compromised release pipeline that bypasses cosign (the operator’s install.sh refuses the unsigned tarball; the escape hatch --insecure-no-signature requires explicit reason text logged to the structured install log).

The installer does not protect against:

  • A compromised GitHub account that has CI write permissions and can modify release.yml itself (the cosign identity is bound to the workflow path — if that path is rewritten, future signatures are bound to the rewritten path). Mitigation: pin the workflow file with branch protection + required reviews.
  • A compromise of Sigstore or GitHub OIDC (out of scope; same trust anchor as the rest of the OSS ecosystem).
  • An attacker who is already root on the target machine before the installer runs (no installer can defend against this).

Each release of Parako.ID publishes the SHA256 of install.sh in the release notes. To verify what you’re about to pipe to bash:

Terminal window
# Download without executing
curl --proto '=https' --tlsv1.2 -fsSL https://get.parako.id -o /tmp/install.sh
# Get the expected SHA256 from the release notes
# (look for: "Installer SHA256: <value>" in the v0.2.0 release on GitHub)
EXPECTED="<value from release notes>"
# Compare
ACTUAL=$(sha256sum /tmp/install.sh | awk '{print $1}')
[ "$ACTUAL" = "$EXPECTED" ] && echo "verified" || echo "MISMATCH — DO NOT RUN"
# If verified, run from disk
bash /tmp/install.sh --help

This single check pins the entire installer to a known-good byte sequence. If it matches, the rest of the trust chain (cosign for the release tarball) is bootstrapped from that.

The installer must verify a cosign-signed release tarball, but cosign itself may not be installed on a fresh box. The chain-of-trust pattern is:

  1. Inline constants in install.sh:
    Terminal window
    COSIGN_VERSION=2.4.1
    COSIGN_SHA256_LINUX_AMD64=<sha256 of the official cosign-linux-amd64 binary>
    COSIGN_SHA256_LINUX_ARM64=<sha256 of the official cosign-linux-arm64 binary>
  2. Bootstrap fetch: if cosign isn’t in PATH, the installer downloads https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-${arch} and computes its SHA256.
  3. Verify: the SHA256 must match the inlined constant exactly. Mismatch → hard fail.
  4. Install: the verified cosign binary is installed to /usr/local/bin/cosign (or ~/.local/bin/cosign for user installs).
  5. Use: the verified cosign is then used to verify the Parako.ID release tarball against Sigstore.

The trust root of this chain is the inlined SHA256 constant. When the maintainer bumps COSIGN_VERSION, the constants must be updated in lockstep — see Maintainer procedure below.

For each Parako.ID release v0.2.0+, the CI workflow release.yml signs three artifacts via cosign keyless:

  • parako-id-v${V}.tar.gzparako-id-v${V}.tar.gz.sig + .pem
  • parako-id-v${V}.zipparako-id-v${V}.zip.sig + .pem
  • SHA256SUMSSHA256SUMS.sig + .pem

The cosign certificate identity is bound to the workflow path:

--certificate-identity-regexp 'https://github\.com/Dahkenangnon/Parako\.ID/\.github/workflows/release\.yml@.*'
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com'

This regex means: only signatures produced by the release.yml workflow in the Parako.ID repo (on any branch/tag) verify. A signature from any other workflow, or any other repo, is rejected.

You can verify a Parako.ID release independently:

Terminal window
gh release download v0.2.0 \
-p 'parako-id-v0.2.0.tar.gz' \
-p 'parako-id-v0.2.0.tar.gz.sig' \
-p 'parako-id-v0.2.0.tar.gz.pem'
cosign verify-blob \
--signature parako-id-v0.2.0.tar.gz.sig \
--certificate parako-id-v0.2.0.tar.gz.pem \
--certificate-identity-regexp 'https://github\.com/Dahkenangnon/Parako\.ID/\.github/workflows/release\.yml@.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
parako-id-v0.2.0.tar.gz

Expected output: Verified OK.

In the rare case where Sigstore is unreachable (network partition, Sigstore outage, regulated network), the installer offers an explicit escape:

Terminal window
curl --proto '=https' --tlsv1.2 -fsSL https://get.parako.id | sudo bash -s -- \
--update --insecure-no-signature \
--reason "Sigstore outage on 2026-06-15; verified SHA256 manually from release notes"

The escape requires:

  1. The exact word --insecure-no-signature
  2. A non-empty --reason "<text>"
  3. In interactive mode, the operator typing yes in full when prompted

The reason is logged verbatim to the structured install log at /var/log/parako-install-<ts>.log (or ${XDG_STATE_HOME}/parako/parako-install-<ts>.log for non-root installs). Use this escape only when you have manually verified the tarball SHA256 against the release notes.

The installer writes a structured JSON-lines log at /var/log/parako-install-${ts}.log (or ${XDG_STATE_HOME}/parako/... for non-root installs). Every line passes through a redactor that masks:

  • URI authentication (scheme://user:pass@hostscheme://***@host)
  • Any value following a key named password, secret, token, credential, api_key, hmac_secret, jwt_secret, cookie_secret_N, encryption_key, pairwise_salt

You can safely share the install log when reporting bugs.

Files the installer writes (mode is enforced regardless of the operator’s umask):

FileModeOwnerNotes
/var/log/parako-install-*.log0600install operatorInstaller’s own structured log
/usr/local/bin/parako0755root:rootOperator helper; install is non-fatal
/usr/local/bin/cosign0755root:rootOnly if cosign bootstrap was needed
${INSTALL_DIR}/.parako-state0644install operatorNo secrets; readable by non-root operators
${INSTALL_DIR}/.install-lock0644install operatorflock target for install / update / rollback / gc

Files the installer does not create or modify:

FileWhy
runtime/.envOperator-owned secrets
runtime/jwks/jwks.jsonOperator-owned signing keys
runtime/parako.jsonc, runtime/parako-rp.jsoncOperator-owned config
runtime/data/parako.dbOperator-owned database
/etc/systemd/system/*.serviceOperator-managed supervisor
/etc/nginx/sites-available/*Operator-managed reverse proxy
/etc/letsencrypt/*Operator-managed TLS

During install and update, the installer makes outbound HTTPS connections only to:

HostPurpose
api.github.comResolve latest release tag (skipped under --offline and when --version is set)
github.com / objects.githubusercontent.comRelease tarball + signature + certificate download
rekor.sigstore.dev / fulcio.sigstore.devCosign transparency log + certificate authority

Under --offline, the installer makes no network calls and requires --version, --tarball, --checksum, --signature, --certificate, and a preinstalled cosign binary on PATH.

No telemetry. The installer does not phone home.

When the cosign release version is bumped:

  1. Download the new cosign-linux-amd64 and cosign-linux-arm64 binaries from https://github.com/sigstore/cosign/releases/tag/v${NEW_VERSION}.
  2. Compute their SHA256 sums.
  3. Update COSIGN_VERSION, COSIGN_SHA256_LINUX_AMD64, COSIGN_SHA256_LINUX_ARM64 in installer/install.sh:§1.
  4. Update the cosign-installer step in .github/workflows/release.yml to the matching version.
  5. Test against the test VPS (fresh install + --update).

After each release, publish the SHA256 of installer/install.sh in the GitHub release notes so operators can verify the installer before piping to bash:

Terminal window
sha256sum installer/install.sh

Sigstore keyless signing does not use long-lived keys, so there’s no key rotation per se. The trust anchor is the workflow path. To revoke trust:

  1. Update COSIGN_CERT_IDENTITY_REGEX in installer/install.sh to a regex that excludes the bad commit range.
  2. Publish a security advisory.