Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 50 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write # OIDC identity for Sigstore keyless signing / attestation
steps:
- uses: actions/download-artifact@v4
with:
Expand All @@ -52,13 +53,58 @@ jobs:
- name: checksums
run: sha256sum odek-* > checksums.txt

# SPDX SBOM of a representative build. All matrix entries compile the
# same source with the same go.mod, so the module set is identical;
# one SBOM covers the release. Upload handled by the release step so
# the SBOM ships signed like every other asset.
- name: SBOM
uses: anchore/sbom-action@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 # v0.24.2
with:
format: spdx-json
artifact: odek-linux-amd64
output-file: odek-${{ github.ref_name }}-sbom.spdx.json
upload-artifact: false
upload-release-assets: false

- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: v3.1.3 # pinned: a future cosign major must not break a tagged release mid-flight

# Keyless signing: Fulcio-issued certificate bound to this workflow's
# OIDC identity, logged in the Rekor transparency log. Explicit file
# list (not a glob) so generated .bundle outputs can never be re-signed.
- name: sign artifacts
run: |
BINARIES="odek-linux-amd64 odek-linux-arm64 odek-darwin-amd64 odek-darwin-arm64"
for f in $BINARIES checksums.txt; do
cosign sign-blob --yes --bundle "${f}.bundle" "$f"
done

# in-toto attestation naming the exact source commit each artifact was
# built from. Predicate type "custom" keeps the step free of cosign's
# SLSA schema validation; the content is SLSA-provenance-shaped.
- name: provenance attestation
env:
GIT_REF: ${{ github.ref }}
GIT_SHA: ${{ github.sha }}
run: |
printf '{
"builder": {"id": "https://github.com/BackendStack21/odek/.github/workflows/release.yml@%s"},
"buildType": "https://github.com/BackendStack21/odek/.github/workflows/release.yml",
"invocation": {"configSource": {"uri": "git+https://github.com/BackendStack21/odek", "digest": {"sha1": "%s"}}},
"metadata": {"completeness": {"parameters": true, "environment": false}, "reproducible": false},
"materials": [{"uri": "git+https://github.com/BackendStack21/odek", "digest": {"sha1": "%s"}}]
}' "${GIT_REF}" "${GIT_SHA}" "${GIT_SHA}" > provenance.json
BINARIES="odek-linux-amd64 odek-linux-arm64 odek-darwin-amd64 odek-darwin-arm64"
for f in $BINARIES checksums.txt; do
cosign attest-blob --yes --type custom --predicate provenance.json --bundle "${f}.attestation.bundle" "$f"
done

- name: release
uses: softprops/action-gh-release@v3
with:
files: |
odek-linux-amd64
odek-linux-arm64
odek-darwin-amd64
odek-darwin-arm64
odek-*
checksums.txt
checksums.txt.bundle
generate_release_notes: true
11 changes: 10 additions & 1 deletion docs/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,15 @@ All security-relevant state under `~/.odek` is written through `internal/fsatomi

`odek upgrade` verifies the downloaded release against the published `checksums.txt` (SHA-256) and refuses to install a binary with no checksum entry, swapping it in atomically over the running executable. The latest-release lookup authenticates with `GITHUB_TOKEN` / `GH_TOKEN` when set; a 401/403/429 from the REST API falls back to the public HTML latest-release redirect and synthesized `browser_download_url`s (checksum verification is unchanged).

Release binaries are additionally Sigstore keyless-signed at build time: each release ships `<artifact>.bundle` signatures and `<artifact>.attestation.bundle` in-toto attestations naming the exact source commit, plus an SPDX SBOM (`odek-<tag>-sbom.spdx.json`). Signatures are issued against the workflow's OIDC identity (`https://github.com/BackendStack21/odek/.github/workflows/release.yml@refs/tags/*`) and logged in the Rekor transparency log, so a mirrored or tampered release feed cannot forge a valid bundle. Verify with:

```
cosign verify-blob --bundle odek-darwin-arm64.bundle \
--certificate-identity-regexp '^https://github\.com/BackendStack21/odek/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
odek-darwin-arm64
```

### Resource bounds

Hostile or accidental input is bounded everywhere it is sized, to keep it from OOMing or stalling the process. The major caps:
Expand Down Expand Up @@ -711,7 +720,7 @@ Background jobs inherit the shell tool's security model with no downgrade:
| Served Web UI page is framed or clickjacked | `X-Frame-Options: DENY` + CSP `frame-ancestors 'none'`, no inline scripts |
| Forged `<untrusted_content>` envelope renders as instructions in the Web UI | Client renders mismatched envelopes as plain text; all output HTML-escaped |
| Skill imported from attacker URL with rebinding / `inet_aton` tricks | Import-time SSRF guard (scheme allowlist, 1-hop redirects, private-IP blocking) |
| Compromised release feed serves a trojaned binary | `odek upgrade` verifies SHA-256 against `checksums.txt`, fails closed |
| Compromised release feed serves a trojaned binary | `odek upgrade` verifies SHA-256 against `checksums.txt`, fails closed; artifacts are additionally Sigstore keyless-signed with the bundles published in the same release — a tampered feed cannot forge a signature rooted in the Rekor transparency log |
| Compaction summary launders untrusted text into system context | Digest wrapped with the untrusted boundary |
| Concurrent `odek schedule add` processes clobber each other | Cross-process `flock`; lock failure is a hard error |
| Tampered `schedules.json` replaced with a multi-gigabyte blob | 10 MiB size cap |
Expand Down
Loading