SuperAPI AI Directory

jfrog-curation-onboarding

Office & productivity

View repository

Updated 10-15 23:31

About this skill

JFrog Curation Onboarding

Prefer jf api (../../../platform-features/skills/jfrog-cli/jf-api-patterns.md); curl is fallback where shown.

Sets up JFrog Curation protection on an existing JFrog Platform instance by verifying Curation is enabled and creating a set of security, license, and operational risk policies across remote repositories. Malicious packages are blocked; all other policy violations are logged in dry-run mode with email notifications.

Trigger Phrases

Activate this skill when the user says things like:

  • "Setup curation with my jfrog"
  • "Onboard jfrog curation"
  • "Connect my jfrog with curation"
  • "Enable curation protection"
  • "Block malicious packages"

Operating Modes

This skill has two operating modes:

Standalone Mode (Interactive)

Triggered directly by the user. Runs the full interactive workflow: prerequisite checks, repo discovery, email collection, scope selection, user confirmation, policy creation, and verification. This is the default mode described in the sections below.

Automated Mode (Manifest-Driven)

Called by the jfrog-project-onboarding orchestrator when curation.enabled resolves to true for one or more projects. In this mode the skill:

  • Skips prerequisite checks (Steps 0-1b) -- already verified by the orchestrator
  • Skips interactive email collection (Step 4) -- uses notification_email from the manifest
  • Skips interactive scope selection and user confirmation (Step 5) -- the orchestrator already collected confirmation
  • Executes Steps 2 (check curation status), 3 (discover repos), 6 (enable curation on repos), 7 (create policies), 8 (verify), and 9 (report)

Automated Mode Inputs

Input Source Description
CURATED_REMOTE_REPOS Orchestrator JSON array of remote repo keys from all curation-enabled projects (e.g., ["proj-npm-remote", "proj-pypi-remote"])
CURATION_NOTIFY_EMAIL Manifest jfrog.curation.notification_email (or per-project override) Email for policy violation notifications

Automated Mode Scope Logic

  • If all projects in the manifest have curation enabled (i.e., every remote repo on the platform created by this onboarding run is curated), use all_repos scope -- this also covers repos added in the future.
  • If only some projects have curation enabled, use specific_repos scope with CURATED_REMOTE_REPOS to limit policies to the curated repos only.

Automated Mode Policy Creation

Policies are created once after all projects are processed (not per-project), because curation policies are platform-level resources with unique names. The orchestrator collects all curated remote repos across projects and invokes this skill a single time.

If policies already exist (409), they are treated as success -- no update is attempted. If the user later onboards additional projects with curation enabled, the existing policies continue to work if they use all_repos scope. For specific_repos scope, the orchestrator should update the existing policies to include the new repos (see "Updating Existing Policies" below).

Updating Existing Policies

When onboarding adds new curated repos to a platform that already has curation policies (from a previous onboarding run), the existing specific_repos policies need their repo_include list expanded. The automated mode handles this by:

  1. Listing existing policies (GET /xray/api/v1/curation/policies)
  2. For each of the 8 expected policies, check if it already exists
  3. If it exists with specific_repos scope, update it (PUT /xray/api/v1/curation/policies/{id}) to merge the new repos into repo_include
  4. If it exists with all_repos scope, no update is needed (new curated repos are automatically covered)
  5. If it does not exist, create it as usual
# Example: update an existing specific_repos policy to include new repos
EXISTING_POLICY_ID="<id from GET response>"
EXISTING_REPOS=$(jq -r '.repo_include' /tmp/existing-policy.json)
NEW_REPOS='["proj2-npm-remote","proj2-pypi-remote"]'
MERGED_REPOS=$(echo "$EXISTING_REPOS" "$NEW_REPOS" | jq -s 'add | unique')

jq -n --argjson repos "$MERGED_REPOS" --arg email "$CURATION_NOTIFY_EMAIL" \
  '{repo_include: $repos, notify_emails: [$email]}' >/tmp/curation-update.json
