> ## Documentation Index
> Fetch the complete documentation index at: https://docs.endorlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.endorlabs.com/feedback

```json
{
  "path": "/developers-api/cli/commands/validate/policy/index",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# policy

> Use this command to validate policies.

export const YamlTable = ({children, data: propData, content}) => {
  const KV_RE = /^([A-Za-z][A-Za-z0-9_()/#\s-]+?):\s*(.+)$/;
  const INLINE_MD_RE = /(\[([^\]]+)\]\(([^)]+)\))|(`([^`]+)`)|(\*\*([^*]+)\*\*)|(\*([^*]+)\*)/g;
  const YES_RE = /^-yes-$/i;
  const NO_RE = /^-no-$/i;
  const LIMITED_RE = /^-(limited|partial)-$/i;
  const NA_RE = /^-(na|none)-$/i;
  const NA2_RE = /^-na2-$/i;
  const SIMPLE_TAG_RE = /(<br\s*\/?>)|(<p\s*\/?>)|(-note-)|(-warning-)/gi;
  const tryParseKV = trimmed => {
    const m = KV_RE.exec(trimmed);
    return m ? {
      key: m[1],
      value: m[2].trim()
    } : null;
  };
  const registerKey = (key, seenKeys, orderedKeys) => {
    if (!seenKeys.has(key)) {
      orderedKeys.push(key);
      seenKeys.add(key);
    }
  };
  const flushEntry = (currentEntry, entries) => {
    if (Object.keys(currentEntry).length > 0) entries.push(currentEntry);
  };
  const parseDashPrefixed = (lines, entries, orderedKeys, seenKeys) => {
    let currentEntry = {};
    let inEntry = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed.startsWith('- ')) {
        if (inEntry) entries.push(currentEntry);
        currentEntry = {};
        inEntry = true;
        const kv = tryParseKV(trimmed.substring(2).trim());
        if (kv) {
          registerKey(kv.key, seenKeys, orderedKeys);
          currentEntry[kv.key] = kv.value;
        }
      } else if (inEntry && trimmed !== '') {
        const kv = tryParseKV(trimmed);
        if (kv) {
          registerKey(kv.key, seenKeys, orderedKeys);
          currentEntry[kv.key] = kv.value;
        }
      }
    }
    flushEntry(currentEntry, entries);
  };
  const parseBlankSeparated = (lines, entries, orderedKeys, seenKeys) => {
    let currentEntry = {};
    let inEntry = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed === '') {
        if (inEntry) {
          flushEntry(currentEntry, entries);
          currentEntry = {};
          inEntry = false;
        }
        continue;
      }
      const kv = tryParseKV(trimmed);
      if (!kv) continue;
      const isNewEntry = !line.startsWith(' ') && !line.startsWith('\t');
      if (isNewEntry && inEntry && Object.keys(currentEntry).length > 0) {
        entries.push(currentEntry);
        currentEntry = {};
      }
      registerKey(kv.key, seenKeys, orderedKeys);
      currentEntry[kv.key] = kv.value;
      inEntry = true;
    }
    flushEntry(currentEntry, entries);
  };
  const normalizeEntries = (entries, orderedKeys) => entries.map(entry => {
    const filled = {};
    for (const key of orderedKeys) filled[key] = entry[key] || '';
    return filled;
  });
  const parseYamlTableContent = contentStr => {
    if (!contentStr) return [];
    const entries = [];
    const orderedKeys = [];
    const seenKeys = new Set();
    const lines = contentStr.split('\n');
    if (lines.some(line => line.trim().startsWith('- '))) {
      parseDashPrefixed(lines, entries, orderedKeys, seenKeys);
    } else {
      parseBlankSeparated(lines, entries, orderedKeys, seenKeys);
    }
    return normalizeEntries(entries, orderedKeys);
  };
  const processText = text => {
    if (!text) return text;
    const parts = [];
    let keyIndex = 0;
    let lastIndex = 0;
    let match;
    while ((match = INLINE_MD_RE.exec(text)) !== null) {
      if (match.index > lastIndex) parts.push(text.slice(lastIndex, match.index));
      if (match[1]) {
        parts.push(<a key={keyIndex++} href={match[3]}>{match[2]}</a>);
      } else if (match[4]) {
        parts.push(<code key={keyIndex++}>{match[5]}</code>);
      } else if (match[6]) {
        parts.push(<strong key={keyIndex++}>{match[7]}</strong>);
      } else if (match[8]) {
        parts.push(<em key={keyIndex++}>{match[9]}</em>);
      }
      lastIndex = match.index + match[0].length;
    }
    if (lastIndex < text.length) parts.push(text.slice(lastIndex));
    if (parts.length === 0) return text;
    const keyRef = {
      current: keyIndex
    };
    return expandHtmlTags(parts, keyRef);
  };
  const processBadges = text => {
    if (!text || typeof text !== 'string') return text;
    if (YES_RE.test(text)) return <span className="yt-badge-yes" role="img" aria-label="Supported" title="Supported">✓</span>;
    if (NO_RE.test(text)) return <span className="yt-badge-no" role="img" aria-label="Not supported" title="Not supported">✗</span>;
    if (LIMITED_RE.test(text)) return <span className="yt-badge-limited" role="img" aria-label="Partially supported" title="Partially supported">◐</span>;
    if (NA_RE.test(text) || NA2_RE.test(text)) return <span className="yt-sr-only" title="Not applicable">Not applicable</span>;
    return processText(text);
  };
  const cellClassName = text => {
    if (!text || typeof text !== 'string') return undefined;
    if (NA_RE.test(text)) return 'yt-cell-na';
    if (NA2_RE.test(text)) return 'yt-cell-na2';
    return undefined;
  };
  const expandSimpleTags = (str, keyRef) => {
    const result = [];
    let last = 0;
    SIMPLE_TAG_RE.lastIndex = 0;
    let m;
    while ((m = SIMPLE_TAG_RE.exec(str)) !== null) {
      if (m.index > last) result.push(str.slice(last, m.index));
      if (m[1]) {
        result.push(<br key={keyRef.current++} />);
      } else if (m[2]) {
        result.push(<br key={keyRef.current++} />, <br key={keyRef.current++} />);
      } else if (m[3]) {
        result.push(<span key={keyRef.current++} className="yt-badge-note" style={{
          fontWeight: 600
        }}>Note: </span>);
      } else if (m[4]) {
        result.push(<span key={keyRef.current++} className="yt-badge-warning" style={{
          fontWeight: 600
        }}>Warning: </span>);
      }
      last = m.index + m[0].length;
    }
    if (last < str.length) result.push(str.slice(last));
    return result;
  };
  const expandHtmlTags = (chunks, keyRef) => {
    const out = [];
    for (const chunk of chunks) {
      if (typeof chunk === 'string') {
        out.push(...expandSimpleTags(chunk, keyRef));
      } else {
        out.push(chunk);
      }
    }
    return out;
  };
  const extractText = node => {
    if (node === null || node === undefined) return '';
    if (typeof node === 'string') return node;
    if (typeof node === 'number') return String(node);
    if (typeof node === 'boolean') return '';
    if (Array.isArray(node)) return node.map(extractText).join('');
    if (node && typeof node === 'object' && node.type) {
      const props = node.props || ({});
      if (typeof props.children === 'string') return props.children;
      if (props.children) return extractText(props.children);
      return '';
    }
    return String(node || '');
  };
  const [mounted, setMounted] = useState(false);
  useEffect(() => {
    setMounted(true);
  }, []);
  const data = useMemo(() => {
    if (propData) return propData;
    if (content && typeof content === 'string') return parseYamlTableContent(content);
    if (!children) return [];
    if (typeof children === 'string') return parseYamlTableContent(children);
    const childrenArray = Array.isArray(children) ? children : [children];
    return parseYamlTableContent(childrenArray.map(extractText).join('').trim());
  }, [children, propData, content]);
  const columns = useMemo(() => {
    if (!data || data.length === 0) return [];
    const firstRow = data[0];
    if (!firstRow || typeof firstRow !== 'object') return [];
    return Object.keys(firstRow);
  }, [data]);
  if (!mounted) return null;
  if (!data || data.length === 0) return null;
  const rowKey = row => columns.map(c => row[c] || '').join('|');
  return <table>
      <thead>
        <tr>
          {columns.map(col => <th key={col}>{col.replaceAll('_', ' ')}</th>)}
        </tr>
      </thead>
      <tbody>
        {data.map(row => <tr key={rowKey(row)}>
            {columns.map(col => <td key={col} className={cellClassName(row[col])}>{processBadges(row[col])}</td>)}
          </tr>)}
      </tbody>
    </table>;
};

