Govern Skills
For how skill risk scoring works conceptually — the two risk planes, fidelity tiers, and fail-open vs. fail-closed postures — see Skill Risk. This page is the how-to: viewing the inventory, reading a skill's detail, submitting a bundle for a full scan, tuning weights, and writing policy against the result.
View the skill inventory
Every skill any connected agent has invoked appears automatically — no setup required.
- Open Insights from the left navigation.
- Switch to the Resources view.
- Filter the resource type to Skills.
The list shows every skill by name and content digest, with its source (prompt, uploaded, marketplace, builtin), risk band, risk score, and derived trust tier. Sort or filter by band to triage — start with anything in Blocked, then Review. Hover any skill's name for its declared description (from SKILL.md frontmatter) as a quick reference without opening the row.
A row can show a pending risk score instead of a band: the skill's content is queued for scanning and hasn't completed yet. This normally clears itself once the scan sweep catches up. If it never clears — a rare case where the observation carried no scannable content at all (a rate-limited capture, or a bare slash command with nothing to scan) — see Submit a skill bundle for full scanning and Archive a skill you don't need to act on below for the two ways to resolve it.
By default the list only shows active skills. Toggle Show archived to also see rows an admin has archived — see Archive a skill you don't need to act on.
A skill whose content has changed since it was last seen — a new content digest — doesn't create an unrelated duplicate entry. It's grouped under the same name as one expandable row, tagged with its version count; expand it to see each digest listed separately with its own independent risk band, call count, and last-seen. This is intentional: it's what lets you tell a skill whose content changed from one that's identical to a previously-reviewed version, without the inventory reading as accidental duplication.
Prefer to query the inventory from a script rather than the UI? GET /api/v1/skills supports the same sort/band/source/q filters plus real server-side pagination (page/pageSize):
curl "https://YOUR_WORKSPACE_URL/api/v1/skills?band=blocked&sort=risk" \
-H "Authorization: Bearer $ADMIN_TOKEN"
Read a skill's risk detail
Click any row to open the detail drawer. It has four sections:
- Indicators — every fired indicator, both static and behavioral, each with its severity (static) or weight (behavioral), the message describing what was detected, and evidence — the exact file, line range, and snippet that triggered it. See Skill Indicators for the full catalog.
- Observed behavior — connection graph — the tools invoked and hosts contacted while this skill was active, aggregated over the current window.
- Drift vs. baseline — populated only when
BEHAV_DRIFThas fired: which tools or hosts are new relative to this digest's established baseline. - Scope check — if the skill declares an
allowed-toolslist in its frontmatter, whether observed tool use has stayed inside that declared scope.
A Rescan button re-runs the static scan against the skill's current stored content immediately, rather than waiting for the next scheduled sweep — useful right after you've changed risk weights and want to see a specific skill's score update. It only works when the row has stored content; a row that's pending with nothing to rescan shows Upload content instead (see below). A Generate policy button pre-fills a CEL snippet keyed to that skill's current riskBand, as a starting point for the policy recipes further down this page. Archive hides the row from the active queue without deleting it — see Archive a skill you don't need to act on.
Submit a skill bundle for full scanning
Skills observed only from prompt extraction get prompt fidelity — the SKILL.md body only. To scan a skill's full package (scripts, references, dependency manifests included) before it ever reaches an agent, submit it through the ingest API. This is the recommended gate for a marketplace publish step or a CI pipeline.
The same endpoint is available from a skill's detail drawer as an Upload content button, shown only when the row has no stored content to scan (the pending-forever case noted above). Drag in the skill's files there for the same synchronous verdict shown below — it registers under the content's own digest, as a new row, rather than overwriting the one you opened the drawer from.
Requires an admin token (a personal access token belonging to an admin, or an admin session). Requests need a name, a source (marketplace or uploaded — builtin is rejected here, since that source is reserved for vendor-provenance skills configured separately), and the bundle's files, base64-encoded:
curl -X POST https://YOUR_WORKSPACE_URL/api/v1/skills/ingest \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "release-notes-generator",
"source": "marketplace",
"files": [
{
"path": "SKILL.md",
"contentBase64": "LS0tCm5hbWU6IHJlbGVhc2Utbm90ZXMtZ2VuZXJhdG9yCi0tLQo=",
"executable": false
},
{
"path": "scripts/format.sh",
"contentBase64": "IyEvYmluL2Jhc2gKc2V0IC1ldW8gcGlwZWZhaWwK",
"executable": true
}
]
}'
The scan runs synchronously and the response carries the verdict immediately:
{
"digest": "sha256:8f2a1c...",
"riskScore": 15,
"riskBand": "trusted",
"indicators": [
{
"id": "DEPS_UNPINNED",
"severity": "medium",
"message": "Unpinned dependencies detected (version ranges, *, or 'latest')",
"evidence": [
{ "path": "requirements.txt", "lines": "3-3", "snippet": "requests>=2.0" }
]
}
]
}
Gate your publish step on the response: fail the pipeline (or hold the publish) when riskBand comes back blocked, and consider the same for review if your team wants a human sign-off before anything new reaches the marketplace.
A submission is capped at 100 files and 5 MB total, and every path is validated against directory-traversal (a path attempting to escape the bundle root is rejected) before scanning.
Archive a skill you don't need to act on
Not every row needs a decision — a one-off observation from an agent nobody uses anymore, or a stuck pending row you've decided isn't worth chasing content down for. Archive, in the detail drawer, removes it from the active Skills list and from the summary counts, without deleting anything: the row, its digest, and its risk data all stay exactly as they are.
Archiving is purely a triage convenience — it never affects enforcement. A policy gating on object.skill.riskBand (or any other object.skill.* field) evaluates an archived skill exactly the same as before archiving it: if it runs again, it's scored and enforced identically.
Toggle Show archived on the Skills list to bring archived rows back into view, and use Restore in an archived row's drawer to return it to the active list.
Scriptable equivalent:
curl -X POST https://YOUR_WORKSPACE_URL/api/v1/skills/DIGEST/archive \
-H "Authorization: Bearer $ADMIN_TOKEN"
curl -X POST https://YOUR_WORKSPACE_URL/api/v1/skills/DIGEST/unarchive \
-H "Authorization: Bearer $ADMIN_TOKEN"
Tune risk weights
Every indicator's contribution to the risk score, and the review/blocked band thresholds, are shipped defaults — not fixed.
- Open Settings from the left navigation.
- Go to Security & Privacy → Risk Scoring.
- Select the Skills tab.
An Agents tab and a Sessions tab appear alongside Skills, both labeled "Soon" — they're not functional yet. Skills is the only resource type risk scoring covers today.
From this page you can:
- Drag the Trusted / Review / Blocked band boundaries and set the overall score cap.
- Edit the list of vendor-provenance (
builtin) skills and the fixed score assigned to them. - Browse every risk indicator grouped into categories (Remote Code Execution, Privilege Escalation, Credential & Secrets Access, and others), search or filter by category, and toggle an individual indicator on or off, or bulk-toggle a whole category at once.
- Tune the four behavioral thresholds: write breadth, tool breadth, call rate, and policy friction.
Changes are explicit — edit what you want, then select Save, or Reset to discard your edits. Nothing autosaves.
Indicators are split into two kinds, filterable from the same search bar: Security and Hygiene. Hygiene indicators — spec-conformance and code-quality signals such as a malformed skill name or a script missing error handling — default to a weight of 0. They still fire and stay visible in a skill's evidence list; they just don't move the risk score unless you explicitly re-enable them. Only Security-kind indicators contribute to the score by default.
Automate weight changes
The same configuration is fully scriptable, which is the better fit for keeping weights in version control or rolling one configuration out across workspaces. Retrieve the current configuration and the full indicator catalog:
curl https://YOUR_WORKSPACE_URL/api/v1/skills/risk-config \
-H "Authorization: Bearer $ADMIN_TOKEN"
The response's config.weights map is indicator ID → point value, and config.bands holds the review/blocked score cutoffs. To change a weight or a band — for example, to raise the weight on NETWORK_ACCESS_PATTERN if your fleet runs almost entirely network-calling skills and the default is too noisy — submit the full updated configuration back:
curl -X PUT https://YOUR_WORKSPACE_URL/api/v1/skills/risk-config \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"cap": 100,
"weights": { "NETWORK_ACCESS_PATTERN": 20, "...": "..." },
"bands": { "review": 30, "blocked": 70 },
"disabled": ["DEPS_NO_LOCKFILE"]
}'
The update is a full replace, not a patch — fetch the current configuration first, edit the fields you want to change, and submit the whole object back. An update takes effect on the replica that received it immediately; any other replica in your deployment picks it up on its next refresh cycle, typically within a minute.
An indicator can also be disabled outright (contributes 0 regardless of weight) by adding its ID to disabled — useful for a check that's simply too noisy for your fleet rather than one you want to keep at reduced weight.
Any weight or band change bumps the configuration's version. A skill scored under an older version is flagged for the next scan sweep to pick up your new weights, so changing a weight doesn't require manually re-scanning your whole inventory.
Write a policy that gates on skill risk
These recipes use the object.skill.* CEL fields — see Policy Context for the full field list. For the reasoning behind each posture, see Skill Risk — fail-open vs. fail-closed.
Deny the blocked band
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: skill-deny-blocked
spec:
matchConditions:
- expression: 'object.skill.name != ""'
validations:
- expression: '!(object.skill.riskBand == "blocked")'
message: "This skill's risk score has crossed the blocked threshold"
validationActions: [Deny]
Require approval for the review band
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: skill-require-approval-review-band
annotations:
proxy.nirmata.io/enforcement-mode: warn-approve
spec:
matchConditions:
- expression: 'object.skill.name != ""'
validations:
- expression: '!(object.skill.riskBand == "review")'
message: "This skill is in the review band and needs approval before use"
validationActions: [Deny]
Fail closed on unscanned skills
Pair this with the deny-blocked policy above to hold any skill that hasn't completed a scan yet, rather than letting it proceed by default. The not-builtin condition matters: built-in slash commands (/compact, /init, …) are vendor-provided, carry no scannable content, and are never scored — without the carve-out, this policy in Deny mode blocks every one of them:
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: skill-require-scanned
annotations:
proxy.nirmata.io/enforcement-mode: warn-approve
spec:
matchConditions:
- expression: 'object.skill.name != ""'
- name: not-builtin
expression: 'object.skill.source != "builtin"'
validations:
- expression: '!(object.skill.name != "" && !object.skill.scanned)'
message: "This skill has not yet completed a risk scan"
validationActions: [Deny]
Require a declared tool scope
A skill's allowed-tools frontmatter is what makes scope-violation detection possible in the first place — without it, drift to unexpected tools can never be caught. Built-in slash commands have no frontmatter and can never declare a scope, so carve them out here too:
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: skill-require-scope-declared
spec:
matchConditions:
- expression: 'object.skill.name != ""'
- name: not-builtin
expression: 'object.skill.source != "builtin"'
validations:
- expression: 'size(object.skill.allowedTools) > 0'
message: "This skill does not declare an allowed-tools scope, so tool-scope drift cannot be detected for it"
validationActions: [Deny]
Deny a skill that has drifted outside its declared scope
Complements Deny the blocked band above: a single scope violation may not push the aggregate risk score past the blocked threshold on its own, so this recipe reacts to the specific indicator directly:
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: skill-deny-scope-violation
spec:
matchConditions:
- expression: 'object.skill.name != ""'
validations:
- expression: '!("BEHAV_TOOL_SCOPE_VIOLATION" in object.skill.indicators)'
message: "This skill called a tool outside its declared allowed-tools scope"
validationActions: [Deny]
Flag elevated compatibility requirements
compatibility is freeform prose, not a structured field, so this is a keyword signal for human review rather than a reliable enforcement gate on its own — start it in Audit mode and keep it there:
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: skill-flag-elevated-compatibility
annotations:
proxy.nirmata.io/enforcement-mode: audit
spec:
matchConditions:
- expression: 'object.skill.name != ""'
validations:
- expression: '!object.skill.compatibility.matches("(?i)(root|sudo|privileged|admin access)")'
message: "This skill declares elevated or privileged environment requirements in its compatibility frontmatter"
validationActions: [Audit]
Start any new skill-risk policy in Audit mode (validationActions: [Audit]) to measure how many in-flight skills would be affected before switching to Deny or Approve — see the general policy workflow in Write a Policy.
Appeal a false deny with a policy exception
Every skill-risk policy on this page has a stable name (skill-deny-blocked, skill-require-scanned, skill-require-scope-declared, …), so a false deny is appealable without editing the policy: grant a policy-targeted exception naming the denying policy, scoped to the affected user, team, or agent, optionally time-boxed. The policy keeps enforcing for everyone else; the exception downgrades its decision only for the matching subject, and every exercised exception is stamped on the audit event. See Request a Config Exception for the workflow.