jf api "/xray/api/v1/curation/policies/$EXISTING_POLICY_ID" -X PUT -H "Content-Type: application/json" \
  --input /tmp/curation-update.json >/tmp/curation-update-resp.json 2>/tmp/curation-update-resp.code
HTTP_CODE=$(tr -d '\r\n' < /tmp/curation-update-resp.code)

if [ "$HTTP_CODE" = "200" ]; then
  echo "OK: Policy updated with new repos"
else
  echo "ERROR: Failed to update policy (HTTP $HTTP_CODE)"
  cat /tmp/curation-update-resp.json
fi

Prerequisites

Before starting, load credentials and verify access. If any check fails, abort immediately.

Step 0: Load credentials

if [ -z "$JFROG_URL" ] || [ -z "$JFROG_ACCESS_TOKEN" ]; then
  if [ -f .env ]; then
    set -a; source .env; set +a
  fi
fi

[ -z "$JFROG_URL" ] && echo "FAIL: JFROG_URL is not set" && exit 1
[ -z "$JFROG_ACCESS_TOKEN" ] && echo "FAIL: JFROG_ACCESS_TOKEN is not set" && exit 1
echo "OK: JFROG_URL=$JFROG_URL"
echo "OK: JFROG_ACCESS_TOKEN is set"

Step 1a: Validate token (run with required_permissions: ["full_network"])

jf api /artifactory/api/system/version >/tmp/jf-cur-ver.json 2>/tmp/jf-cur-ver.code
HTTP_CODE=$(tr -d '\r\n' < /tmp/jf-cur-ver.code)

if [ "$HTTP_CODE" = "200" ]; then
  echo "OK: Token authentication successful"
else
  echo "FAIL: Token authentication failed (HTTP $HTTP_CODE)"
  exit 1
fi

Step 1b: Validate platform admin privileges (run with required_permissions: ["full_network"])

jf api /access/api/v1/config/security/authentication/basic_authentication_enabled >/tmp/jf-cur-admin.json 2>/tmp/jf-cur-admin.code
HTTP_CODE=$(tr -d '\r\n' < /tmp/jf-cur-admin.code)

if [ "$HTTP_CODE" = "200" ]; then
  echo "OK: Platform admin privileges confirmed"
else
  echo "ABORT: Token does not have platform admin privileges (HTTP $HTTP_CODE)"
  echo "Generate an admin token from: $JFROG_URL/ui/admin/configuration/security/access_tokens"
  exit 1
fi

API Reference

Operation Method Endpoint Notes
Check curation status GET /artifactory/api/curation/status Returns {"status": true/false, ...}
List remote repos with curation status GET /artifactory/api/curation/repositories Returns curated boolean and project per repo
Enable/disable curation on a repo POST /artifactory/api/repositories/{repoKey} Send {"curated": true} (standard repo update)
Create curation policy POST /xray/api/v1/curation/policies See payload fields in Step 7
List curation policies GET /xray/api/v1/curation/policies Returns {data: [...], meta: {...}}
Get policy by ID GET /xray/api/v1/curation/policies/{id}
Update policy PUT /xray/api/v1/curation/policies/{id}
Delete policy DELETE /xray/api/v1/curation/policies/{id}

Policies to Create

The skill creates the following 8 curation policies. Block Malicious blocks downloads; all others use dry-run (audit) mode to log violations without blocking.

# Policy Name Condition ID Action Risk Type
1 Block Malicious 1 block Security
2 CVE CVSS 9 and above with fix 2 dry_run Security
3 License GNU AGPL 9 dry_run Legal
4 License GNU GPL 10 dry_run Legal
5 License GNU LGPL 11 dry_run Legal
6 Aged no newer version 12 dry_run Operational
7 Aged newer available 13 dry_run Operational
8 Immature moderate 15 dry_run Operational

Policy naming rule: The JFrog API rejects policy names containing special characters (+, :, (, ), etc.). Use only alphanumeric characters, spaces, and hyphens in policy names.

Workflow

