Skip to main content

Pre-Deployment Agent Verification

Statically verify an AI agent's guardrails before it ships. skylos discover inventories every LLM integration in the tree; skylos defend runs 13 deterministic checks against each integration, scores the result, gates CI, and emits auditor-ready evidence with a reproducible attestation digest.

Runtime guardrails are the WAF; this is the SAST. Verification is AST-based, runs locally, and involves no model — the same tree with the same flags, policy, plugin set, and Skylos version produces the same verdict and digest.

Quick start

skylos discover .                               # what LLM integrations exist?
skylos defend . # are they guarded? (table)
skylos defend . --format md -o evidence.md # evidence report + attestation
skylos defend . --format sarif -o defend.sarif # GitHub code scanning
skylos defend . --json # machine-readable (schema 1.1)
skylos defend . --fail-on critical # CI gate

skylos defend exits 1 when the gate fails (--fail-on, --min-score, or policy gate.*), otherwise 0. Format never changes gating: a SARIF or md run with --fail-on high still writes the report and exits non-zero.

The 13 checks

Defense checks (weighted; drive the defense score and the gate):

CheckSeverity (weight)OWASP LLMVerifies
no-dangerous-sinkcritical (8)LLM02No eval/exec/subprocess sink in the LLM output scope
tool-scopecritical (8)LLM04Agent tools avoid dangerous calls
tool-schema-presentcritical (8)LLM04Every agent tool has a typed schema
untrusted-input-to-promptcritical (8)LLM01Untrusted input cannot reach prompts unguarded
prompt-delimiterhigh (5)LLM01Untrusted input is delimited inside prompts
output-validationhigh (5)LLM02Model output is validated before use
rag-context-isolationhigh (5)LLM01Retrieved context is isolated from instructions
output-pii-filterhigh (5)LLM06PII filtering guards model output
model-pinnedmedium (3)LLM03Model version is pinned, not floating
input-length-limitlow (1)LLM01Input length is limited before prompting

Ops checks (scored separately as the ops score; never gate CI and never inflate the defense score):

CheckSeverityOWASP LLMVerifies
logging-presentmediumLLM calls are logged
cost-controlsmediumLLM10max_tokens/cost limits are set
rate-limitingmediumLLM10LLM-facing input paths are rate limited

Scoring: defense score = passed weight / total weight, rated SECURE (≥90), LOW (≥75), MEDIUM (≥50), HIGH (≥25), CRITICAL (<25). Checks apply per integration only where relevant (applies_to), so a tool-less chat app is not penalized for agent-tool checks.

OWASP frameworks

Coverage is reported against OWASP LLM Top 10 2024 or 2025 (default) and the OWASP Agentic ASI Top 10 2026:

skylos defend . --owasp-framework agentic          # ASI01–ASI10
skylos defend . --owasp-framework llm --owasp-version 2024
skylos defend . --owasp LLM01,LLM04 # filter to specific risks

The md evidence report always includes both the selected framework and its counterpart, so one artifact answers both "LLM risks" and "agentic risks".

Output formats

--format table (default), --format json (alias: --json), --format md, --format sarif. -o/--output writes any format to a file.

  • json — schema 1.1: version, skylos_version, timestamp, project, summary (scores, by-severity), integrations[] (inventory + per-integration scores), findings[] (every check result with remediation), owasp_coverage, framework_evidence, ops_score, attestation.
  • md — the auditor evidence report: executive summary, integration inventory, per-check results, OWASP coverage (both frameworks), regulatory framework evidence, remediation appendix, attestation, methodology.
  • sarif — SARIF 2.1.0 with only failed defense checks (ops results and passes excluded); ready for GitHub code scanning upload. Rule IDs are the check ids above; the attestation rides in runs[0].properties.

Attestation

Every non-table report carries an attestation block:

