Skip to content

The .froga/* artefacts

✅ Stable

The .froga/ directory in the repository holds the signed evidence artefacts written by the engine when the corresponding subcommands are run. Each artefact is a JSON file inside a versioned envelope (FrogaArtifact<T>) accompanied by a signature file (.sig). The cloud platform reads these files from the repository and verifies them before rendering any report.


All .froga/* artefacts follow the same envelope contract defined in crates/froga-core/src/froga_artifact.rs. The envelope is a versioned wrapper with deterministic serialisation (no timestamps, stable field ordering):

FrogaArtifact<T> envelope shape
{
"schema_version": 1,
"kind": "conformance",
"system": "loan",
"payload": { ... }
}
FieldRust typeDescription
schema_versionu32Incremental schema version of the payload (per artefact type). Currently 1 for all types.
kindStringArtefact type: "conformance" or "reconstruct".
systemStringName of the system the artefact belongs to (human-readable anchor; the technical anchor is the external git reference).
payloadTArtefact-specific content (varies by kind).

Serialisation is deterministic (pretty JSON, declaration order of fields) — the same bytes are signed and stored. There are no timestamps in the envelope; temporal traceability is provided by git history.


Every <name>.json artefact has a sibling file <name>.json.sig. The signature follows the same scheme as the main bundle:

  • Algorithm: ECDSA over the NIST P-256 curve with SHA-256, deterministic signatures (RFC 6979); signature in raw r‖s form (64 bytes) inside a DSSE envelope (Dead Simple Signing Envelope)
  • Statement: in-toto Statement v1 (type https://in-toto.io/Statement/v1)
  • Canonical payload: JCS (RFC 8785) for byte-for-byte determinism
  • Subject: SHA-256 of the artefact bytes — links signature and content
  • keyid: hex(SHA-256(uncompressed SEC1 pubkey, 65 B)) — identifies the verifying key without transporting it

The signing backend is pluggable (environment variable FROGA_SIGNING_BACKEND): local (32-byte EC P-256 scalar in hex in FROGA_SIGNING_KEY; without it, a fixed development scalar, integrity only, no non-repudiation) or scaleway-kms (signing delegated to the Scaleway Key Manager, alg EC-P256-SHA256; the private key never leaves the KMS). froga pubkey exports the verifying pubkey (SEC1, 65 B hex) to register it in the cloud per connection (ScmConnection.signerPubkey).

See Sign and verify evidence for the full flow and the current state of the trust chain.


.froga/ fileSignatureCommand that writes itRequired flagkind
bundle.jsonbundle.json.sigfroga run(always)(bundle, not FrogaArtifact)
reconstruct.jsonreconstruct.json.sigfroga reconstruct--out"reconstruct"
conformance/<slug>.jsonconformance/<slug>.json.sigfroga conformance--out"conformance"
risk-projection/<slug>.jsonrisk-projection/<slug>.json.sigfroga risk-projection(always, on demand)"risk-projection"

bundle.json does not use the FrogaArtifact<T> envelope: it is a directly signed EvidenceBundle. It is the central artefact that the others reference. Its signature is verified with froga verify.

The conformance file slug is generated from the standard id by replacing / and @ with _:

eu/pren-18228@2026 → .froga/conformance/eu_pren-18228_2026.json
iso/23894@2023 → .froga/conformance/iso_23894_2023.json

This transformation is performed by the standard_slug function in froga_artifact.rs.

The risk-projection file slug is derived from the framework id in the same way:

iso/14971@2019 → .froga/risk-projection/iso_14971_2019.json
eu/pren-18228@2026 → .froga/risk-projection/eu_pren-18228_2026.json
iso/23894@2023 → .froga/risk-projection/iso_23894_2023.json

The write_froga_artifact function in crates/froga-cli/src/util/artifact.rs follows this pattern for all artefacts:

Write pattern (from util/artifact.rs)
// 1. Build the envelope
let art = FrogaArtifact::new(1, "conformance", &system, payload);
// 2. Serialise to deterministic JSON
let json = artifact.to_json()?;
// 3. Sign the bytes (ECDSA-P256; the backend is chosen by FROGA_SIGNING_BACKEND
// internally via signer_from_env: LocalSigner or ScalewayKmsSigner)
let proof = EcdsaDsseSigner.sign(json.as_bytes())?;
// 4. Write <rel>.json and <rel>.json.sig
std::fs::write(&path, &json)?;
std::fs::write(sig_path, &proof)?;

Subdirectories are created automatically (e.g. .froga/conformance/ for conformance reports).


The cloud platform (TypeScript, cloud/lib/froga/) reproduces the froga verify check without calling the CLI:

  1. Reads the artefact bytes (.json) and signature (.sig).
  2. Parses the DSSE envelope and in-toto Statement from the .sig file.
  3. Verifies the ECDSA-P256 signature (raw r‖s) over the PAE (DSSE Pre-Authentication Encoding) with @noble/curves.
  4. Checks that the envelope keyid matches hex(SHA-256(SEC1 pubkey)) of the expected pubkey for THAT connection (ScmConnection.signerPubkey), in a fail-loud manner: with no registered pubkey, the cloud does not verify silently but fails.
  5. Verifies that the Statement subject.sha256 matches the SHA-256 of the artefact bytes.

The cloud does not compute evidence: it only reads and verifies what the engine has committed. The git repository is always the source of truth.

Verify the bundle signature from the CLI
froga verify --repo /path/to/project

froga compile → generates oscal.assessment_plan (not a FrogaArtifact)
froga run → writes .froga/bundle.json + bundle.json.sig
froga reconstruct --out → writes .froga/reconstruct.json + .sig
froga conformance --out → writes .froga/conformance/<slug>.json + .sig
froga risk-projection → writes .froga/risk-projection/<slug>.json + .sig
[git commit -A .froga/] → artefacts are versioned and traceable

Artefacts are not committed automatically; the commit is a deliberate human act that constitutes the “placing on the market” of the conformance state.

See also: froga CLI Reference, Sign and verify evidence.