> ## 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": "/risk-remediation/automated-pull-requests/index",
  "feedback": "Description of the issue"
}
```

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

</AgentInstructions>

# Automated Pull Requests

> Automatically generate pull requests with dependency upgrades and security fixes.

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 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>;
    return processText(text);
  };
  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}>{processBadges(row[col])}</td>)}
          </tr>)}
      </tbody>
    </table>;
};

You can set up Remediation PRs in your GitHub environment if you use the [Endor Labs GitHub App (Pro)](/setup-deployment/scm-integrations/github-app-pro) or the [Endor Labs GitHub Enterprise Server App](/setup-deployment/scm-integrations/github-app-pro/github-enterprise-app). When Remediation PRs are set up, Endor Labs creates a PR to update the manifest files with dependency version upgrades, based on a remediation policy, to address vulnerability findings.

<Warning>
  You cannot have both the GitHub App and the GitHub App (Pro) simultaneously in your environment. When you migrate from one app to the other, select the same set of repositories as before to preserve the currently scanned projects and vulnerability findings after the migration.
</Warning>

Your tenant must have the upgrades and remediation feature for Remediation PRs to function.

## Understanding Remediation PRs

If Endor Labs identifies any fixes that address vulnerability findings according to the remediation policy in the next scan, it creates a pull request in GitHub with the details of the patch. You can merge the PR after review to fix the vulnerability findings.

Endor Labs updates the PR if there is a recommendation change in [upgrade impact analysis](/risk-remediation/upgrade-impact-analysis). If there are any changes in the vulnerability findings, Endor Labs updates the PR description. If there is new patch version available, Endor Labs closes the existing PR with comments and opens a new PR. If you resolve the notification in Endor Labs, the PR is closed with a comment.

Endor Labs does not further update the PR in the following scenarios, if you:

* Add a commit to the PR
* Close the PR
* Delete the PR branch
* Dismiss the notification in Endor Labs

## Set up Remediation PRs

Complete the following tasks to set up automated PR.

1. Install and enable SCA scanner using either [GitHub App (Pro)](/setup-deployment/scm-integrations/github-app-pro) or [GitHub Enterprise Server App](/setup-deployment/scm-integrations/github-app-pro/github-enterprise-app).
2. [Create a GitHub PR for remediations notification integration.](#create-a-github-pr-for-remediations-notification-integration)
3. [Create a remediation policy with the notification integration that you created in the previous step.](/platform-administration/policies/remediation-policies)

   The following image shows an example of a remediation policy that targets projects with the tag `java` and automatically raises a PR when remediation is found for reachable dependencies that resolve critical and high issues with low upgrade risk.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/2CFeZIRm7eKUPEq0/images/risk-remediation/automated-pull-requests/autoPR_remediationpolicy.webp?fit=max&auto=format&n=2CFeZIRm7eKUPEq0&q=85&s=ed8c26410a752d7791cf2f2a21270f2e" alt="Remediation Policy 1" width="1140" height="1687" data-path="images/risk-remediation/automated-pull-requests/autoPR_remediationpolicy.webp" />

## Remediation PRs support

Remediation PRs are supported for Java (with Gradle or Maven), Go (version 1.18 and higher), Python, .NET, and JavaScript.

#### Remediation PRs support matrix

<YamlTable>
  {`


    - Ecosystem: Python
    Package_manager: pip
    Build_files: \`pyproject.toml\`, \`requirements.txt\`
    Automated_PR_remediation: -yes-
    - Ecosystem:
    Package_manager: pip
    Build_files: \`setup.py\`, \`setup.cfg\`
    Automated_PR_remediation: -no-
    - Ecosystem:
    Package_manager: Poetry
    Build_files: \`pyproject.toml\`, \`poetry.lock\`
    Automated_PR_remediation: -no-
    - Ecosystem:
    Package_manager: PDM
    Build_files: \`pyproject.toml\`, \`pdm.lock\`
    Automated_PR_remediation: -no-
    - Ecosystem: Java
    Package_manager: Maven / Gradle
    Build_files: \`pom.xml\`, \`build.gradle\`, \`build.gradle.kts\`
    Automated_PR_remediation: -yes-
    - Ecosystem: Go
    Package_manager: Go modules
    Build_files: \`go.mod\`, \`go.sum\`
    Automated_PR_remediation: -yes-
    - Ecosystem: JavaScript
    Package_manager: npm / yarn / pnpm
    Build_files: \`package.json\`, \`package-lock.json\`, \`yarn.lock\`, \`pnpm-lock.yaml\`
    Automated_PR_remediation: -yes-
    - Ecosystem: .NET
    Package_manager: NuGet
    Build_files: \`*.csproj\`, \`packages.lock.json\`
    Automated_PR_remediation: -yes-


    `}