"attestation": {
"algorithm": "sha256",
"digest": "9f2c…",
"generated_at": "2026-07-05T08:14:03+00:00",
"inputs": {
"files_hashed": 42,
"files_digest": "ab41…",
"policy_hash": null,
"plugin_set": ["cost-controls", "…"],
"owasp_framework": "llm",
"owasp_version": "2025",
"skylos_version": "4.27.0",
"results_digest": "31bb…",
"integrations_digest": "47e2…",
"run_evidence_digest": "ce90…"
}
}

The digest is SHA-256 over a canonical JSON document containing: the attestation schema tag, the Skylos version, the sorted list of scanned files (target-relative POSIX paths + per-file SHA-256), the policy file hash (or null), the sorted active plugin ids, the OWASP selection, the CLI filters, the integration inventory, defense/ops scores, selected OWASP coverage, framework evidence, and the sorted full check evidence (all public result fields including messages, remediation, weights, and OWASP ids). generated_at is not part of the digest input.

Consequences:

  • Re-running on an identical tree with identical flags, policy, plugin set, and Skylos version reproduces an identical digest — on any machine.
  • Any file edit, policy change, plugin-set change, or result change produces a different digest, so changed run evidence is detectable by re-running.

To re-verify a report: skylos defend <path> --format json on the same tree with the same flags and Skylos version; compare attestation.digest.

Regulatory framework evidence

JSON and md reports include framework_evidence: check results mapped to controls in the EU AI Act (Regulation (EU) 2024/1689) (Art. 12 record-keeping, Art. 15 accuracy/robustness/cybersecurity, Art. 15(5) resilience against AI-specific attacks), the NIST AI RMF 1.0 (trustworthiness characteristics), and ISO/IEC 42001:2023 (Annex A control themes).

The semantics are strictly "evidence toward" the referenced control — never a compliance determination, certification, or legal advice. Each report states this disclaimer, and the md report lists obligations static verification cannot address (e.g. EU AI Act Art. 14 human oversight, organizational risk-management processes). Passing every check does not make a system compliant with anything; it gives an auditor deterministic, reproducible evidence about the state of the agent's code-level guardrails at a point in time.

Policy as code

# skylos-defend.yaml (loaded only via --policy, never auto-discovered)
rules:
output-pii-filter:
severity: critical # raise severity for your context
input-length-limit:
enabled: false # disable a check
gate:
min_score: 75 # exit 1 below this defense score
fail_on: high # exit 1 on any failed check at/above this severity
skylos defend . --policy skylos-defend.yaml

The policy file's hash is part of the attestation digest, so evidence is bound to the exact policy it was scored against.

CI and pre-commit

skylos cicd init --defend        # generate a workflow with a defend step

Pre-commit hook (ships with the repo's .pre-commit-hooks.yaml):

repos:
- repo: https://github.com/duriantaco/skylos
rev: <version>
hooks:
- id: skylos-defend # runs: skylos defend . --fail-on critical

GitHub code scanning:

- run: skylos defend . --format sarif -o defend.sarif --fail-on critical
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: defend.sarif

Inside GitHub Actions, skylos defend automatically appends a score summary (failed checks, OWASP coverage, gate result) to the job's step summary via $GITHUB_STEP_SUMMARY — no extra flags.

MCP: agents verifying agents

The Skylos MCP server exposes verify_agent, so Claude Code, Cursor, and other MCP clients can verify an agent codebase they just wrote or modified:

verify_agent(path=".", fail_on="critical", owasp_framework="agentic")

Returns compact JSON: defense_score, ops_score, failed_checks[] (with remediation), OWASP coverage summary, the attestation digest, and the gate verdict. The verification itself is deterministic static analysis — the calling agent gets a ground-truth verdict, not another model's opinion.

Scope and honesty

What this verifies: the presence and placement of guardrail code patterns at LLM integration points, before deployment, deterministically.

What it does not do: observe runtime behavior, evaluate prompt quality, catch semantic failures (an agent doing an allowed-but-wrong action), or replace runtime controls. Pair pre-deployment verification with runtime measures (gateways, policy engines, human approval for high-stakes actions) — the two layers catch different failure classes.

To check a running agent's final responses, selected tools, explicit refusals, and source evidence against a checked-in contract, use Agent Behavior Testing.