Step 2: Check Curation Status (run with required_permissions: ["full_network"])

Verify that Curation is enabled on the platform. Uses the safe API call pattern.

HTTP_CODE=$( (jf api "/artifactory/api/curation/status" >/tmp/curation-status.json 2>/tmp/curation-status.code) ; tr -d '\r\n' < /tmp/curation-status.code )

if [ "$HTTP_CODE" = "200" ]; then
  echo "OK: Curation API is reachable"
  cat /tmp/curation-status.json
else
  echo "ABORT: Curation is not enabled or not available (HTTP $HTTP_CODE)"
  echo ""
  echo "To enable Curation, go to:"
  echo "  $JFROG_URL/ui/admin/curation-settings/general"
  echo ""
  echo "Enable Curation in the settings, then run this skill again."
  exit 1
fi

If the API returns a non-200 status, abort and instruct the user:

Curation is not enabled on your JFrog Platform.

To enable it, navigate to Curation Settings > General in the JFrog UI: {JFROG_URL}/ui/admin/curation-settings/general

Please enable the following settings:

  1. Enable Curation -- activates curation on the platform
  2. Enable compliant version selection (optional, but recommended) -- when a package version is blocked by a curation policy, Artifactory will automatically suggest the closest compliant version that passes all policies

After enabling Curation, run this skill again.

Step 3: Discover Remote Repositories and Curation Status (run with required_permissions: ["full_network"])

Before creating policies, discover the remote repositories and their curation status. Use the curation-specific endpoint which returns the curated boolean and project for each repo.

HTTP_CODE=$( (jf api "/artifactory/api/curation/repositories" >/tmp/curation-remote-repos.json 2>/tmp/curation-remote-repos.code) ; tr -d '\r\n' < /tmp/curation-remote-repos.code )

if [ "$HTTP_CODE" = "200" ]; then
  echo "OK: Retrieved remote repositories with curation status"
  jq -r '.[] | "\(.key) (pkg: \(.packageType), project: \(.project // "none"), curated: \(.curated))"' /tmp/curation-remote-repos.json
else
  echo "ERROR: Could not list remote repositories (HTTP $HTTP_CODE)"
  cat /tmp/curation-remote-repos.json
  exit 1
fi

Response format (each element):

{
  "key": "myrepo-npm-remote",
  "packageType": "npm",
  "url": "https://registry.npmjs.org",
  "project": "myproj",
  "curated": false
}

Group the repositories by project for display. The curated field indicates whether curation is already enabled on each repo -- this is critical for the later enablement step.

Step 4: Collect Notification Email

Ask the user for an email address to receive notifications when curation policies are violated.

What email address should receive notifications for Curation policy violations?

Store the email as CURATION_NOTIFY_EMAIL. This email will be included in the notify_emails field of every policy created in Step 7.

Step 5: Present Plan and Get Confirmation

Present the user with a clear plan before creating or applying any policies. The plan must include:

  1. Policies to create: list all 8 policies with their actions (block vs dry_run)
  2. Notification email: the email provided in Step 4
  3. Scope options: Ask the user to choose one:
    • Global (all remote repositories) -- the policies apply to every remote repo on the platform, including repos added in the future
    • Specific project(s) -- the policies apply only to remote repos belonging to the selected JFrog project(s). List the discovered projects and their remote repo counts so the user can choose.

Present the plan like this (adapt based on discovered repos):

Curation Policies Plan

The following policies will be created:

# Policy Action Description
1 Block Malicious Block Blocks all known malicious packages (JFrog Security Research)
2 CVE CVSS 9 and above with fix Dry Run Audits packages with critical CVE scores where a fix exists
3 License GNU AGPL Dry Run Audits packages with GNU AGPL license
4 License GNU GPL Dry Run Audits packages with GNU GPL license
5 License GNU LGPL Dry Run Audits packages with GNU LGPL license
6 Aged no newer version Dry Run Audits aged package versions with no newer version identified
7 Aged newer available Dry Run Audits aged package versions where a newer version exists
8 Immature moderate Dry Run Audits immature package versions (< 14 days old)