</YamlTable>

#### Limitations of Remediation PRs

Currently, Remediation PRs have the following limitations:

* Maven projects that use `dependencyManagement` tags and rely solely on dependency information in the parent pom file are not supported.
* Gradle projects with convention files (Groovy files with `.gradle` extension with any name) are not supported.
* Gradle projects with resource catalogues (version defined in `.toml` files) are not supported.
* * Gradle projects that use Spring Framework plugins, such as the Spring Boot Gradle plugin, to manage dependency versions are not supported. These plugins handle versioning internally, so dependency versions are not explicitly declared in the Gradle manifest file.
* Go projects that use the `replace` directive in `go.mod` are not supported. `replace` directives are commonly used for local development, debugging, or patching dependencies.
* JavaScript projects using npm and Yarn workspaces are not supported.
* .NET projects using `Directory.Packages.props` (central dependency management) or `packages.config`, are not supported.
* Dependency names are case sensitive.
* Updates to .NET dependencies with wildcard characters in their names are not supported.

## Create a GitHub PR for remediations notification integration

Remediation notification integration allows Endor Labs to get a notification from GitHub regarding pull requests. The notification alerts the GitHub App to perform Remediation PRs.

1. Sign in to Endor Labs and select **Integrations** from the left sidebar.
2. Under **Notifications**, click **Add** for **GitHub PR for Remediations**.
3. Click **Add Notification Integration**.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/2CFeZIRm7eKUPEq0/images/risk-remediation/automated-pull-requests/automatePR_createnotification.webp?fit=max&auto=format&n=2CFeZIRm7eKUPEq0&q=85&s=4f3c04ef0379b07196894e1d63fd26eb" alt="Add GitHub PR for Remediation" width="1898" height="784" data-path="images/risk-remediation/automated-pull-requests/automatePR_createnotification.webp" />
4. Enter a name and description for this integration.
5. Select **Enable GitHub PR Notification Integration for Remediations**.
6. Optionally, select **Propagate this notification target to all child namespaces** so that the notification integration applies to all child namespaces.
7. Click **Add Notification Integration**.

## View remediation PRs in GitHub

Endor Labs automatically generates pull requests in GitHub repositories for dependency upgrades and security fixes. Each PR contains version changes, vulnerability details, and compatibility analysis.

To view the remediation PRs:

1. Navigate to your GitHub repository.

2. Click **Pull Requests** to view all the remediation PRs in the repository.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/2CFeZIRm7eKUPEq0/images/risk-remediation/automated-pull-requests/remediation-pr.webp?fit=max&auto=format&n=2CFeZIRm7eKUPEq0&q=85&s=67f26b7e4f63bfb596ffa48173cae23f" alt="GitHub Remediation PRs" width="2615" height="1904" data-path="images/risk-remediation/automated-pull-requests/remediation-pr.webp" />

3. Click on a PR to view it's details.
   * Select **Conversation** to view the version changes, security impact, fixed vulnerabilities, and potential risks, providing context to assess the upgrade.

     <img src="https://mintcdn.com/endorlabs-b4795f4f/2CFeZIRm7eKUPEq0/images/risk-remediation/automated-pull-requests/remediation-pr-description.webp?fit=max&auto=format&n=2CFeZIRm7eKUPEq0&q=85&s=edcad4e52fd1e844a8c3a6ecd61e403e" alt="PR description" width="1928" height="1306" data-path="images/risk-remediation/automated-pull-requests/remediation-pr-description.webp" />

   * Select **Files changed** to view the changes made to the manifest files.

     <img src="https://mintcdn.com/endorlabs-b4795f4f/2CFeZIRm7eKUPEq0/images/risk-remediation/automated-pull-requests/remediation-pr-files-changed.webp?fit=max&auto=format&n=2CFeZIRm7eKUPEq0&q=85&s=793a8276ee75c9f6586460f8262919bb" alt="files changed in the PR" width="2380" height="1010" data-path="images/risk-remediation/automated-pull-requests/remediation-pr-files-changed.webp" />
