RegistryDashboard

Definition Safety

Publish-time risk analysis for registry definitions — sync scanning, deep analysis, and the trust contract consumers must honor.

Overview

Every definition published to the registry is scanned for evidence of misuse. The result is a risk profile stamped on the published version — a versioned, immutable record of what the analyzers observed in that exact content. Consumers (the CLI, the registry app, and SDK clients) render it before you run someone else's agent.

The system makes one deliberate trade you should understand up front: scanning informs, it does not block. A publish always succeeds regardless of verdict (the scanner is non-fatal by design), and a flagged definition remains fetchable. The registry's position is that risk information belongs in front of the person about to execute a definition, not silently enforced at the gate — enforcement thresholds would train authors to optimize against the scanner rather than surface honest signals.

Two Analysis Layers

LayerWhen it runsWhat it is
Sync scanDuring the publish request (< 400 ms budget)Deterministic pattern analysis: capability extraction plus signal detectors over the parsed YAML
Deep analysisAsynchronously after publish (public definitions)An LLM auditor (definition-safety-auditor) that reads the full definition semantically as untrusted data

The sync scan is a fast floor, not the whole defense. Its injection detection is English-only and pattern-based — a documented limitation, not a bug — so paraphrased or non-English injection evades it by design and is caught by the language-agnostic deep analyzer instead.

Risk Levels

Three levels: none, medium, high. There is deliberately no low tier — a signal must always mean something, and a "low" bucket trains consumers to ignore signals.

Risk is derived from evidence of misuse only — never from capability count, tool declarations, or author provenance. A definition declaring [bash, write, network] with zero signals scores none; capabilities are surfaced separately as neutral transparency metadata for the consumer to weigh. This is a design principle, not an oversight: scoring capability as risk would flag every legitimate shell-capable agent and destroy signal trust.

Signals found in teaching sections of an agent (knowledge bases, calibration examples, rendered example outputs — where security agents legitimately quote attack patterns) are downgraded, never suppressed: a high-severity match in a teaching section surfaces at medium rather than escalating the whole definition to high. Injection-language detectors are global and never section-scoped — injection delivered as "example data" is a real attack surface.

The Risk Profile

jsonc
{
  "sync": {
    "version": "0.3.0",           // analyzer version that produced this scan
    "scannedAt": "…",
    "capabilities": { "tools": ["bash", "read"], "preflightCommands": 0 },
    "signals": [ /* { id, severity: 'medium'|'high', title, detail, location } */ ],
    "riskLevel": "none"
  },
  "deep": {                        // null until the async analyzer runs
    "version": "0.1.0",
    "analyzedAt": "…",
    "findings": [ /* categorized: injection, exfiltration, escalation, … */ ],
    "riskLevel": "none",
    "status": "analyzed"           // or "error" — see the trust contract below
  },
  "aggregateRiskLevel": "none",    // max(sync, deep)
  "scanStatus": "complete",        // or "failed" — see the trust contract below
  "lastUpdated": "…"
}

Profiles are version-scoped: they describe one version's content under the analyzer that scanned it. When the analyzer's detectors change, previously scanned versions are re-scanned (backfilled) so an old none does not silently mean "clean under rules that no longer exist."

The Trust Contract

This is the part every consumer must get right, and the part that has historically gone wrong twice.

aggregateRiskLevel: 'none' is only a verdict when analysis actually completed. Two sentinel states carry 'none' while meaning "could not determine":

  1. scanStatus: 'failed' — the sync scan aborted (parse error, timeout, internal error). There are no signals because nothing was scanned.
  2. deep.status: 'error' — the deep auditor ran but its output could not be extracted. The aggregate deliberately stays at the sync level, so a sync-clean definition whose deep audit crashed reads 'none' on every headline field.

Reading aggregateRiskLevel directly renders both failure states as "clean." The second case is the sharper one: an adversary who evades the sync regex only needs to make the deep auditor error — not defeat it — to launder a hostile definition into a green rendering.

The predicate that encodes the contract is exported by @uluops/registry-sdk (0.43.0+):

typescript
import { isVerdictTrustworthy } from '@uluops/registry-sdk';

if (!isVerdictTrustworthy(def.riskProfile)) {
  // Never scanned, sync scan failed, or deep analysis errored.
  // Render "analysis incomplete"never "no risk signals".
}

deep: null (a definition whose deep analysis is pending, skipped for private definitions, or predates the deep analyzer) stays trustworthy — "not yet analyzed" is an incompleteness, not an error, and flipping it to untrusted would mark every freshly published definition suspicious until the async worker caught up.

Where You See It

  • CLIulu def get renders signals, capabilities, and analysis status; a failed or deep-errored analysis prints "could not determine", never "No risk signals." ulu exec agent prints a pre-run warning for flagged definitions and an "analysis incomplete / verdict is sync-only" advisory for unanalyzed ones (--no-safety-warnings suppresses; nothing blocks).
  • Registry app — risk badges on definition cards, a Safety Analysis card on the detail page (with explicit incomplete states), and search deprioritization of flagged definitions.
  • SDKsriskProfile on Definition (registry-sdk) and ResolvedDefinition (core), with the sentinel fields typed and the isVerdictTrustworthy predicate exported.

Limitations, Stated Plainly

  • The sync scan's injection patterns are English-only and exact-phrase; the deep analyzer is the compensating control for paraphrase and other languages.
  • The deep auditor is itself an LLM reading hostile input. It runs with zero execution surface and treats manipulation attempts as findings, and a failed or manipulated run surfaces as deep.status: 'error' (untrusted) rather than a fabricated CLEAN.
  • Publishing is not gated on the verdict. If your threat model requires blocking, gate on isVerdictTrustworthy + aggregateRiskLevel at your point of consumption — the CLI's integrity pins (--hash) compose with this for CI use.