Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
cba08b4
AX-1694 - Align VS Code copilot-instructions with Claude/Cursor agent…
MatanEden1 Jun 15, 2026
5f4db9a
AX-1694 - Apply review feedback: generalize installed-state read, mir…
MatanEden1 Jun 15, 2026
e722a81
AX-1694 - Restore type:"http" qualifier on Step 5 OAuth condition
MatanEden1 Jun 15, 2026
a033dca
Refine copilot instructions: clarify project switching and input hand…
MatanEden1 Jun 15, 2026
0606423
AX-1694 - Apply review feedback for cross-plugin alignment
MatanEden1 Jun 16, 2026
6a626ac
AX-1694 - Replace platform-specific scripts with cross-platform Node.…
MatanEden1 Jun 16, 2026
3585b81
AX-1694 - Implement full authentication precedence in inject-instruct…
MatanEden1 Jun 16, 2026
e344389
AX-1694 - Apply review feedback to inject-instructions
MatanEden1 Jun 16, 2026
5cc4d1d
AX-1694 - Clarify --login requires full permissions, not just network
MatanEden1 Jun 16, 2026
2c199e5
AX-1694 - Add jf-cli config fallback and refine MCP instructions
MatanEden1 Jun 17, 2026
01652bf
Clarify MCP config scope, server ID, and paths
MatanEden1 Jun 17, 2026
e247621
Refine MCP configuration instructions for user-level and workspace se…
MatanEden1 Jun 17, 2026
2fc87e1
Standardize MCP config on user-level ~/.vscode/mcp.json
MatanEden1 Jun 17, 2026
af832bb
Use the real VS Code MCP config locations
MatanEden1 Jun 17, 2026
ba067fa
Clarify user-level MCP config path
MatanEden1 Jun 17, 2026
dace1a6
Materialize Copilot instructions and change output
MatanEden1 Jun 17, 2026
5036f0d
Update copilot-instructions.md
MatanEden1 Jun 17, 2026
013dbd8
AX-1694 — Fix user-level MCP config paths and align with claude-plugin
YoniMelki Jun 20, 2026
066b77b
AX-1694 — Address David's review: rename template, drop file-write in…
MatanEden1 Jun 21, 2026
f72c13b
AX-1694 — Remove non-VS-Code-specific bare-JFROG_URL block; tidy inje…
MatanEden1 Jun 21, 2026
ddd80fd
AX-1694 — Address David's review: bump plugin.json, align with Claude…
MatanEden1 Jun 21, 2026
275ae89
AX-1694 — Resolve creds via 'jf config export' instead of parsing the…
MatanEden1 Jun 22, 2026
3154978
AX-1694 — Align template with Claude/Cursor; trim injector comments
MatanEden1 Jun 22, 2026
7d2767d
AX-1694 — Add injector smoke test + CI (catches template-filename drift)
MatanEden1 Jun 22, 2026
b3a531f
AX-1694 — Address review: jf-export timeout, uniform fail-closed, val…
MatanEden1 Jun 22, 2026
b96eee5
Rename validation scripts and update injector checks for template con…
MatanEden1 Jun 23, 2026
ae0ccfc
Enhance SessionStart injector validation with plugin packaging checks
MatanEden1 Jun 23, 2026
484ee52
AX-1694 — Restructure validator: proper main(), grouped checks, manif…
MatanEden1 Jun 23, 2026
1dfeb0c
AX-1694 — Align scope vocabulary with Claude/Cursor
MatanEden1 Jun 23, 2026
bd96899
Update user instructions for server start: avoid prompting if already…
MatanEden1 Jun 23, 2026
2d29afb
AX-1694 — Resolve server via 'jf config show'; simplify 4a
MatanEden1 Jun 24, 2026
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
32 changes: 32 additions & 0 deletions .github/workflows/validate-inject-instructions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright (c) JFrog Ltd. 2026
# Licensed under the Apache License, Version 2.0
# https://www.apache.org/licenses/LICENSE-2.0

name: Validate hook injection

on:
pull_request:
branches: [main]
paths:
- "plugin/scripts/inject-instructions.mjs"
- "plugin/templates/jfrog-mcp-management.md"
- "plugin/hooks/hooks.json"
- "plugin/.claude-plugin/plugin.json"
- "marketplace.json"
Comment thread
YoniMelki marked this conversation as resolved.
- "scripts/validate-hook-injector.mjs"

jobs:
validate:
name: Validate hook injection
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 injector validation
run: node scripts/validate-hook-injector.mjs
2 changes: 1 addition & 1 deletion marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{
"name": "jfrog",
"description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices",
"version": "1.0.3",
"version": "1.0.4",
Comment thread
davida-jfrog marked this conversation as resolved.
"source": "plugin",
"categories": ["security", "artifact-management", "supply-chain", "devops", "mcp", "mlops", "agent-guard", "ai-catalog"],
"platforms": ["darwin", "linux", "windows"],
Expand Down
2 changes: 1 addition & 1 deletion plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jfrog",
"description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices",
"version": "0.1.0",
"version": "1.0.4",
Comment thread
MatanEden1 marked this conversation as resolved.
Comment thread
MatanEden1 marked this conversation as resolved.
"author": { "name": "JFrog", "url": "https://jfrog.com" },
"hooks": "hooks/hooks.json"
}
14 changes: 9 additions & 5 deletions plugin/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
{
"hooks": {
"sessionStart": [
"SessionStart": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/ensure-instructions.sh",
"windows": "powershell -ExecutionPolicy Bypass -File ${CLAUDE_PLUGIN_ROOT}/scripts/ensure-instructions.ps1"
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/inject-instructions.mjs\"",
"timeout": 7
Comment thread
MatanEden1 marked this conversation as resolved.
}
]
}
]
}
}
}
15 changes: 0 additions & 15 deletions plugin/scripts/ensure-instructions.ps1