## Usage

Use the `endorctl validate policy` command to validate one or more policies against data from one or more projects.
If the policy is valid, the command returns all matches for the given projects, in the requested format, with the corresponding exit code.

The syntax of the `endorctl validate policy` command is:

```bash theme={null}
endorctl validate policy [policies] [flags]
```

### Flags and variables

The `endorctl validate policy` command uses the following flags and environment variables:

<YamlTable>
  {`


    - Flag: \`all-releases\`
    Environment_Variable: \`ENDOR_VALIDATE_POLICY_ALL_RELEASES\`
    Type: boolean
    Description: Load data from all official releases of the project.

    - Flag: \`filter\`
    Environment_Variable: \`ENDOR_VALIDATE_POLICY_PROJECT_FILTER\`
    Type: string
    Description: Filter projects to load data from. For example, \`"meta.tags contains sanity"\`.

    - Flag: \`input\`
    Environment_Variable: \`ENDOR_VALIDATE_POLICY_INPUT_FILE_PATH\`
    Type: json
    Description: Path to a json file containing the input parameter values, if applicable. Input parameters are sometimes used for policy templates.

    - Flag: \`output-type\`
    Environment_Variable: \`ENDOR_VALIDATE_POLICY_SUMMARY_OUTPUT_TYPE\`
    Type: string
    Description: Set output format (\`json\`, \`yaml\`, or \`table\`) (default \`table\`).

    - Flag: \`policy\`
    Environment_Variable: \`ENDOR_VALIDATE_POLICY_FILE_PATH\`
    Type: string
    Description: Path to a file containing the policies to validate. Supported formats: Text (plain Rego rules), json (one or more policies), or yaml (one or more policies or policy templates).

    - Flag: \`policy-uuid\`
    Environment_Variable: \`ENDOR_VALIDATE_POLICY_UUID\`
    Type: string
    Description: UUIDs of policies to validate.

    - Flag: \`pr-baseline\`
    Environment_Variable: \`ENDOR_VALIDATE_POLICY_PR_BASELINE\`
    Type: string
    Description: Name of the baseline version from which to load data. For example, \`main\`.

    - Flag: \`pr-uuid\`
    Environment_Variable: \`ENDOR_VALIDATE_POLICY_PR_UUID\`
    Type: string
    Description: PR scan from which to load data.

    - Flag: \`query\`
    Environment_Variable: \`ENDOR_VALIDATE_POLICY_QUERY_STATEMENTS\`
    Type: string
    Description: Query statements for this policy (for example, \`data.packagename.match_finding\`). This option is only needed for plain text Rego rules.

    - Flag: \`resource-kinds\`
    Environment_Variable: \`ENDOR_VALIDATE_POLICY_RESOURCE_KINDS\`
    Type: comma-separated string
    Description: Resource kinds required by this policy (for example, \`PackageVersion,Metric\`). This option is only needed for plain text Rego rules.

    - Flag: \`uuid\`
    Environment_Variable: \`ENDOR_VALIDATE_POLICY_PROJECT_UUID\`
    Type: string
    Description: UUID of the project from which to load data.


    `}
