# CocoaSkills RFC 0008: Audit Registry and Chain of Trust

Status: draft
Date: 2026-07-07
Author: Ivan Oparin
Target: v0.11.0
Russian translation: [v0.11-design.ru.md](v0.11-design.ru.md)

An audit verdict is only useful when a machine can obtain it, verify who issued
it, and confirm that it covers the exact bytes about to be installed. This RFC
defines a signed audit record, a registry protocol that serves those records, a
trust model rooted in out-of-band pinned keys, and the client configuration
that lets an organization point CocoaSkills at its own registry.

## 1. Overview

- An audit record is a signed statement that a named skill, at a specific
  commit and content hash, holds a given audit status. Records are signed with
  Ed25519.
- A registry serves records over a small HTTP API and maintains an append-only
  transparency log with a signed snapshot.
- Trust is rooted out of band: a client pins each registry's public keys in its
  configuration. The transport carries data, the signatures carry trust.
- The client configuration accepts an array of trusted registries. A built-in
  default points at the public central registry; an organization can disable it
  and pin only its internal registry.
- At install time the client resolves each skill against its trusted registries
  and applies a policy. Federation is deny-wins: a revocation in any trusted
  registry blocks the skill.
- The install marker records the attestation that authorized the install, so
  the decision is auditable offline.

## 2. Motivating case

A regulated organization runs CocoaSkills across hundreds of machines and
allows skill installs only from sources its own security team has audited. It
cannot depend on a public service and cannot let a developer's local
configuration widen the trust boundary. It needs three things: a registry
inside its own network that records which commits its auditors approved, a way
to pin trust to that registry alone, and a machine configuration a developer
cannot override. This RFC defines the first two; the enforced machine
configuration arrives in a companion change (section 10).

## 3. Audit record

A record is a JSON object with a canonical serialization for signing. The
signature covers every field except `sig`.

```json
{
  "schema_version": 1,
  "name": "skill-tracker",
  "source_identity": "gitlab.example.com/skills/skill-tracker",
  "commit": "8c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d",
  "content_sha256": "sha256:1f2e3d...",
  "status": "audited",
  "audit": {
    "ruleset_version": "csk-audit/1",
    "findings_summary": { "critical": 0, "high": 0, "medium": 1, "low": 3 },
    "capabilities_hash": "sha256:aa11bb...",
    "scope": "all",
    "auditor": { "org": "Example Security", "contact": "security@example.com" },
    "audited_at": "2026-07-01T10:00:00Z",
    "expires_at": "2027-07-01T10:00:00Z"
  },
  "sig": { "key_id": "a1b2c3d4", "algorithm": "ed25519", "signature": "base64..." }
}
```

Field rules:

- `name`, `source_identity`, `commit` and `content_sha256` identify the audited
  artifact. `source_identity` is the canonical `host/path` form from RFC 0007,
  `commit` is a full 40-character hex, and `content_sha256` is the install
  content hash CocoaSkills already computes.
- `status` is one of `audited`, `revoked`, `deprecated`, or `pending`.
  `audited` authorizes installs, `revoked` blocks them, `deprecated` warns and
  allows, `pending` carries no verdict.
- `audit.ruleset_version` names the detector ruleset that produced the verdict,
  so a client can tell a stale audit from a current one.
- `audit.capabilities_hash` binds the verdict to the declared capability
  envelope. A skill that changes its declared capabilities without a new commit
  is impossible, and the hash lets a client confirm the approved envelope.
- `audit.scope` is `all` when the whole skill is approved, or a value that
  names the approved subset when an organization approves specific commands
  only.
- `audit.expires_at` is optional. A client treats an expired `audited` record
  as `pending`.
- `sig.key_id` selects the signing key among the registry's published keys.

## 4. Registry API

A registry serves the following endpoints over HTTPS. Responses are JSON.

- `GET /v1/meta` returns the registry name, its published signing keys, the
  record schema versions it serves, and its stated policy.
- `GET /v1/records?source_identity=&commit=` returns the record for one
  artifact, or an empty result. `GET /v1/records?content_sha256=` resolves by
  content hash.
- `GET /v1/snapshot` returns a signed snapshot: a Merkle root over all records,
  the log size, a monotonically increasing version, and a timestamp. A client
  uses the snapshot to detect a registry that withholds newer records or serves
  a rolled-back view.
- `GET /v1/log?since=` returns transparency log entries, each carrying a record
  and its position in the hash chain.
- `POST /v1/records` submits a signed record. The endpoint requires an auditor
  token and verifies the record signature against a registered auditor key.
- `GET /health` returns liveness with no authentication.

Records and snapshots are signed at rest. A client never trusts a field because
the transport delivered it; it verifies the signature against a pinned key
first.

## 5. Trust model

Trust is rooted in keys the client pins out of band. A registry's keys are
never bootstrapped from the registry itself.

- Each trusted registry entry in the client configuration lists one or more
  public keys. The client accepts a record only when its `sig` verifies against
  one of those keys.
- The snapshot defends freshness. A client stores the highest snapshot version
  it has seen per registry and refuses a snapshot whose version moved backward,
  which detects rollback. A snapshot older than a configured maximum age is
  stale, and the client treats lookups against it as unavailable.
- The transparency log makes issuance auditable. Every record enters an
  append-only, hash-chained log, so an auditor can verify that the set of
  records a registry serves matches the set it has ever logged.

This is a reduced profile of The Update Framework: pinned roots plus a signed
snapshot and timestamp, without delegated roles. A full delegation hierarchy is
future work (section 14).

