fremforge-CLI
Die fremforge-CLI ist ein Wrapper aus einer einzigen Binärdatei um die öffentliche REST-API. Dieselben Verben, dasselbe Berechtigungsmodell, dieselben Scopes. Verteilt ausschließlich über PoPs in der EU; keine npm-Registry, kein Docker Hub, keine Abhängigkeit von Sigstore Fulcio.
Supportzeitraum, Sicherheitsupdates und SBOM
| Supportzeitraum | 5 Jahre ab dem Veröffentlichungsdatum jeder Minor-Version. Das ist der Supportzeitraum im Sinne von Art. 13 Abs. 8 des Cyber Resilience Act (Verordnung (EU) 2024/2847), und er gilt für die veröffentlichte Version, nicht für dein Abonnement. |
| Sicherheitskorrekturen | Kostenlos, immer, und nie an einen Tarif gebunden. Korrekturen erscheinen vorwärts als neues Release unter denselben URLs, abgedeckt durch dieselbe signierte SHA256SUMS. Du nutzt eine abgelöste Version? Aktualisiere, um die Korrektur zu erhalten. |
| Eine Schwachstelle melden | security@frem.sh – koordinierte Offenlegung, Details unter www.frem.sh/security. |
| SBOM | CycloneDX-JSON unter cli.frem.sh/cli/SBOM.cdx.json, pro Version unter cli.frem.sh/cli/v<version>/SBOM.cdx.json. |
| Vertragsbedingungen | AGB §7.5 – Lizenz, Gewährleistung und Haftung speziell für die Binärdatei. |
Die SBOM ist kurz, und genau darum geht es. Die CLI deklariert null npm-Abhängigkeiten
von Dritten. Sie hat zwei Abhängigkeiten, beide von uns: @fremverk/audit-chain, derselbe
Hash-Ketten-Code, den die API selbst ausführt (gemeinsam genutzt statt neu implementiert, damit
audit-verify nicht von dem abweichen kann, was es prüft), und @fremforge/tlog-verify, das
für attest-verify die Aufnahme in das Transparenzlog prüft. Keine von beiden hat eigene
Abhängigkeiten; beide importieren nur eingebaute Node-Module, sodass die gesamte Angriffsfläche
durch Dritte in der ausgelieferten Binärdatei die beim Kompilieren eingebettete Bun-Laufzeit
ist. Ab v0.3.1 führt die SBOM auch die beiden eigenen Pakete auf, als unsere gekennzeichnet,
damit sie jede Komponente nennt und nicht nur die von Dritten.
Der Hash der SBOM steht in SHA256SUMS und ist damit durch dieselbe Ed25519-Signatur abgedeckt
wie die Binärdateien – prüfe sie auf dieselbe Weise (siehe §„Die Ed25519-Signatur prüfen“
unten – das gilt für jede im Manifest aufgeführte Datei, nicht nur für die Binärdateien):
curl -fsSL https://cli.frem.sh/cli/SBOM.cdx.json -o SBOM.cdx.json
curl -fsSL https://trust.frem.sh/SHA256SUMS -o SHA256SUMS
grep SBOM.cdx.json SHA256SUMS | shasum -a 256 -c -Die CLI ist optional, mit einer Ausnahme. Jede Aktion, die sie ausführt, ist auch über
die REST-API verfügbar; verbietet deine Richtlinie also, Binärdateien von
Anbietern auszuführen, verlierst du ohne sie keine Funktion. Die Ausnahme ist audit-verify,
das deine Audit-Hash-Kette auf deinem Rechner neu berechnet – das kann eine API nicht für
dich tun, denn der ganze Sinn der Neuberechnung einer Hash-Kette besteht darin, der Partei, die
sie erzeugt hat, nicht zu vertrauen. Die Rohzeilen liefert
GET /orgs/{org}/audit/chain, du kannst die Rechnung also auch mit eigenen
Werkzeugen erledigen; was du nicht kannst, ist, sie von uns erledigen zu lassen und das
Ergebnis Verifikation zu nennen.
Installieren
Einzeiler (empfohlen)
curl -sSfL https://cli.frem.sh/cli/install.sh | shDas Installationsskript erkennt automatisch dein Betriebssystem (Linux oder macOS) und deine Architektur (arm64 oder x86_64), lädt die passende Binärdatei von https://cli.frem.sh/cli/ herunter, lädt die maßgebliche SHA256SUMS von https://trust.frem.sh/ (einem separaten Host mit separaten Schreib-Zugangsdaten, sodass eine Kompromittierung allein des Binär-Hosts keine Übereinstimmung fälschen kann), prüft den SHA-256 und installiert nach ~/.local/bin/fremforge oder /usr/local/bin/fremforge.
Den Installationsort änderst du mit FREMFORGE_INSTALL_DIR:
curl -sSfL https://cli.frem.sh/cli/install.sh | FREMFORGE_INSTALL_DIR=/opt/fremforge/bin shEine bestimmte Version festlegen:
curl -sSfL https://cli.frem.sh/cli/install.sh | FREMFORGE_VERSION=0.1.0 shManueller Download
Möchtest du kein entferntes Skript an sh weiterleiten, sind die Binärdateien direkt erreichbar. Wähle die passende Kombination:
| Betriebssystem | Architektur | URL |
|---|---|---|
| macOS | arm64 | https://cli.frem.sh/cli/fremforge-darwin-arm64 |
| macOS | x86_64 | https://cli.frem.sh/cli/fremforge-darwin-x64 |
| Linux | arm64 | https://cli.frem.sh/cli/fremforge-linux-arm64 |
| Linux | x86_64 | https://cli.frem.sh/cli/fremforge-linux-x64 |
| Windows | x86_64 | https://cli.frem.sh/cli/fremforge-windows-x64.exe |
Das Architektur-Suffix lautet x64 (nicht x86_64) bzw. arm64 – das entspricht den --target-Bezeichnern von Bun. uname -m meldet auf Intel/AMD x86_64; ordne das also x64 zu, wenn du die URL von Hand zusammensetzt (das Einzeiler-Installationsskript erledigt das für dich).
Prüfe die Datei vor dem Ausführen gegen den maßgeblichen Hash auf trust.frem.sh:
# 0. Map uname -m's x86_64 to the published x64 token
arch=$(uname -m); [ "$arch" = x86_64 ] && arch=x64
# 1. Download the binary
curl -fsSL https://cli.frem.sh/cli/fremforge-$(uname -s | tr A-Z a-z)-$arch \
-o fremforge
# 2. Download the canonical SHA256SUMS (separate host, separate credential)
curl -fsSL https://trust.frem.sh/SHA256SUMS -o SHA256SUMS
# 3. Verify
sha256sum --check --ignore-missing SHA256SUMS
# 4. Install
chmod +x fremforge
sudo install -m 0755 fremforge /usr/local/bin/fremforgeDie Ed25519-Signatur prüfen (optional, empfohlen)
Über die SHA-256-Übereinstimmung hinaus ist jedes Release als in DSSE verpacktes in-toto-Statement signiert, unter derselben Vertrauenswurzel wie die SLSA-Build-Provenienz von fremforge. Der Prüfer lädt die öffentliche Vertrauenswurzel von www.frem.sh/.well-known/slsa-trust-root.json und prüft die Ed25519-Signatur über die Bytes von SHA256SUMS.
Greif nicht zu
slsa-verifier verify-artifact. Dieser Unterbefehl hat kein--trusted-root-Flag und kann weder das Trust-Root-JSON von fremforge noch einen DSSE-Umschlag mit reinem Ed25519 verarbeiten – er erwartet ein Bundle im Sigstore-Stil. Diese Seite zeigte früher einen solchen Befehl; er hätte nie funktionieren können. Prüfe stattdessen direkt, mit Werkzeugen, die deine Distribution bereits mitbringt.
SHA256SUMS.intoto.jsonl ist JSON Lines: ein DSSE-Umschlag pro veröffentlichter
Binärdatei. Prüfe jede Zeile, die dich interessiert.
# Pull the trust root and the signed envelopes
curl -fsSL https://www.frem.sh/.well-known/slsa-trust-root.json -o slsa-trust-root.json
curl -fsSL https://trust.frem.sh/SHA256SUMS.intoto.jsonl -o SHA256SUMS.intoto.jsonl
# Extract the builder public key: v2, the active key since 2026-09-12. An envelope's
# .signatures[0].keyid names its key; one signed before 2026-09-12 needs ...-v1.
jq -r '.trusted_keys[] | select(.kid=="fremforge-slsa-builder-v2") | .pem' \
slsa-trust-root.json > builder.pub
# Verify every envelope: rebuild the DSSE pre-authentication encoding and check it
while read -r line; do
pt=$(printf '%s' "$line" | jq -r .payloadType)
printf '%s' "$line" | jq -r .payload | base64 -d > payload.bin
printf '%s' "$line" | jq -r '.signatures[0].sig' | base64 -d > sig.bin
{ printf 'DSSEv1 %s %s %s ' "${#pt}" "$pt" "$(wc -c < payload.bin)"; cat payload.bin; } > pae.bin
openssl pkeyutl -verify -pubin -inkey builder.pub -rawin -in pae.bin -sigfile sig.bin \
&& jq -r '.subject[] | .name + " " + .digest.sha256' payload.bin
done < SHA256SUMS.intoto.jsonlJeder geprüfte Umschlag gibt jedes Artefakt aus, das er abdeckt, zusammen mit dem Digest, den fremforge dafür signiert hat. Vergleiche diesen Digest mit der Binärdatei, die du heruntergeladen hast – genau darauf kommt es an; die Signatur sagt dir nur, dass der Digest von uns stammt.
Die Vertrauenswurzel ist dieselbe wie für die Build-Provenienz der Artefakte, die dein Runner erzeugt – derselbe Ed25519-Schlüssel, dieselbe builderId, dasselbe Prüfrezept. Ein SLSA-Level nennen wir nicht; die Sicherheitsseite erklärt, warum.
Authentifizierung
Die CLI unterstützt drei Arten von Zugangsdaten, ausgewertet in dieser Reihenfolge: das Flag --token=…, die Umgebungsvariable FREMFORGE_TOKEN, dann die gespeicherte Anmeldung in ~/.config/fremforge/auth.json.
Empfohlen – Anmeldung im Browser (ohne Einfügen)
fremforge auth loginÖffnet frem.sh in deinem Browser, du bestätigst das Gerät, das Token landet in ~/.config/fremforge/auth.json (Modus 600), und jeder weitere Aufruf lädt es automatisch. Es ist derselbe OAuth2-+-PKCE-Ablauf, den dein git push bereits nutzt; widerrufst du das Token unter frem.sh/user/settings/applications, widerrufst du also Git- und CLI-Zugriff in einem Schritt.
fremforge auth status # show current login + expiry
fremforge auth logout # revoke + delete the local fileDas Token funktioniert mit beiden APIs: der fremforge-REST-API (/_app/api/v1/orgs/…) UND der nativen API von Forgejo (/api/v1/repos/…) – Forgejo akzeptiert seine eigenen OAuth-Bearer-Token direkt, und die fremforge-API prüft sie per Introspection gegen /api/v1/user von Forgejo. Dieselbe Identität, beide Schnittstellen.
CI und Skripte – PAT
Für nicht interaktive Kontexte (CI-Jobs, Skripte, Agenten) erstellst du ein PAT mit eingeschränkten Scopes. Am einfachsten geht das mit der CLI selbst, sobald du dich einmal interaktiv angemeldet hast (fremforge auth tokens create --name=… --scopes=… – siehe Ein PAT mit eingeschränkten Scopes für CI erstellen); alternativ erstellst du eines unter https://frem.sh/user/settings/applications. Dann:
export FREMFORGE_TOKEN='<your PAT>'
fremforge whoamiDie Basis-URL änderst du mit FREMFORGE_URL= (muss HTTPS sein); Standard ist https://frem.sh.
Befehle
fremforge auth login Browser-based OAuth2+PKCE login.
fremforge auth logout Revoke + delete the stored token.
fremforge auth status Show current login + expiry.
fremforge whoami Print the user the token authenticates as.
fremforge data-export start <slug> Queue a full data export for the org.
fremforge data-export list <slug> List recent export jobs.
fremforge dependency-updates enable <slug> Enable hosted Renovate for the org.
fremforge dependency-updates disable <slug> Disable hosted Renovate for the org.
fremforge dependency-updates run-now <slug> Trigger an out-of-cycle Renovate run (1/h cap).
fremforge audit-log <slug> Fetch the org audit-log slice.
[--since=DATE] [--actor=…] [--action=…] [--limit=N]
fremforge audit-verify <slug> RECOMPUTE the per-tenant audit hash chain
[--human] [--full] locally, then cross-check our own verdict.
Exit 0=ok, 2=partial, 3=broken,
4=anchor_mismatch, 5=local ok but we
disagreed. --full walks from the chain
epoch instead of the latest anchor.
fremforge sbom <owner>/<repo> <tag> Download a release SBOM.
[--format=spdx|cyclonedx]
fremforge runs <owner>/<repo> List recent CI runs (JSON; --human for a table).
[--limit=N] [--status=…] [--event=…]
fremforge runs logs <owner>/<repo> <run-id> Fetch a run's logs (text/plain, all jobs).
fremforge auth tokens create Mint a scoped PAT (printed once).
--name=N --scopes=a,b [--ttl-days=N]
fremforge auth tokens list List your PATs (--human for a table).
fremforge auth tokens revoke <token-id> Revoke a PAT.
fremforge findings <type> <slug> Security findings. type ∈ secrets |
[--status=…] [--repo=…] dependencies | signed-commits | attestations.
fremforge secrets list <slug> List org Actions/CI secret names.
fremforge secrets set <slug> <NAME> Set a secret (value via FREMFORGE_SECRET_VALUE).
fremforge secrets delete <slug> <NAME> Delete a secret.
fremforge seats list <slug> List org members + seat usage.
fremforge seats add <slug> --email=E Add a member (--username=, --full-name= optional).
fremforge seats remove <slug> <username> Remove a member.
fremforge work start <org>/<repo> Declare work before the first push (Active work).
--title=TEXT [--intent=TEXT] Prints the branch to create and related work.
[--areas=a,b] [--branch=NAME]
fremforge work list <org>/<repo> Who is building what in a repository.
[--area=AREA]
fremforge version Print the CLI version.Die maßgebliche Referenz liefert fremforge --help; bei jedem Befehl, der es unterstützt, erhältst du mit --human eine Ausgabe als Tabelle.
Ausgearbeitete Beispiele
Deine Audit-Kette selbst prüfen (in CI nutzbar)
audit-verify fragt uns nicht, ob dein Audit-Log intakt ist. Es lädt die Rohzeilen herunter,
berechnet jeden Hash und jede Verknüpfung auf deinem Rechner neu und vergleicht sein
Ergebnis erst danach mit unserem. Der Exit-Code ist dein Ergebnis, nicht unsere Behauptung –
und weichen die beiden voneinander ab, wird genau diese Abweichung gemeldet, und der Befehl
schlägt fehl.
Dieser Unterschied ist der Grund, warum es diesen Befehl gibt. Bis August 2026 las er ein Feld
integrity_status aus einer API-Antwort und bildete es auf einen Exit-Code ab – die geprüfte
Partei berechnete also die Antwort, und das „Prüfwerkzeug“ gab sie nur weiter. Nichts, was du
ausführen konntest, hätte eine kompromittierte oder unehrliche API erkannt. Jetzt schon.
export FREMFORGE_TOKEN='<your PAT, audit:read scope>'
# Human-readable, for terminals
fremforge audit-verify acme --human
# Script-friendly. The exit code reflects the LOCAL walk:
# 0 = ok
# 2 = partial — some of the chain could not be checked (see below)
# 3 = broken — a row's content or its link does not match. Investigate.
# 4 = anchor_mismatch — the WORM-anchored tip is not present in your chain
# 5 = we disagreed — your walk was clean but our verdict was not
# 6 = bad signature — the anchor's signature failed against our published key
fremforge audit-verify acme || {
echo "audit verification failed with exit code $?"
exit 1
}
# Walk the entire chain from its first row instead of just the tail since the
# last anchor. Slower and much larger; appropriate for a quarterly review.
fremforge audit-verify acme --full --humanDie Ergebnisse ehrlich lesen – denn ein Prüfwerkzeug, das mehr verspricht, als es leistet, ist schlimmer als keines:
partialheißt nicht bestanden. Es bedeutet, dass eine Kürzung einen Teil der Kette verdeckt hat; die Zeilen außerhalb des Fensters sind also ungeprüft. Wir melden in diesem Fallok, die CLI bewusst nicht.- Gelöschte und geschwärzte Zeilen ergeben absichtlich nicht mehr denselben Hash. Die
Löschung nach DSGVO und der Payload-Reaper nach 90 Tagen schreiben Zeileninhalte
nachträglich um, sodass diese Zeilen nicht mehr zu ihrem eigenen Hash passen. Sie werden
getrennt gezählt (
tenant_erased_count,payload_redacted_count) und sind keine Manipulation. Ihre Verknüpfungen werden trotzdem geprüft. - Der WORM-Anker ist signiert, und die CLI prüft ihn gegen eine Vertrauenswurzel, die sie
selbst abruft.
audit-verifyholtwww.frem.sh/.well-known/slsa-trust-root.jsondirekt – nie einen Schlüssel aus der API –, prüft die Ed25519-DSSE-Signatur und stellt sicher, dass der Umschlag an diesen Anker gebunden ist und nicht an einen anderen. Exit 6 bedeutet, dass die Signatur geprüft wurde und fehlgeschlagen ist; behandle das als Vorfall.NOT CHECKED(Exit-Code unverändert) bedeutet, dass entweder der Anker älter ist als die Signierung (04.08.2026) oder die Vertrauenswurzel von deinem Rechner aus nicht erreichbar war – beides ist kein Fehler, und die CLI sagt dir, welcher Fall vorliegt.
Dasselbe ohne Binärdatei: Lies die Zeilen aus
GET /orgs/{org}/audit/chain und hashe das kanonische JSON jeder Zeile mit
den Schlüsseln in der Reihenfolge action, actor, created_at_iso, fields_json, prev_hash, tenant_id.
Lass fields_json byte-genau unverändert – parse und serialisiere es nicht neu, sonst passen
deine Digests nicht.
Die vollständige Garantie beschreibt Integrität der Audit-Kette.
Datenexport in Selbstbedienung
# Kick off
fremforge data-export start acme | jq .
# Poll
fremforge data-export list acme | jq '.[] | {id, status, created_at}'Artefaktformat, Aufbewahrung und den Download über signierte URLs beschreibt Datenexport.
Abhängigkeits-Updates (gehostetes Renovate)
fremforge dependency-updates enable acme
fremforge dependency-updates run-now acme # 1/h rate-limit appliesWas einbezogen wird und wie du es pro Repository eingrenzt, beschreibt Abhängigkeits-Updates.
SBOM herunterladen
fremforge sbom acme/api v1.4.0 --format=spdx > sbom.spdx.json
fremforge sbom acme/api v1.4.0 --format=cyclonedx > sbom.cyclonedx.jsonAusschnitt aus dem Audit-Log
fremforge audit-log acme --since=2026-05-01 --action=push-protection-override | jq .CI-Läufe und Logs
Liste die letzten Läufe auf und hol dann die vollständigen Logs eines Laufs (alle Jobs aneinandergehängt) über seine globale Lauf-ID:
# List the 20 most recent runs as a table
fremforge runs acme/api --human
# Script-friendly: grab the newest run id, then fetch its logs
run_id=$(fremforge runs acme/api --limit=1 | jq -r '.workflow_runs[0].id')
fremforge runs logs acme/api "$run_id"
# Tail just the failing step
fremforge runs logs acme/api "$run_id" | grep -iE "error|failed|exit code" | tail<run-id> ist die globale Lauf-ID, die fremforge runs anzeigt (das Feld .workflow_runs[].id) – nicht die Laufnummer pro Workflow aus der URL der Weboberfläche. Der Logs-Endpunkt liefert text/plain mit allen Jobs in einer Antwort; ein Download von Artefakten pro Job ist nicht nötig.
Ein PAT mit eingeschränkten Scopes für CI erstellen
Nach deinem ersten fremforge auth login erstellst du eng begrenzte untergeordnete Token für CI, ohne das Terminal zu verlassen. Das Token wird einmal ausgegeben – speichere es sofort:
fremforge auth tokens create --name=ci-readonly --scopes=findings:read,orgs:read --ttl-days=30 \
| jq -r .token
fremforge auth tokens list --human
fremforge auth tokens revoke <token-id>Du kannst nur Scopes vergeben, die dein eigenes Token bereits hat, und eine Wildcard (*) lässt sich hier nicht vergeben.
CI-Secrets verwalten
fremforge secrets list acme
# Pass the value via env (preferred — not in shell history or `ps`)
FREMFORGE_SECRET_VALUE="$(cat ./deploy-key)" fremforge secrets set acme DEPLOY_KEY
fremforge secrets delete acme OLD_TOKENNutzer und Mitglieder
fremforge seats list acme --human # cap + usage + roster
fremforge seats add acme --email=jane@acme.example --full-name="Jane Doe"
fremforge seats remove acme janeFügst du über das vertraglich vereinbarte Nutzerlimit hinaus ein Mitglied hinzu, kommt ein 409 mit einem Link zur Abrechnung zurück – kaufe zuerst weitere Nutzer.
Ausgabeformat
Standardmäßig gibt jeder Befehl JSON auf stdout aus, gut für Skripte und Pipes. Übergib --human bei den Befehlen, die es unterstützen (audit-verify, attest-verify, release verify, runs, auth tokens list, seats list und work), für eine formatierte Zusammenfassung.
Die CLI aktualisieren
Das Einzeiler-Installationsskript ist idempotent: Führst du es erneut aus, holt es die neueste Binärdatei, prüft die Prüfsumme erneut und überschreibt die alte Binärdatei an Ort und Stelle:
curl -sSfL https://cli.frem.sh/cli/install.sh | shHinweise zur Souveränität
Der Verteilweg der CLI ist vollständig EU-souverän:
- Binärdateien: T Cloud Public OBS (eu-de, Magdeburg / Biere, Deutschland), hinter einer Bunny-Pull-Zone, die auf PoPs in der EU beschränkt ist.
- SHA256SUMS + DSSE-Umschlag: ein separater Bucket in T Cloud Public OBS (
trust.frem.sh) mit separaten Schreib-Zugangsdaten; durch die Trennung von Binär-Host und Integritäts-Host kann die Kompromittierung des einen keine Übereinstimmung mit dem anderen fälschen. - Vertrauenswurzel: statische JSON-Datei, ausgeliefert von
www.frem.sh(dasselbe Bunny-Routing nur über die EU). - Keine Abhängigkeit von der npm-Registry: registry.npmjs.com läuft in US-AWS; die CLI berührt sie weder bei der Installation noch bei der Nutzung.
- Keine Abhängigkeit von Docker Hub: aus demselben Grund.
- Kein Sigstore Fulcio / Rekor: Das Transparenzlog von Sigstore wird in den USA gehostet. fremforge betreibt einen eigenen Ed25519-Signierer und eine statische Vertrauenswurzel, statt von Fulcio abzuhängen.
Die am Installationsweg beteiligten Unterauftragsverarbeiter stehen im öffentlichen Verzeichnis der Unterauftragsverarbeiter und sind dieselben, die bereits gemäß Anhang B des AVV personenbezogene Daten von Kunden verarbeiten.