Anatomy of an Immutable Audit Log: How We Use OIDC & Sigstore

Most 'AI Auditing' tools just save text files to a database. Here is how we cryptographically bind an ephemeral GitHub Action identity to a specific decision using Keyless Signing.

Posted on January 20, 2025 by Cabin crew team

Anatomy of an Immutable Audit Log: How We Use OIDC & Sigstore

Anatomy of an Immutable Audit Log: How We Use OIDC & Sigstore

Most “AI Auditing” tools just save text files to a database. Here is how we cryptographically bind an ephemeral GitHub Action identity to a specific decision using Keyless Signing.

The Problem with API Keys

Traditional CI/CD systems rely on long-lived API keys or service account tokens. These have fundamental problems:

1. They Leak

API keys stored in environment variables, config files, or secret managers eventually get exposed. GitHub’s secret scanning finds thousands of leaked keys every day.

2. They’re Persistent

Once compromised, an API key remains valid until manually rotated. An attacker who steals a key has unlimited time to use it.

3. They’re Shared

The same API key is often used across multiple workflows, making it impossible to attribute a specific action to a specific run.

4. They’re Mutable

Logs signed with API keys can be forged if the key is compromised. There’s no way to prove a log entry was created at a specific time by a specific identity.

The OIDC Revolution

OpenID Connect (OIDC) solves this by replacing long-lived keys with short-lived, cryptographically verifiable identity tokens.

Here’s how it works in the context of GitHub Actions:

Step 1: Request an Identity Token

When a GitHub Action runs, it can request an OIDC token from GitHub’s identity provider:

curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
  "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sigstore" \
  | jq -r '.value'

This token contains:

  • Issuer: https://token.actions.githubusercontent.com
  • Subject: repo:cabincrew/dev-engine:ref:refs/heads/main
  • Audience: sigstore
  • Expiration: 5 minutes from issuance
  • Claims: Workflow name, run ID, commit SHA, actor

Step 2: Exchange Token for Certificate

The Cabin Crew Orchestrator sends this OIDC token to Sigstore’s Fulcio (a certificate authority):

POST https://fulcio.sigstore.dev/api/v2/signingCert
{
  "credentials": {
    "oidcIdentityToken": "eyJhbGc..."
  }
}

Fulcio validates the token and issues a short-lived X.509 certificate (valid for 10 minutes). This certificate includes:

  • The GitHub repository and workflow identity
  • The exact commit SHA
  • The timestamp of issuance
  • A public key (generated ephemerally by the Orchestrator)

Step 3: Sign the Audit Log

The Orchestrator uses the private key (which exists only in memory) to sign the audit.json file:

{
  "workflow_id": "run-12345",
  "timestamp": "2025-01-15T10:30:00Z",
  "artifacts": [
    {
      "name": "changes.patch",
      "hash": "sha256:a1b2c3d4...",
      "role": "evidence"
    }
  ],
  "policy_verdict": {
    "status": "pass",
    "checks": ["no-secrets", "cost-limit"]
  },
  "signature": {
    "algorithm": "ECDSA-SHA256",
    "value": "MEUCIQDx..."
  }
}

Step 4: Publish to Transparency Log

The signature and certificate are published to Rekor (Sigstore’s transparency log). This creates an immutable, timestamped record that:

  • The certificate was issued at time T
  • The audit log was signed with that certificate
  • The signature is cryptographically valid

Hash Chaining: Preventing Log Deletion

Even with cryptographic signatures, an attacker who gains access to your storage backend could delete audit logs. To prevent this, we use hash chaining.

Each audit log includes the hash of the previous log:

{
  "log_id": "log-456",
  "previous_hash": "sha256:log-455-hash",
  "artifacts": [...],
  "signature": {...}
}

This creates a blockchain-like structure. If any log in the chain is deleted or modified, the chain breaks, and verification fails.

Verification Process

To verify the integrity of the audit trail:

  1. Start with the most recent log
  2. Verify its signature using the Sigstore certificate
  3. Check that previous_hash matches the hash of log N-1
  4. Repeat for all logs in the chain

If any log is missing or tampered with, the verification fails.

The Audit Log Schema

Here’s a real example of what our audit.json looks like:

{
  "version": "1.0",
  "workflow": {
    "id": "run-98765",
    "repository": "cabincrew/demo-app",
    "ref": "refs/heads/main",
    "commit": "a1b2c3d4e5f6",
    "actor": "github-actions[bot]"
  },
  "timestamp": "2025-01-15T14:22:33Z",
  "orchestrator": {
    "version": "0.5.2",
    "mode": "governed"
  },
  "inputs": {
    "issue_number": 42,
    "task": "Add user authentication"
  },
  "artifacts": [
    {
      "role": "evidence",
      "name": "auth-implementation.patch",
      "path": "./artifacts/auth.patch",
      "hash": "sha256:7f8e9d0c1b2a3456...",
      "size_bytes": 4521
    },
    {
      "role": "state",
      "name": "planner-state.json",
      "path": "./artifacts/state.json",
      "hash": "sha256:3c4d5e6f7a8b9012..."
    }
  ],
  "policy": {
    "engine": "opa",
    "version": "0.58.0",
    "verdict": "pass",
    "checks": [
      {
        "name": "no-high-entropy-secrets",
        "status": "pass"
      },
      {
        "name": "require-jira-ticket",
        "status": "pass",
        "metadata": {"ticket": "PROJ-123"}
      }
    ]
  },
  "signature": {
    "algorithm": "ECDSA-SHA256",
    "certificate": "-----BEGIN CERTIFICATE-----\nMIIC...",
    "value": "MEUCIQDxY7...",
    "transparency_log_entry": "https://rekor.sigstore.dev/api/v1/log/entries/24a8c..."
  },
  "previous_hash": "sha256:log-98764-hash"
}

Why This Matters

This architecture provides non-repudiation. You can prove:

  1. Who: The exact GitHub workflow and commit that generated the decision
  2. What: The specific artifacts that were created (via hash)
  3. When: The precise timestamp (via Sigstore’s transparency log)
  4. Why: The policy verdict that approved the action

And you can prove all of this years later, even if:

  • The GitHub Action has been deleted
  • The repository has been archived
  • The API keys have been rotated
  • The engineer who ran the workflow has left the company

Comparison to Traditional Logging

Feature Traditional Logs Cabin Crew Audit Logs
Identity API Key (long-lived) OIDC Token (5 min)
Signature HMAC (shared secret) ECDSA (public key)
Timestamp Server clock (mutable) Transparency log (immutable)
Deletion Protection None Hash chaining
Verification Requires original key Public certificate

Try It Yourself

The Cabin Crew Orchestrator is open source (BSL 1.1). You can inspect the signing logic in our GitHub repository:

Or run a demo workflow to see the audit logs generated in real-time.


This is how you build trust in AI systems. Not with promises. With mathematics.


Want to learn more about our platform? Check out The Black Box or read the Cabin Crew Protocol.

End of Transmission

Questions? Insights? Join the crew in the briefing room.

Discuss n Github

Further Intelligence