Notification email: {CURATION_NOTIFY_EMAIL}

Discovered remote repositories: {N} total

  • Global (no project): {list or count}
  • Project {key1}: {list or count}
  • Project {key2}: {list or count}

How would you like to apply these policies?

  1. Globally -- applies to all {N} remote repositories (and any future ones)
  2. Per project -- applies only to remote repositories in specific project(s)

Wait for the user to choose the scope option. If they choose per-project, ask which project(s) to include.

After the user selects the scope, present a final confirmation before proceeding:

Please confirm the following changes:

  • Create 8 curation policies (1 blocking + 7 dry-run)
  • Notification email: {CURATION_NOTIFY_EMAIL}
  • Scope: {Global / Projects: key1, key2}
  • Affected repositories: {list of repo names}

Proceed? (yes/no)

Do not create or apply anything until the user explicitly confirms.

Step 6: Enable Curation on Target Repositories (run with required_permissions: ["full_network"])

IMPORTANT: Curation policies can only target repos that have curated: true. If no repos are curated, even an all_repos policy will fail with "policy cannot have empty effective scope". Repos are not curated by default.

Check whether the target repos have curated: true (from Step 3 data). For any repo with curated: false, enable curation via the standard Artifactory repo update API:

# Enable curation on a single repo
jf api "/artifactory/api/repositories/$REPO_KEY" -X POST -H "Content-Type: application/json" \
  -d '{"curated": true}' >/tmp/curation-enable-resp.json 2>/tmp/curation-enable-resp.code
HTTP_CODE=$(tr -d '\r\n' < /tmp/curation-enable-resp.code)

if [ "$HTTP_CODE" = "200" ]; then
  echo "OK: Curation enabled on $REPO_KEY"
else
  echo "ERROR: Failed to enable curation on $REPO_KEY (HTTP $HTTP_CODE)"
  cat /tmp/curation-enable-resp.json
fi

Loop over all target repos that have curated: false, enabling each one with a sleep 1 between calls to avoid rate limiting. After enabling, re-fetch GET /artifactory/api/curation/repositories and verify all target repos now show curated: true.

For global scope: enable curation on all remote repos that are currently curated: false. For specific_repos scope: enable curation only on the selected repos.

Step 7: Create Curation Policies (run with required_permissions: ["full_network"])

After the user confirms and curation is enabled on target repos (Step 6), create all 8 curation policies with the chosen scope. Loop through each policy definition, creating them one at a time with a sleep 1 between calls to avoid rate limiting.

Policy definitions

POLICIES='[
  {"name":"Block Malicious","condition_id":"1","policy_action":"block","waiver_request_config":"forbidden"},
  {"name":"CVE CVSS 9 and above with fix","condition_id":"2","policy_action":"dry_run","waiver_request_config":"forbidden"},
  {"name":"License GNU AGPL","condition_id":"9","policy_action":"dry_run","waiver_request_config":"forbidden"},
  {"name":"License GNU GPL","condition_id":"10","policy_action":"dry_run","waiver_request_config":"forbidden"},
  {"name":"License GNU LGPL","condition_id":"11","policy_action":"dry_run","waiver_request_config":"forbidden"},
  {"name":"Aged no newer version","condition_id":"12","policy_action":"dry_run","waiver_request_config":"forbidden"},
  {"name":"Aged newer available","condition_id":"13","policy_action":"dry_run","waiver_request_config":"forbidden"},
  {"name":"Immature moderate","condition_id":"15","policy_action":"dry_run","waiver_request_config":"forbidden"}
]'

Option A: Global scope