</YamlTable>

#### Specify one or more policies

Use one of the following formats to specify one or more policies:

<YamlTable>
  {`


    - Input_format: Plain text Rego
    Description: Provide the file path to a file containing just the Rego rules. Provide the query statement as a separate parameter. For example, \`endorctl validate policy --policy policy.txt --query data.example.match_package_version_score\`.

    - Input_format: json
    Description: Provide the file path to a json file containing one or more [Policy](/api-reference/policyservice/listpolicies) objects (see \`Policy\`, \`Policies\`, and \`ListPoliciesResponse\`). For example, \`endorctl validate policy --policy policy.json\`.

    - Input_format: yaml
    Description: Provide the file path to a yaml file containing one or more policy or policy template definitions (see \`Policies\` and \`PolicyTemplates\`). For example, \`endorctl validate policy --policy policy.yaml\`.

    - Input_format: UUID
    Description: Provide one or more UUIDs of existing [Policy](/api-reference/policyservice/listpolicies) objects. For example, \`endorctl validate policy --policy-uuid 6418dc7a55afcfb7b0d0e025\`.


    `}
</YamlTable>

#### Specify a project or a project filter

Use one of the following formats to specify one or more projects from which to load data:

<YamlTable>
  {`


    - Input_format: Filter
    Description: Load data from all projects matching a given [filter](/developers-api/rest-api/using-the-rest-api/filters). For example, \`endorctl validate policy --policy policy.rego --query data.example.match_package_version_score --filter "meta.tags contains release"\`.

    - Input_format: UUID
    Description: Provide the UUID of the project from which to load data. For example, \`endorctl validate policy --policy-uuid 6418dc7a55afcfb7b0d0e025 --uuid 6699c827cd89accb3a017536\`.

    - Input_format: PR UUID
    Description: Provide the UUID of a PR from which to load data. Add the baseline version name to match only new findings. For example, \`endorctl validate policy --policy-uuid 6418dc7a55afcfb7b0d0e025 --uuid 6699c827cd89accb3a017536 --pr-uuid 43064105-1fd7-42fe-a380-bd5e36657d39 --pr-baseline main\`.


    `}
