Skip to main content

Custom Framework JSON Upload

JupiterOne allows you to import custom compliance frameworks by pasting a JSON document directly in the UI. This is useful when you have an internal security policy, a regulatory framework, or any custom standard that is not available out of the box.

This guide documents the full JSON schema, all supported fields, and provides examples to help you build your framework JSON.

How to Import

  1. Navigate to Frameworks under the Controls tab in the navigation bar
  2. Click New Framework in the top right
  3. Select Paste Framework JSON
  4. Paste your JSON document
  5. Click Continue to import

JSON Schema Overview

A framework JSON document has three levels of nesting: the framework itself contains requirements, each requirement contains controls, and each control can optionally contain control tests.

{
"name": "Framework Name",
"description": "Framework description",
"requirements": [
{
"displayName": "Requirement Name",
"description": "Requirement description",
"controls": [
{
"displayName": "Control Name",
"controlTests": [
{
"name": "Test Name",
"query": "FIND ... AS x RETURN x",
"resultsAre": "GOOD"
}
]
}
]
}
]
}

Field Reference

Framework (Top Level)

FieldTypeRequiredDescription
namestringYesThe name of the framework (e.g., "ACME Internal Security Policy v2")
descriptionstringYesA description of what the framework covers
requirementsarrayYesArray of requirement objects

Requirement

FieldTypeRequiredDescription
displayNamestringYesThe display name shown in the UI (e.g., "1.2 - Ensure MFA is enabled")
descriptionstringYesDetailed description of the requirement
sequencestringNoSort order identifier (e.g., "1.2", "2.1.3"). Used to order requirements in the UI
titlestringNoUser-facing title. If provided, overrides displayName for the stored title. If omitted, displayName is used
identifierstringNoAn external identifier for the requirement (e.g., a regulation section number or internal policy ID)
prioritystringNoPriority level. One of: CRITICAL, HIGH, MEDIUM, LOW
controlsarrayYesArray of control objects. Can be an empty array [] if controls have not been defined yet

Control

FieldTypeRequiredDescription
displayNamestringYesThe name of the control
descriptionstringNoDescription of what the control checks. Should be specific enough to write a test against
sourceIdstringNoA unique identifier for the control. Controls with the same sourceId across multiple requirements are automatically deduplicated and shared
statestringNoThe lifecycle state of the control. One of: DRAFT, REVIEW, LIVE, RETIRED. Defaults to LIVE if not provided
catalogstringNoThe catalog or standard this control belongs to (e.g., "CIS", "NIST")
identifierstringNoAn external identifier for the control (e.g., "CIS 1.2", "AC-2")
ownerstringNoEmail address of the control owner
remediationstringNoInstructions for how to remediate when this control fails. Supports markdown
exceptionProcessstringNoDescription of the exception process for this control. Supports markdown
controlTestsarrayNoArray of control test objects. Can be omitted or set to null if tests have not been defined yet

Control Test

FieldTypeRequiredDescription
namestringYesA descriptive name for this test query
querystringYesA J1QL query that returns entities to evaluate
resultsArestringYesEither GOOD or BAD (see below)

Understanding resultsAre

  • GOOD — Results from this query represent compliant entities. If the query returns results, that is a positive signal.
  • BAD — Results from this query represent non-compliant entities. If the query returns results, that indicates a failure.

A common pattern is to define two tests per control: one GOOD query that finds compliant resources, and one BAD query that finds non-compliant resources.

Control State Lifecycle

Controls follow a lifecycle with the following valid transitions:

DRAFT → REVIEW → LIVE ↔ RETIRED

DRAFT
StateDescription
DRAFTControl is being authored and is not yet active
REVIEWControl is under review before going live
LIVEControl is active and being evaluated. This is the default for imported controls
RETIREDControl is no longer active
note

When you import a framework, controls default to the LIVE state unless you explicitly set a different state value. You can transition control states after import through the UI.

Control Deduplication

If the same control appears in multiple requirements, JupiterOne automatically deduplicates it. Deduplication works in two ways:

  • By sourceId: If two controls across different requirements share the same sourceId, they are treated as the same control and linked to both requirements.
  • By displayName: During import, controls with the same displayName are also deduplicated. The first occurrence is used if there are any differences.

This means you can reference the same control in multiple requirements without creating duplicates.

Examples

Minimal Framework

A valid framework with just requirements and no controls or tests:

{
"name": "ACME Security Policy",
"description": "Internal security controls for ACME Corp.",
"requirements": [
{
"displayName": "1.1 - Enable MFA for all users",
"description": "All users with console access must have MFA enabled.",
"controls": []
},
{
"displayName": "1.2 - Rotate access keys",
"description": "Access keys should be rotated every 90 days.",
"controls": []
}
]
}

You can add controls and tests later through the UI.

Framework with Controls and Tests