This file was deleted.

16 changes: 0 additions & 16 deletions plugin/scripts/ensure-instructions.sh

This file was deleted.

146 changes: 146 additions & 0 deletions plugin/scripts/inject-instructions.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#!/usr/bin/env node
// Copyright (c) JFrog Ltd. 2026
// Licensed under the Apache License, Version 2.0
// https://www.apache.org/licenses/LICENSE-2.0

import { execFileSync } from "node:child_process";
Comment thread
YoniMelki marked this conversation as resolved.
import { readFileSync } from "node:fs";
import path from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";

// Logs go to stderr; stdout is reserved for the hook JSON payload.
const debugEnabled = process.env.JF_AGENT_GUARD_DEBUG === "true";
const log = (message) => console.error(`[jfrog-agent-guard] ${message}`);
const debug = (message) => {
if (debugEnabled) log(message);
};

// New JFROG_* env vars take precedence over the legacy JF_* names.
const env = (newName, oldName) =>
process.env[newName] ?? process.env[oldName];

const forceDisabled =
Comment thread
MatanEden1 marked this conversation as resolved.
env("_JF_AGENT_GUARD_FORCE_DISABLE") === "true";
const forceEnabled =
env("JF_AGENT_GUARD_FORCE_ENABLE") === "true";

// Resolve {baseUrl, token} from env vars, falling back to the JFrog CLI's
// default server. Returns null when nothing resolves.
function resolveCredentials() {
Comment thread
MatanEden1 marked this conversation as resolved.
const baseUrl = env("JFROG_URL", "JF_URL");
const token = env("JFROG_ACCESS_TOKEN", "JF_ACCESS_TOKEN");
if (baseUrl && token) {
debug("Resolved credentials from environment variables");
return { baseUrl, token };
}

// `jf config export` emits the default server as a base64-encoded JSON token.
let configToken;
try {
configToken = execFileSync("jf", ["config", "export"], {
Comment thread
MatanEden1 marked this conversation as resolved.
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
timeout: 3000,
}).trim();
} catch (error) {
debug(`'jf config export' failed (jf not on PATH or no server configured): ${error.message}`);
return null;
}

let cfg;
try {
cfg = JSON.parse(Buffer.from(configToken, "base64").toString("utf8"));
} catch (error) {
debug(`Could not decode the jf Config Token: ${error.message}`);
return null;
}

if (!cfg?.url || !cfg?.accessToken) {
debug("jf Config Token did not contain a usable url + accessToken");
return null;
}

debug(`Resolved credentials via 'jf config export' (serverId: ${cfg.serverId ?? "<unknown>"})`);
return { baseUrl: cfg.url, token: cfg.accessToken };
}

async function isAgentGuardEnabledViaSettings() {
const credentials = resolveCredentials();
if (!credentials) {
debug("No JFrog credentials resolved; skipping settings check");
return false;
}
const { baseUrl, token } = credentials;

const url =
baseUrl.replace(/\/+$/, "") +
"/ml/core/api/v1/administration/account-settings/mcp_gateway_plugin_enabled";

debug(`Fetching agent guard setting from ${url}`);

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch(url, {
method: "GET",
headers: {
Accept: "application/json",
Authorization: `Bearer ${token}`,
},
signal: controller.signal,
});
if (!response.ok) {
const body = await response.text().catch(() => "");
debug(`Settings request returned HTTP ${response.status}; body: ${body || "<empty>"}`);
return false;
}
const data = await response.json();
const enabled = data?.settings?.mcpGatewayPluginEnabled?.value === true;
debug(`Settings response indicates agent guard enabled=${enabled}`);
return enabled;
} catch (error) {
const reason = error?.name === "AbortError" ? "timeout" : error?.message ?? "unknown error";
debug(`Settings request failed: ${reason}`);
return false;
} finally {
clearTimeout(timeout);
}
}

if (forceDisabled) {
debug("Force-disable flag is set.");
process.stdout.write("{}");
process.exit(0);
} else if (forceEnabled) {
debug("Force-enable flag is set.");
} else if (!(await isAgentGuardEnabledViaSettings())) {
debug("Agent Guard not enabled; exiting without injecting instructions");
process.stdout.write("{}");
process.exit(0);
}
debug("Injecting instructions");

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");

let template;
try {
template = readFileSync(
path.join(root, "templates", "jfrog-mcp-management.md"),
"utf8",
);
} catch (error) {
debug(`Could not read instructions template: ${error.message}`);
process.stdout.write("{}");
process.exit(0);
Comment thread
MatanEden1 marked this conversation as resolved.
}
Comment thread
YoniMelki marked this conversation as resolved.
Comment thread
YoniMelki marked this conversation as resolved.

// The IDE consumes hookSpecificOutput.additionalContext from a SessionStart hook.
process.stdout.write(
JSON.stringify({
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext: template,
},
}),
);
Comment thread
YoniMelki marked this conversation as resolved.
Comment thread
YoniMelki marked this conversation as resolved.
Loading
Loading