Security - Terrio-cz/CodeLoupe GitHub Wiki
What protects what in the repository and in a release, how to verify a release, how far the updater is trusted, and where the repository settings the owner still has to apply are listed (they cannot be set from a pull request). The local API is covered in Configuration, the secret store in Environment and secrets, reporting a problem in the security policy.
| Rule | Where | Checked by |
|---|---|---|
Every workflow has a top-level permissions: block (read-only by default); a job that needs more asks for it on the job |
.github/workflows/*.yml |
node tools/check-workflows.mjs, in the tools job of every push |
| Every action is pinned to a full commit SHA, never a tag | the same | the same, and Dependabot proposes the update (after a 7-day cooldown) |
actions/checkout leaves no token on disk; no pull_request_target; nothing an outsider controls inside a run: block |
the same | the same |
| A pull request that adds a vulnerable dependency (severity high or worse) does not pass | dependency-review.yml |
GitHub's dependency review |
A release is built from a tag that is on main; the build job holds a read-only token and runs the third-party code, the publish job holds the write token and runs nothing but gh
|
release.yml |
the workflow itself |
The release is a draft; publishing it is a person's decision. The publish job runs in the release environment, where the owner can require an approval (below) |
release.yml |
GitHub |
| Every file of a release carries a signed build provenance attestation (SLSA, Sigstore) |
release.yml, actions/attest
|
gh attestation verify |
SBOMs (CycloneDX) of the daemon and of what the app ships, SHA256SUMS.txt, and an update feed that must match the installers |
release.yml, tools/feed-check.mjs
|
the release job |
CODEOWNERS names the maintainer for everything; SECURITY.md says how to report privately |
.github/CODEOWNERS, SECURITY.md
|
|
| The Gradle distribution is pinned by checksum | gradle/wrapper/gradle-wrapper.properties |
Gradle |
sha256sum -c SHA256SUMS.txt # in the folder with the downloaded files
gh attestation verify CodeLoupe-<version>-linux-x86_64.AppImage --repo Terrio-cz/CodeLoupeThe second command checks the Sigstore signature of the provenance and prints the workflow, the tag and the commit the file was built from. A file that was replaced after the build, or built anywhere else, fails it. The release notes repeat both lines.
What exists, and costs nothing: the app asks only github.com/Terrio-cz/CodeLoupe (the origin is a constant, not a setting; a
redirect GitHub answers a download with is followed), over HTTPS; it reads latest.yml / latest-linux.yml of one release,
downloads the installer, and installs it only if its SHA-512 and size match the feed; it never installs an older version than
the running one; the release job refuses a feed that does not match the installers beside it; the previous bundle comes back if the
new daemon does not answer in 90 seconds.
What a SHA-512 does not give: the feed and the installer come from the same release, so whoever can change a release's files (a stolen
maintainer token, a hijacked publish job) can change both. A signature that the app checks against a key it already holds is what
closes that, and there are two ways to have one:
- Authenticode (Windows) and Developer ID (macOS) need a certificate, which costs money; the owner decided on 2026-10-08 to pay for nothing (code-signing.md). Open until that changes.
-
A detached signature over the feed costs nothing and is built (CL-174), but is off until the owner has made the key pair. The
publishjob signslatest.ymlandlatest-linux.ymlwith an Ed25519 key kept as theUPDATE_SIGNING_KEYsecret of thereleaseenvironment (only that job reads it, and it runs nothing butopensslandghwith it; the build code never sees it) and addslatest.yml.sigandlatest-linux.yml.sigto the release, base64 of the raw signature. The app embeds the matching public keys (app/src/main/update/updateKeys.ts; a list, so a new key can ship before the old one is dropped) and, before it reads a feed, fetches the.sigbeside it and verifies it over the exact text it then parses. A feed with no signature, a signature by a key the build does not list (an old key included) or a feed changed after signing is refused with the reason in the update log, and no installer is requested. While the list is empty a build checks only the SHA-512 as before and writesupdate feeds are not signature-checkedtoupdate.log; the release workflow then prints a notice that the feeds were not signed. Rotating a key by signing the new one with the old one is not built: ship the new public key next to the old in one release, and drop the old in the next. The owner's steps (key pair, secret, public key in the constant) are in repository-hardening.md.
Until a key is embedded the provenance attestation above is the check a person can make by hand, and immutable releases (see Repository settings below) keep a published release from being changed afterwards.
A few protections are repository settings, not files, and an owner with admin rights has to apply them (rulesets for main and for
v* tags, the Actions policy, immutable releases, the release environment, private vulnerability reporting). They are listed with the
current state and the commands in repository-hardening.md.