{
"name": "ACME Cloud Security Standard",
"description": "Cloud infrastructure security controls for ACME Corp.",
"requirements": [
{
"displayName": "1.1 - Ensure MFA is enabled for all IAM users",
"description": "All IAM users with console access must have MFA enabled to prevent unauthorized access.",
"sequence": "1.1",
"identifier": "IAM-001",
"priority": "CRITICAL",
"controls": [
{
"displayName": "IAM Users Have MFA Enabled",
"description": "Verify that all IAM users with console passwords have at least one MFA device assigned.",
"sourceId": "iam-mfa-check",
"catalog": "CIS AWS",
"identifier": "CIS 1.10",
"owner": "security-team@acme.com",
"remediation": "Enable MFA for the user via the IAM console:\n1. Go to **IAM > Users**\n2. Select the user\n3. Click **Security credentials**\n4. Click **Assign MFA device**",
"exceptionProcess": "Submit a request to the Security team with business justification.",
"controlTests": [
{
"name": "IAM users with MFA enabled",
"query": "FIND aws_iam_user WITH mfaEnabled = true AS user RETURN user",
"resultsAre": "GOOD"
},
{
"name": "IAM users without MFA enabled",
"query": "FIND aws_iam_user WITH mfaEnabled != true AND passwordEnabled = true AS user RETURN user",
"resultsAre": "BAD"
}
]
}
]
},
{
"displayName": "1.2 - Rotate access keys within 90 days",
"description": "Access keys should be rotated regularly to limit the impact of compromised credentials.",
"sequence": "1.2",
"identifier": "IAM-002",
"priority": "HIGH",
"controls": [
{
"displayName": "Access Keys Rotated Within 90 Days",
"description": "Ensure all active access keys have been rotated within the last 90 days.",
"controlTests": [
{
"name": "Access keys rotated within 90 days",
"query": "FIND aws_iam_access_key WITH active = true AND createdOn > date.now - 90 days AS key RETURN key",
"resultsAre": "GOOD"
},
{
"name": "Access keys older than 90 days",
"query": "FIND aws_iam_access_key WITH active = true AND createdOn < date.now - 90 days AS key RETURN key",
"resultsAre": "BAD"
}
]
}
]
}
]
}

Shared Controls Across Requirements

In this example, the "Encryption at Rest Enabled" control (with sourceId encryption-at-rest) appears in two requirements. JupiterOne creates the control once and links it to both:

{
"name": "Data Protection Standard",
"description": "Controls for protecting data at rest and in transit.",
"requirements": [
{
"displayName": "2.1 - Encrypt S3 buckets",
"description": "All S3 buckets must have default encryption configured.",
"sequence": "2.1",
"controls": [
{
"displayName": "Encryption at Rest Enabled",
"sourceId": "encryption-at-rest",
"description": "Verify that storage resources have encryption enabled.",
"controlTests": [
{
"name": "Encrypted S3 buckets",
"query": "FIND aws_s3_bucket WITH encrypted = true AS bucket RETURN bucket",
"resultsAre": "GOOD"
}
]
}
]
},
{
"displayName": "3.1 - Encrypt EBS volumes",
"description": "All EBS volumes must be encrypted.",
"sequence": "3.1",
"controls": [
{
"displayName": "Encryption at Rest Enabled",
"sourceId": "encryption-at-rest",
"description": "Verify that storage resources have encryption enabled.",
"controlTests": [
{
"name": "Encrypted EBS volumes",
"query": "FIND aws_ebs_volume WITH encrypted = true AS vol RETURN vol",
"resultsAre": "GOOD"
}
]
}
]
}
]
}

Controls with State and Metadata

Use state to import controls in different lifecycle stages:

{
"name": "Phased Rollout Framework",
"description": "Framework demonstrating controls in different lifecycle states.",
"requirements": [
{
"displayName": "1.1 - Production-ready control",
"description": "This control is fully validated and active.",
"controls": [
{
"displayName": "Active Control",
"state": "LIVE",
"catalog": "Internal",
"identifier": "SEC-001",
"owner": "compliance@acme.com",
"controlTests": [
{
"name": "Check for compliant resources",
"query": "FIND Host WITH encrypted = true AS h RETURN h",
"resultsAre": "GOOD"
}
]
}
]
},
{
"displayName": "1.2 - Control under development",
"description": "This control is still being authored.",
"controls": [
{
"displayName": "Draft Control",
"state": "DRAFT",
"catalog": "Internal",
"identifier": "SEC-002"
}
]
}
]
}

Validation

The JSON is validated on upload. The following rules apply:

  • name and description are required at the top level
  • Every requirement must have displayName, description, and a controls array
  • Every control must have a displayName
  • Every control test must have name, query, and resultsAre (either GOOD or BAD)
  • Extra properties are allowed and will not cause validation errors—they are passed through and stored

Common Validation Errors

ErrorCauseResolution
"Framework JSON and name are required"Missing the JSON body or framework nameEnsure both are provided when uploading
"Invalid frameworkJson"The JSON structure does not match the expected schemaCheck that all required fields are present and correctly typed

Tips for Writing Control Tests

  • Use the Query Builder first: Test your J1QL queries in JupiterOne's Query Builder before adding them to your framework JSON to verify they return the expected results.
  • Pair GOOD and BAD tests: Define both a positive and negative test for comprehensive coverage. The GOOD test shows what is compliant, and the BAD test shows what needs attention.
  • Use specific filters: Write precise WITH clauses to avoid false positives. For example, FIND aws_iam_user WITH mfaEnabled != true AND passwordEnabled = true is better than FIND aws_iam_user WITH mfaEnabled != true because it excludes programmatic users without console access.
  • Use the AS and RETURN pattern: Queries should follow the FIND <entity> WITH <filters> AS <alias> RETURN <alias> pattern for best results.