## 6. Client configuration

The machine configuration gains an `audit_registries` array.

```json
{
  "audit_registries": [
    {
      "name": "central",
      "url": "https://registry.cocoaskills.org",
      "public_keys": ["ed25519:base64key..."],
      "enabled": true
    }
  ],
  "disable_builtin_registries": false
}
```

- A CocoaSkills release ships one built-in registry entry: the public central
  registry with its public key compiled into the release. The built-in entry is
  merged with the configured entries.
- `disable_builtin_registries: true` removes the built-in entry, which suits a
  closed network that trusts only its internal registry.
- An entry with the same `url` as the built-in entry overrides it, so an
  organization can re-add the central registry under its own explicit terms
  after disabling the defaults.
- `enabled: false` keeps an entry documented but inactive.

Registry keys are configuration, not data. They are distributed with the same
tooling that distributes the rest of the machine configuration and are pinned
before the first lookup.

## 7. Federation and precedence

A skill may resolve against several trusted registries. The client combines the
results with a deny-wins rule:

- If any trusted registry returns `revoked` for the artifact, the skill is
  denied. Revocation is the strongest signal and one registry is enough to
  trigger it.
- Otherwise, if any trusted registry returns a valid `audited` record, the
  skill is authorized.
- Otherwise the artifact is unknown, and the configured policy decides: advisory
  policy warns and continues, strict policy fails the install unless the content
  hash is pinned through the existing trust workflow.

Most-specific configuration wins for policy, and deny always wins for status.

## 8. Resolution at install time

The audit gate gains a registry step, run against the same content snapshot the
install pipeline already produced.

1. Compute the content hash of the skill snapshot, which the pipeline already
   does.
2. Refresh each trusted registry's snapshot, subject to the freshness rules. A
   registry that cannot be reached uses the last cached snapshot within the
   offline grace window, with a warning.
3. Look up the artifact by `source_identity` and `commit`, and by
   `content_sha256`, in each trusted registry. Verify every returned record
   against the registry's pinned keys.
4. Apply the federation rule to reach a status, then apply policy.
5. Record the authorizing attestation in the install marker: the registry name,
   the record, and the verifying key id.

`csk status --attest` re-runs steps 2 through 4 against the installed markers,
so a revocation issued after install surfaces on the next status check.

## 9. Transparency log

Each accepted record appends to a hash-chained log. Entry N carries the record
and a hash over the previous entry hash and the canonical record bytes. The
signed snapshot commits to the log head. Two properties follow:

- Tamper evidence. A registry cannot alter or drop a past record without
  breaking the chain, which a client or auditor detects by recomputing the head
  and comparing it to a snapshot it retained.
- Non-equivocation. A registry that serves different views to different clients
  produces snapshots that cannot both extend one chain, which cross-checking
  snapshots reveals.

## 10. Enterprise operations

- Enforced machine configuration. A companion change adds a system
  configuration layer, read before the user configuration, whose selected keys
  cannot be overridden from user configuration. An organization pins
  `audit_registries` and the audit policy there and distributes it through
  device management, so a developer cannot widen the trust boundary. This layer
  is specified with its own change and composes with configuration signing from
  RFC 0007 section 10.
- Air-gapped import. A registry can export a signed bundle of records, and an
  internal registry can import it, so an offline network mirrors an upstream
  audit set without a live connection.
- Auditor access. Submission requires an auditor token bound to a registered
  key, so the set of parties that can issue records is controlled and
  attributable.
- Dual signature. A record may carry more than one signature when an
  organization requires two auditors to approve a skill.

## 11. Security boundary

- Keys are pinned out of band. A registry cannot enlarge its own authority,
  because a client verifies against configured keys and never against keys the
  registry serves.
- The signature binds the verdict to `content_sha256`, so an approval covers
  those exact bytes and a new commit inherits nothing.
- Deny-wins federation means a single revoking registry protects every machine
  that trusts it, even when another registry still serves an `audited` record.
- The snapshot and log defend against a registry that withholds revocations or
  rolls back its view.
- The registry decides nothing about installs on its own. It serves signed
  statements, and the client combines them under a local, deterministic policy.

## 12. Failure catalog

Resolution surfaces a specific outcome when:

- a record signature does not verify against any pinned key for its registry:
  the record is ignored and a warning is emitted;
- a snapshot version moved backward, or a snapshot is older than the maximum
  age: the registry is treated as unavailable;
- a trusted registry cannot be reached and no cached snapshot is within the
  grace window: the registry is unavailable, and strict policy fails the
  install;
- the artifact is unknown to every trusted registry: policy decides;
- any trusted registry returns `revoked`: the install is denied.

## 13. Compatibility

- Existing installs are unaffected. With no `audit_registries` configured and
  the built-in default enabled, lookups are advisory, so a machine that never
  configured a registry installs as before.
- The registry step is additive to the audit gate from RFC 0005 and RFC 0006.
  Static detectors and local backends run as they do today; the registry adds a
  federated verdict on top.
- Install markers gain an optional attestation section. Older markers without
  it remain valid.

## 14. Future work

- Delegated signing roles, moving from pinned roots to a full Update Framework
  delegation hierarchy.
- Automated re-audit on ruleset upgrades, so a new `ruleset_version` schedules
  fresh verdicts for affected artifacts.
- Publication of attestations to a public transparency service for
  third-party monitoring.
- A verifiable link between a registry record and the audit run that produced
  it, so a consumer can replay the detectors that reached the verdict.
