Full data export
You can export everything fremforge holds for your org, on demand, from the admin UI: Repositories, issues and pull requests (in the audit log slice), Actions logs, audit trail, and SLSA attestations, all in standard formats, downloadable as a single zip with a SHA-256 manifest covering every file.
This page documents what’s in the bundle, how to verify it, and what limits apply. The export is built into the €30/seat plan; no Enterprise tier required.
What’s in the bundle
fremforge-export-<your-org-slug>-<YYYYMMDD-HHMMSS>.tar.gz
├── VERIFY.md — step-by-step verification recipe
├── manifest.json — every file, SHA-256, size
├── manifest.json.intoto.jsonl — DSSE-signed in-toto Statement over manifest.json
├── archive.tar.gz.sha256 — top-level digest of the tarball (sibling alongside the tarball)
├── audit.ndjson — per-tenant audit slice (last 90 days)
├── bundles/
│ └── <slug>.bundle — git bundle (mirror clone of the repo)Standards used:
- Repos: Git’s native
git bundleformat.git clone <bundle>reconstructs the repo end-to-end, including history and tags. - LFS: standard pointer format with
oid,size,pathfields. - Audit log: JSONL, one event per line. Same shape as fremforge’s internal audit stream.
- SLSA:
*.intoto.jsonl, the in-toto attestation envelope, the SLSA standard. - Integrity: a
manifest.jsonlists every file with its SHA-256 hash, plus a top-levelarchive.tar.gz.sha256digest. The manifest is DSSE-signed with fremforge’s Ed25519 builder key, the signature lives inmanifest.json.intoto.jsonland verifies against the public trust root athttps://www.frem.sh/.well-known/slsa-trust-root.jsonusingopenssl,jq,python3, andcurl, no fremforge tooling required. The full recipe is inVERIFY.mdinside the bundle and in §“Verifying the signed manifest” below. This is the same trust root published for SLSA build attestations, one key, two predicate types, zero Sigstore dependency.
No proprietary formats. Every artifact in the bundle reads with standard OSS tooling. No fremforge CLI required.
What’s NOT in the bundle (and why)
- Issues / PRs as standalone JSON, they live in the repo’s git history (issue references show up in commit messages) and in the audit log slice (every issue / PR state change is logged). Standalone-JSON-per-issue is planned for Phase 1.5.
- CI run artifacts, Actions logs are in the bundle; the actual artifacts produced by your runs are in OBS and can grow large. Use the API to fetch specific artifacts.
- Other tenants’ data, the audit-log slice is filtered to your org only; cross-tenant identifiers are redacted before they leave the export pipeline.
How to start an export
- Org admin UI → Data export tab.
- Click Start a new export.
- Wait for the bundle. Small orgs (< 1 GB) ready in 1-3 minutes; larger orgs (10+ GB) up to ~30 min.
- The download link is valid for 7 days after the bundle is ready.
You’ll get an email when the bundle is ready (sent via Lettermint from the same address you receive billing receipts from).
Rate limit
One export per org per 24 hours. Hard cap. The reason: git bundle-ing a multi-GB repo and computing manifest hashes isn’t free, and a misclick shouldn’t spawn a hundred exports overnight.
If you genuinely need a second export inside 24 hours, contact support@frem.sh. We can lift the cap on request.
Size limits
- Soft warning at 10 GB, the dashboard shows an estimate before kickoff so you can decide.
- Hard cap at 50 GB, above this we contact you for an enterprise export procedure (typically a direct OBS-to-OBS bucket sync to your own bucket).
For most orgs, repo content is the largest line; Actions logs and audit data are small relative.
Verifying the bundle
Verifying that the bundle reached you intact takes two sha256sum checks. Prerequisite: jq (the SHA-256 utility ships with every modern OS, sha256sum on Linux, shasum -a 256 on macOS).
1. Verify the top-level archive digest
# Linux
sha256sum --check archive.tar.gz.sha256
# macOS
shasum -a 256 -c archive.tar.gz.sha256This confirms the inner tarball you received matches the digest fremforge generated at export time.
2. Verify each file’s hash against the manifest
sha256sum --check <(jq -r '.files[] | "\(.sha256) \(.path)"' manifest.json)If both checks pass, you have an integrity-verified export, every file in the bundle matches the SHA-256 hash fremforge recorded.
What this catches and what it doesn’t
The SHA-256 chain catches post-export tampering (download corruption, MITM modification of an unsigned mirror) and matches the bundle to the manifest fremforge produced. To prove the manifest itself was produced by fremforge (not a third party who swapped your bundle for theirs), run the signed-manifest verification in the next section, it cryptographically binds the manifest bytes to fremforge’s published Ed25519 builder key.
Verifying the signed manifest
The bundle ships with a DSSE-signed in-toto Statement over manifest.json at manifest.json.intoto.jsonl. The signature is produced with fremforge’s SLSA builder Ed25519 key (fremforge-slsa-builder-v1), the same key that signs SLSA build provenance for artifacts produced by the hosted runner, and verifies against the public trust root at https://www.frem.sh/.well-known/slsa-trust-root.json.
You verify with curl, jq, openssl, and python3, all distribution-default. No fremforge CLI. No Sigstore Fulcio / Rekor / TUF-CDN dependency.
1. Fetch the public trust root and extract the pubkey
curl -sSfL https://www.frem.sh/.well-known/slsa-trust-root.json -o trust-root.json
jq -r '.trusted_keys[] | select(.kid=="fremforge-slsa-builder-v1") | .pem' \
trust-root.json > builder.pub2. Verify the DSSE envelope signature
python3 - <<'PY'
import base64, json, hashlib, subprocess, sys, pathlib
env = json.loads(pathlib.Path("manifest.json.intoto.jsonl").read_text().strip())
payload_type = env["payloadType"]
payload = base64.b64decode(env["payload"])
# Reconstruct the DSSE pre-authentication encoding.
pae = (
b"DSSEv1 "
+ str(len(payload_type)).encode() + b" "
+ payload_type.encode() + b" "
+ str(len(payload)).encode() + b" "
+ payload
)
pathlib.Path("pae.bin").write_bytes(pae)
pathlib.Path("sig.bin").write_bytes(base64.b64decode(env["signatures"][0]["sig"]))
# Ed25519 verify: openssl pkeyutl -verify with -rawin (EdDSA is "pure",
# no pre-hash — feed the raw PAE bytes).
r = subprocess.run(
["openssl", "pkeyutl", "-verify", "-pubin",
"-inkey", "builder.pub",
"-rawin", "-in", "pae.bin",
"-sigfile", "sig.bin"],
capture_output=True, text=True,
)
if r.returncode != 0 or "Signature Verified Successfully" not in r.stdout:
print("DSSE signature verification FAILED:", r.stdout, r.stderr, file=sys.stderr)
sys.exit(1)
print("DSSE signature: OK")
# Cross-check the signed manifestSha256 matches the on-disk manifest.json.
stmt = json.loads(payload)
claimed = stmt["predicate"]["manifestSha256"]
actual = hashlib.sha256(pathlib.Path("manifest.json").read_bytes()).hexdigest()
if claimed != actual:
print(f"manifestSha256 mismatch: signed={claimed} actual={actual}", file=sys.stderr)
sys.exit(1)
print(f"manifestSha256: OK ({actual})")
print(f"predicateType: {stmt['predicateType']}")
PY3. Verify every file’s SHA-256 against the (now-trusted) manifest
sha256sum --check <(jq -r '.files[] | "\(.sha256) \(.path)"' manifest.json)If all three steps pass, the bundle is end-to-end tamper-evident: every file matches the SHA-256 fremforge recorded, the manifest matches what fremforge signed, and the signature was produced by the published builder key.
What the signature proves
- The bundle bytes you received are byte-identical to what fremforge produced at export time.
- The manifest was produced by fremforge’s builder key, not by a third party.
- The signed predicate (
https://fremforge.eu/spec/export-bundle/v1) declares the org slug, export request id, file count, and total bytes, substitution of a different tenant’s bundle would not verify.
What it doesn’t prove
- Timeliness, the signed
exportedAtis fremforge-asserted; for third-party-witnessable timestamps, cross-reference the bundle’s audit-log slice against the public WORM-anchored audit chain. - Key revocation, the trust root JSON is the source of truth; if fremforge later revokes the builder key, old signatures stop verifying when the key disappears from
trusted_keys. Re-fetch the trust root if you’re verifying long after the export was issued.
Re-clone a repo from a bundle
Each repos/<slug>.bundle is a standard git bundle. To restore:
git clone <slug>.bundle <slug>
cd <slug>
git remote remove origin # bundle URL is local
git remote add origin <new-fremforge-or-elsewhere-url>
git push -u --mirror origin--mirror preserves all branches + tags + refs. The bundle reconstructs the full repo history, identical SHAs, no force-push required to any new remote.
LFS content
The <slug>.lfs-manifest.json file lists every LFS object in the repo with oid (object id), size, and path. The actual LFS file content is NOT in the bundle by default. Pulling 10 GB of binary attachments into every export would balloon size for everyone.
To download LFS content, after re-cloning the repo from the bundle:
git lfs install
git lfs fetch --allThis pulls from fremforge’s LFS server while you’re still a paying customer (LFS bandwidth is included). After cancellation we keep your data for 60 days; LFS pulls work during that window.
For an enterprise export that includes LFS bytes inline, contact support. We’ll size the bundle and may need to run the export as an OBS-to-OBS sync rather than a downloadable zip.
Cancellation and the export
If you cancel your fremforge subscription:
- Your last 7-day export window stays valid. The signed URL keeps working.
- A new export can be triggered any time during the 60-day post-cancellation read-only retention window. Same flow; same UI; same bundle.
- An export queued late in the 60-day window, even at +59d 23h, is honoured: fremforge holds physical deletion of your Forgejo org for up to 7 days past the 60-day mark whenever an export is queued or running. After the export completes, deletion proceeds on the next scheduled sweep.
- After 60 days (plus the export-grace window above), the data is purged per DPA §9; export availability ends with it.
If you’re cancelling specifically because you want a final export, do the export first, verify it locally, then cancel. The window is generous, but verifying-then-deleting is the rigorous flow.
Just one repository?
The full export is org-scoped, every repo, every issue, every artifact in one signed bundle. If you only need a single repository, the simpler paths are:
git clone --mirror, full Git history, branches, and tags as an offline-clonable bare repo. fremforge does not publicly expose port 22; use either the SSH-over-443 endpoint (ssh.frem.sh:443) or the HTTPS form:# SSH over port 443 (single endpoint for all orgs; uses your registered SSH key): git clone --mirror ssh://git@ssh.frem.sh:443/<org>/<repo>.git # OR HTTPS clone with a PAT (`ffp_…`) from Settings → API tokens: git clone --mirror "https://x-access-token:${FREMFORGE_PAT}@frem.sh/<org>/<repo>.git" cd <repo>.git git lfs fetch --all # if the repo uses LFSHost-key fingerprints for
ssh.frem.share published atwww.frem.sh/ssh-fingerprints, verify on first connection. The result is agit bundle-ready directory;tar -czfit for a portable single file.Repo download as zip/tarball,
frem.sh/<org>/<repo>/archive/<branch>.zipor.tar.gzfor a snapshot of a single ref. No history, no LFS, just the working tree at that branch.Per-repo issues + PRs JSON, via the API,
GET /api/v1/repos/<org>/<repo>/issues?state=all&type=issuesand?type=pulls. The migration guides show the call shape; the response is GitHub-API-compatible JSON.
The org-wide signed export is the right tool when you want everything (audit log slice, attestations, signed manifest); the per-repo paths above are right for the everyday “I need to send this one repo to someone” case.
Programmatic API
The same export is reachable via the REST API:
# Start a new export
curl -X POST \
-H "Authorization: Bearer <your-PAT>" \
https://frem.sh/_app/api/v1/orgs/<your-org>/data-export/full
# Poll for status
curl -H "Authorization: Bearer <your-PAT>" \
https://frem.sh/_app/api/v1/orgs/<your-org>/data-export/jobs/<job-id>
# When status=ready, follow the signed_url fieldOpenAPI spec: docs.frem.sh/reference/api/.
Privacy guarantees
The export is auditable both ways:
- Operator-side: every export job is audit-logged (operator console + the org’s own audit-log view) with
operator: <username>,tenant_id,job_id. fremverk staff cannot pull your bundle without leaving an audit trail visible to you. - Tenant-side: the audit-log slice in the bundle is redacted of cross-tenant identifiers, no other tenants’ slugs / user IDs / private data appear in your slice. This is enforced at the export-pipeline level, not at the audit-log-write level.
- Signed URL hygiene: the URL is emitted to logs only, never to UI elements that could be cached. After 7 days, both the OBS object and the URL are revoked.