POLICY_COUNT=$(echo "$POLICIES" | jq length)
for i in $(seq 0 $(($POLICY_COUNT - 1))); do
  POLICY_NAME=$(echo "$POLICIES" | jq -r ".[$i].name")
  CONDITION_ID=$(echo "$POLICIES" | jq -r ".[$i].condition_id")
  POLICY_ACTION=$(echo "$POLICIES" | jq -r ".[$i].policy_action")
  WAIVER_CONFIG=$(echo "$POLICIES" | jq -r ".[$i].waiver_request_config")

  PAYLOAD=$(jq -n \
    --arg name "$POLICY_NAME" \
    --arg condition_id "$CONDITION_ID" \
    --arg policy_action "$POLICY_ACTION" \
    --arg waiver "$WAIVER_CONFIG" \
    --arg email "$CURATION_NOTIFY_EMAIL" \
    '{
      name: $name,
      condition_id: $condition_id,
      scope: "all_repos",
      policy_action: $policy_action,
      waiver_request_config: $waiver,
      notify_emails: [$email]
    }')

  echo "$PAYLOAD" >/tmp/curation-policy-payload.json
  jf api /xray/api/v1/curation/policies -X POST -H "Content-Type: application/json" \
    --input /tmp/curation-policy-payload.json >/tmp/curation-policy-resp.json 2>/tmp/curation-policy-resp.code
  HTTP_CODE=$(tr -d '\r\n' < /tmp/curation-policy-resp.code)

  if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
    echo "OK: Policy '$POLICY_NAME' created successfully (global scope)"
  elif [ "$HTTP_CODE" = "409" ]; then
    echo "OK: Policy '$POLICY_NAME' already exists (no action needed)"
  else
    echo "ERROR: Failed to create policy '$POLICY_NAME' (HTTP $HTTP_CODE)"
    cat /tmp/curation-policy-resp.json
  fi

  sleep 1
done

Option B: Project-scoped

When the user selects specific projects, create the policies scoped to those projects' remote repositories. Build the repo list from the discovered repos in Step 3.

# SELECTED_REPOS should be a JSON array string, e.g. '["repo1","repo2"]'
POLICY_COUNT=$(echo "$POLICIES" | jq length)
for i in $(seq 0 $(($POLICY_COUNT - 1))); do
  POLICY_NAME=$(echo "$POLICIES" | jq -r ".[$i].name")
  CONDITION_ID=$(echo "$POLICIES" | jq -r ".[$i].condition_id")
  POLICY_ACTION=$(echo "$POLICIES" | jq -r ".[$i].policy_action")
  WAIVER_CONFIG=$(echo "$POLICIES" | jq -r ".[$i].waiver_request_config")

  PAYLOAD=$(jq -n \
    --arg name "$POLICY_NAME" \
    --arg condition_id "$CONDITION_ID" \
    --arg policy_action "$POLICY_ACTION" \
    --arg waiver "$WAIVER_CONFIG" \
    --arg email "$CURATION_NOTIFY_EMAIL" \
    --argjson repo_include "$SELECTED_REPOS" \
    '{
      name: $name,
      condition_id: $condition_id,
      scope: "specific_repos",
      repo_include: $repo_include,
      policy_action: $policy_action,
      waiver_request_config: $waiver,
      notify_emails: [$email]
    }')

  echo "$PAYLOAD" >/tmp/curation-policy-payload.json
  jf api /xray/api/v1/curation/policies -X POST -H "Content-Type: application/json" \
    --input /tmp/curation-policy-payload.json >/tmp/curation-policy-resp.json 2>/tmp/curation-policy-resp.code
  HTTP_CODE=$(tr -d '\r\n' < /tmp/curation-policy-resp.code)

  if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
    echo "OK: Policy '$POLICY_NAME' created for selected project repos"
  elif [ "$HTTP_CODE" = "409" ]; then
    echo "OK: Policy '$POLICY_NAME' already exists (no action needed)"
  else
    echo "ERROR: Failed to create policy '$POLICY_NAME' (HTTP $HTTP_CODE)"
    cat /tmp/curation-policy-resp.json
  fi

  sleep 1
done

Payload fields:

Field Value Description
name Policy display name See policy definitions table above
condition_id "1", "2", "9", etc. Built-in condition ID matching the policy type
scope all_repos, specific_repos, or pkg_types Scope mode
repo_include ["repo-key-1", ...] (Only for specific_repos) List of remote repo keys to protect
repo_exclude ["repo-key-1", ...] (Optional, only for all_repos) List of remote repo keys to exclude
pkg_types_include ["npm", "PyPI", ...] (Only for pkg_types) List of package types to protect
policy_action block or dry_run block for Block Malicious; dry_run for all others
waiver_request_config forbidden Required for all policies (block and dry_run). No waiver requests allowed
notify_emails ["email@example.com"] Email addresses notified on policy violations

Scope modes:

  • all_repos -- applies to all curated remote repos. Optionally use repo_exclude to omit specific repos.
  • specific_repos -- requires repo_include with at least one curated repo key. Cannot use repo_exclude or pkg_types_include.
  • pkg_types -- requires pkg_types_include with at least one package type. Cannot use repo_include or repo_exclude.

Step 8: Verify Policies (run with required_permissions: ["full_network"])

Confirm all policies exist by listing all curation policies.

HTTP_CODE=$( (jf api "/xray/api/v1/curation/policies" >/tmp/curation-policies-list.json 2>/tmp/curation-policies-list.code) ; tr -d '\r\n' < /tmp/curation-policies-list.code )

if [ "$HTTP_CODE" = "200" ]; then
  echo "Current curation policies:"
  jq -r '.data[] | "  - \(.name) (action: \(.policy_action), scope: \(.scope))"' /tmp/curation-policies-list.json
else
  echo "WARN: Could not list policies (HTTP $HTTP_CODE)"
fi

Step 9: Report Results

After completing the workflow, report a summary to the user.

For global scope:

Curation Onboarding Complete

  • Platform: {JFROG_URL}

  • Curation status: Enabled

  • Notification email: {CURATION_NOTIFY_EMAIL}

  • Scope: Global -- all remote repositories ({N} current + any future repos)

  • Policies created:

    Policy Action
    Block Malicious Block
    CVE CVSS 9 and above with fix Dry Run
    License GNU AGPL Dry Run
    License GNU GPL Dry Run
    License GNU LGPL Dry Run
    Aged no newer version Dry Run
    Aged newer available Dry Run
    Immature moderate Dry Run

All remote repositories are now protected. Malicious packages are blocked; all other policies run in audit mode (dry-run) and will generate email notifications without blocking downloads.

View all policies: {JFROG_URL}/ui/package-curation/policies

For project scope:

Curation Onboarding Complete

  • Platform: {JFROG_URL}

  • Curation status: Enabled

  • Notification email: {CURATION_NOTIFY_EMAIL}

  • Scope: Project-scoped -- {project key(s)}

  • Protected repositories: {list of repo names}

  • Policies created:

    Policy Action
    Block Malicious Block
    CVE CVSS 9 and above with fix Dry Run
    License GNU AGPL Dry Run
    License GNU GPL Dry Run
    License GNU LGPL Dry Run
    Aged no newer version Dry Run
    Aged newer available Dry Run
    Immature moderate Dry Run

The selected project repositories are now protected. Malicious packages are blocked; all other policies run in audit mode (dry-run) and will generate email notifications without blocking downloads. Note: Remote repositories outside the selected project(s) are not covered by these policies. Re-run this skill to extend coverage to additional projects or switch to global scope.

View all policies: {JFROG_URL}/ui/package-curation/policies

Error Handling

HTTP Code Meaning Action
200/201 Success Proceed
400 "empty effective scope" No repos have curated: true Enable curation on target repos first (Step 6), then retry
400 "repository X is not curated" A specific repo in repo_include has curated: false Enable curation on that repo via POST /artifactory/api/repositories/{key} with {"curated": true}
409 Policy already exists Treat as success, report to user
401/403 Auth failure or insufficient privileges Abort with guidance
Non-200 on curation status Curation not enabled Abort, instruct user to enable from UI

Related skills

For Curation API concepts, policy types, and manual policy management, see platform-features (jfrog-curation).