Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"metadata": {
"description": "JFrog Platform plugins for Cursor",
"version": "0.5.18",
"version": "0.6.0",
"pluginRoot": "plugins"
},
"plugins": [
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ jobs:
if: steps.detect.outputs.triggered == 'true'
run: node scripts/validate-template.mjs

# Same reason as above: validate-skill-governance.yml is pull-request only, so the merge
# commit the release is cut from is never checked by it.
- name: Validate skill governance before releasing
if: steps.detect.outputs.triggered == 'true'
run: node scripts/validate-skill-governance.mjs

# Tracked files at HEAD only, so nothing left on the runner can end up in the zip.
- name: Package release artifact
if: steps.detect.outputs.triggered == 'true'
Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/validate-skill-governance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright (c) JFrog Ltd. 2026
# Licensed under the Apache License, Version 2.0
# https://www.apache.org/licenses/LICENSE-2.0

name: Validate skill governance

on:
pull_request:
Comment thread
shmuelqwak marked this conversation as resolved.
branches: [main]
paths:
# Governance lives entirely in agent-guard now, so the wiring IS the hooks file. The
# scripts/** glob stays so that re-adding any plugin-side governance code re-triggers the
Comment thread
shmuelqwak marked this conversation as resolved.
# validator that asserts it must not exist.
- "plugins/jfrog/hooks/hooks.json"
- "plugins/jfrog/scripts/**"
- "plugins/jfrog/modules/**"
- "plugins/jfrog/.cursor-plugin/plugin.json"
- "scripts/validate-skill-governance.mjs"
workflow_dispatch:

permissions:
contents: read

jobs:
validate:
name: Validate skill governance
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Run skill-governance validation
run: node scripts/validate-skill-governance.mjs
56 changes: 55 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ JFrog plugin for [Cursor](https://cursor.com): artifact management, security sca

## What's new

- **Skills governance.** A hook checks the skills you invoke against your JFrog governance policy and blocks the ones it disallows. See [Skills governance](#skills-governance).
- **Agent Package Resolution (Preview).** A hook automatically routes the packages your AI agent installs through your JFrog Artifactory instead of public registries. See [Agent Package Resolution](#agent-package-resolution-preview).
- **AI Catalog skill.** New `jfrog-ai-catalog-skills` skill to discover, install, update, and publish agent skills hosted in the JFrog AI Catalog.
---
Expand All @@ -18,7 +19,8 @@ The JFrog plugin provides the following capabilities, grouped by component:
| **Skill** | JFrog Platform | Interact with Artifactory repositories, builds, permissions, users, access tokens, projects, release bundles, and platform administration via the JFrog CLI and REST/GraphQL APIs. Also covers security audits, CVE lookups, and Advanced Security exposure queries. |
| **Skill** | Package safety & download | Check whether npm, Maven, PyPI, Go, and other packages are safe, curated, or allowed, then download them through Artifactory remote caches or curation-aware package managers. |
| **Hook + Skill** | Agent Package Resolution (Preview) | Automatically route packages installed by the AI agent through your organization's JFrog Artifactory, keeping agent-driven installs inside your Curation, Xray, and governance perimeter. |
| **Skill** | Agent Guard | Manage MCPs through the JFrog Agent Guard. Through the Agent Guard you can discover, install, configure, update, and remove MCP servers from the JFrog AI Catalog approved for your project, and authenticate to remote HTTP MCPs via OAuth, API key, or bearer token. |
| **Hook** | Agent Guard | Cursor manage MCPs through the JFrog Agent Guard. Through the Agent Guard you can discover, install, configure, update, and remove MCP servers from the JFrog AI Catalog approved for your project, and authenticate to remote HTTP MCPs via OAuth, API key, or bearer token. |
| **Hook** | Skills governance | When a skill is invoked, the plugin checks it against your JFrog governance policy and blocks disallowed or unscanned skills before they run. Covers the two entry points that carry a skill's identity: skills you run with `/<skill-name>`, and any read of a `SKILL.md` (how a skill's body reaches the model, since Cursor has no dedicated `Skill` tool). Content that reaches the model without a `Read` tool call is outside both — see [Skills governance](#skills-governance). Enforced only when your account is entitled to AI Catalog skills governance. |

---

Expand Down Expand Up @@ -127,6 +129,58 @@ When Agent Package Resolution is enabled and configured, no special prompt synta

When an MCP server requires a sensitive configuration, the agent cannot set the value directly. Instead, it returns a CLI command for you to copy and run in your terminal. Secrets such as API keys, tokens, and connection strings are never exposed in the agent chat history.

### Skills governance

When a skill is about to run, a hook checks it against your JFrog governance policy and blocks it if policy disallows it. Cursor has no dedicated `Skill` tool, so it covers the two entry points that actually carry a skill's identity:

- you running a skill with `/<skill-name>`,
- and the agent reading a skill's `SKILL.md` — how a skill's body reaches the model, and so also the path a model-decided invocation funnels through, since the agent must read the file before it can act on it. This is caught at `preToolUse` (matcher `Read`), before Cursor reads the file's bytes off disk rather than after.

These are the two entry points that carry a skill's identity; they are not a claim to cover every way content can reach the model. A skill whose name the hook cannot resolve to a folder on disk is **allowed**, not blocked — resolution completeness is therefore a security property, and the searched locations are listed in the Agent Guard's architecture notes. Anything that puts a file's contents in front of the model without going through a `Read` tool call is outside both surfaces.

For each, the hook computes the skill's content **fingerprint** and asks the JFrog governance service for a verdict:

| Verdict | What happens |
| --- | --- |
| **Allowed** | The skill runs. |
| **Blocked** | The skill is prevented from running, and each violated policy is named along with the reason it failed. |
| **Not yet scanned** | The skill is submitted for an on-the-fly scan and blocked with a "scan started — retry shortly" message. |
| **Not entitled** | If your account isn't entitled to AI Catalog skills governance, enforcement is skipped and skills run normally. |

#### Requesting a waiver

When a policy block carries a waiver scope, the block message shows the command that requests one, against the blocking policy's application, stage, and gate, with your justification attached. The Agent Guard files it — `agent-guard --request-waiver` — so the plugin holds no credentials and no waiver logic of its own.

On a blocked `Read` the agent is given the command and can run it once you say why you need access. On a blocked `/<skill-name>` there is no model turn, so the command is printed for you to copy and run yourself. Either way the request goes to your project admin for review — it does not unblock the skill on its own, and nothing is submitted unless you ask for it and give a reason.

**Requirements & behavior**

> [!IMPORTANT]
> **A verdict reaches Cursor as JSON on the hook's stdout.** Three outcomes, and they are distinct:
>
> - **Your JFrog policies deny the skill** — **blocked**, naming the policies it violated and the
> command to request a waiver.
> - **The Agent Guard reaches the check but cannot finish it in time** — **blocked**. It writes a
> refusal explaining that it could not answer, and exits 2. It got as far as the check, so it does
> not guess.
> - **The Agent Guard cannot be *run at all*** — `npx` missing, the registry unreachable, no JFrog
> server configured, or it fails internally — **allowed**. A machine that cannot get a verdict is
> not governed by it, and blocking there would stop work without enforcing anything.
>
> One case is Cursor-specific: the hooks carry `failClosed: false`, so if **Cursor** kills the hook
> at its own `timeout` the action is allowed. That is a different clock from the Agent Guard's own
> budget, which is deliberately the shorter of the two so it answers first.
>
> A user who is entitled to nothing is unaffected either way: the Agent Guard answers "allow" for an
> unconfigured or unentitled user, so no setup is needed to opt out of the feature.

- Set `JFROG_URL` and `JF_ACCESS_TOKEN` (or configure the JFrog CLI — see [Authentication](#authentication)) and `JF_PROJECT` (the JFrog project the skill runs in). For an entitled account with credentials but no project, skills are **blocked** with a message telling you what to set; with no credentials at all they are **allowed**, per the table above.
- **Node.js (≥ 18) with `npx` on your `PATH`** — the hook resolves the Agent Guard through `npx`. Without it, governed actions are allowed unchecked.
- **POSIX shell required.** Cursor's hook schema has no `shell` field, so the command runs in the platform's default shell. It uses POSIX syntax (`${VAR:-default}`, `$(( ))`), so on Windows `cmd`/PowerShell it cannot run — and governed actions are allowed unchecked. macOS and Linux are unaffected.
- **Cost per call.** The hook spawns a shell, `npx` and the Agent Guard on *every* prompt submission (`beforeSubmitPrompt` is unmatched, so it sees all of them) and on *every* `Read`. There is no throttle and no cache of a recent verdict, so a read-heavy session pays it repeatedly. Measured warm on macOS: **~1.1s** per prompt, where the Agent Guard is revalidated against the registry so a released fix reaches you, and **~0.3s** per `Read`, which reads the copy that revalidation left in the cache.
- The Agent Guard logs its decisions to stderr; run Cursor with hook output visible to see them.
- To turn enforcement off, remove the `beforeSubmitPrompt`/`preToolUse` entries from `plugins/jfrog/hooks/hooks.json`.

---

## Troubleshooting
Expand Down
2 changes: 1 addition & 1 deletion plugins/jfrog/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jfrog",
"displayName": "JFrog Platform",
"version": "0.5.18",
"version": "0.6.0",
"description": "JFrog Platform integration with MCP, security skills, Agent Package Resolution, supply-chain best practices, and JFrog Agent Guard governance for adding, removing, and listing MCP servers.",
"author": {
"name": "JFrog",
Expand Down
15 changes: 15 additions & 0 deletions plugins/jfrog/hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@
"command": "node \"./scripts/cursor-align-mcp-json.mjs\" session-start",
"timeout": 60
}
],
"beforeSubmitPrompt": [
{
"command": "_JFAG_NOW=$(date +%s 2>/dev/null); npm_config_fetch_retries=0 npm_config_fetch_timeout=10000 JF_AGENT_GUARD_ENFORCE_DEADLINE=\"${_JFAG_NOW:+$((_JFAG_NOW + 25))}\" npx --yes --registry \"${JFROG_AGENT_GUARD_REPO:-https://releases.jfrog.io/artifactory/api/npm/coding-agents-npm/}\" @jfrog/agent-guard --enforce-skill --client cursor",
"timeout": 30,
"failClosed": false
}
],
"preToolUse": [
{
"command": "_JFAG_NOW=$(date +%s 2>/dev/null); npm_config_fetch_retries=0 npm_config_fetch_timeout=10000 JF_AGENT_GUARD_ENFORCE_DEADLINE=\"${_JFAG_NOW:+$((_JFAG_NOW + 25))}\" npx --yes --prefer-offline --registry \"${JFROG_AGENT_GUARD_REPO:-https://releases.jfrog.io/artifactory/api/npm/coding-agents-npm/}\" @jfrog/agent-guard --enforce-skill --client cursor",
"matcher": "Read",
Comment thread
shmuelqwak marked this conversation as resolved.
"timeout": 30,
"failClosed": false
}
]
}
}
Loading
Loading