</YamlTable>

#### Specify output format

As with the other `endorctl` commands, you can specify if you prefer the output as a `table`, or in `json` or `yaml` format.
If the output format is `json` or `yaml`, the matching findings appear under `"matching_findings"` and the results for all other resource kinds appear under `"matching_resources"`.

For example, `endorctl validate policy --policy-uuid 6418dc7a55afcfb7b0d0e025 --uuid 6699c827cd89accb3a017536 --output-type json`.

#### Exit Codes

If the policy is valid and there are no matches the command returns 0.
The following table lists the non-zero exit codes returned by the `endorctl validate policy` command:

<YamlTable>
  {`


    - Value: 3
    Exit_Code_Name: ENDORCTL_RC_INVALID_ARGS
    Description: The command received an invalid argument.

    - Value: 18
    Exit_Code_Name: ENDORCTL_RC_POLICY_ERROR
    Description: There was an error evaluating one or more policies. See log for details.

    - Value: 128
    Exit_Code_Name: ENDORCTL_RC_POLICY_VIOLATION
    Description: One or more policies had matching findings for the given projects.


    `}
</YamlTable>

For a complete list of endorctl exit codes, see [endorctl CLI exit codes](/best-practices/troubleshooting/endorctl-exitcodes).

### Example

Below is an example on how to verify that a Rego policy is correctly formatted.

1. First, define a Rego policy. Let's take the example policy below that searches for dependencies with an Endor Labs overall score of less than 7. You can save this to a file called "test\_policy.rego".

   ```yaml theme={null}
   package example

   match_package_version_score[result] {
     some i
     data.resources.Metric[i].meta.name == "package_version_scorecard"
     data.resources.Metric[i].meta.parent_kind == "PackageVersion"
     data.resources.Metric[i].meta.parent_uuid == data.resources.PackageVersion[_].uuid
     score := data.resources.Metric[i].spec.metric_values.scorecard.score_card.overall_score
     score < 7

     result = {
       "Endor" : {
         "PackageVersion" : data.resources.Metric[i].meta.parent_uuid
       },
       "Score" : sprintf("%v", [score])
     }
   }
   ```

2. Next, validate that the policy is correctly formatted.

   ```shell theme={null}
   endorctl validate policy \
     --policy test_policy.rego \
     --query data.example.match_package_version_score
   ```

3. Add a project UUID to validate the policy against real data.

   ```shell theme={null}
   endorctl validate policy \
     --policy test_policy.rego \
     --query data.example.match_package_version_score \
     --uuid $PROJECT_UUID \
     --output-type json > output.json
   ```

4. Inspect the policy output.

   ```json theme={null}
   {
     "matching_resources": {
       "6553132357b462874261f054": {
         "Policy 1": {
           "PackageVersion": [
             {
               "resource_name": "pypi://astunparse@1.6.3",
               "resource_uuid": "63f599e177cf1f3d7f286ea1",
               "result": {
                 "None": [
                   {
                     "Score": "6"
                   }
                 ]
               }
             },
   ```

### Troubleshooting

* Set `--output-type` to `json` or `yaml` for formatted output
* Add the `--verbose` flag for detailed output
* Set `--log-level debug` for more information
