> ## 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": "/scan/containers/container-reachability/index",
  "feedback": "Description of the issue"
}
```

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

</AgentInstructions>

# Container reachability

> <Badge color="green">Beta</Badge> <br /> Determine if the packages inside a container image are actually used by your application at runtime.

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>;
};

Endor Labs allows you to determine whether the OS packages present in a container image are actually used by your application at runtime. Use container reachability to distinguish dependencies that are merely installed from those that are actively exercised during execution, helping security teams prioritize the most critical security issues for remediation.

<Note>
  Run the container scan using the new `endorctl container scan` command. The `endorctl scan --container` command does not support container reachability.
</Note>

Endor Labs supports the following two container reachability modes. Choose the mode that aligns with how your workload executes and what dependencies it requires at runtime.

* **Basic Reachability**: Endor Labs executes and profiles the container image in a local environment during the scan. Select this method when the application can run without relying on external services.
* [**Instrumented Reachability**](/scan/containers/instrumented-reachability): Endor Labs integrates a sensor into the container image and deploys it in the environment where the application normally runs. The sensor captures runtime behavior and produces a profiling report, which Endor Labs then analyzes. Select this method for workloads that rely on external services or interactions that you cannot trigger in a brief local execution.

## Prerequisites

To perform container reachability analysis, ensure you meet the following system requirements:

* The container must have sufficient CPU and memory resources to run successfully.

* The container must be runnable.

* The container must have network access if its startup process requires external communication.

* Install Docker daemon `dockerd` on the host. It must be runnable and accessible to the current user without elevated privileges. For example, `docker images` should work without `sudo`.

* The negotiated Docker API version between the client and server must be `1.48` or higher.

* Run the scan on either a Linux or macOS host machine. Container reachability supports both amd64 and arm64 architectures.

## Determine container reachability

Endor Labs determines container reachability by extracting OS packages from the container image, profiling the container's runtime behavior, and correlating the results to identify which dependencies are actually used during execution. The following steps describe how Endor Labs determines container reachability.

1. **Dependency extraction** - Endor Labs extracts all packages and dependencies present in the container image by analyzing its file system to identify installed OS packages and their file path locations within the image.

2. **Dynamic profiling** - Endor Labs runs the container image in a controlled environment, monitoring which OS-level files and dependencies the application accesses during execution. The profiling captures runtime behavior including system calls, process IDs, and file path access patterns. It also identifies the main process that starts the container as the entry point and uses it to determine which packages are reachable through their dependency relationships.

3. **Path matching and reachability determination** - Endor Labs correlates the results from both steps by comparing the extracted dependency file paths against the file access patterns captured during profiling, then assigns each dependency a reachability status based on whether the application accessed it during execution.

Run the following command with the `--os-reachability` flag to include container reachability analysis in the scan.

```bash theme={null}
endorctl container scan \
  --namespace=<your-namespace> \
  --image=<image_name:tag> \
  --project-name=<endor_project_name> \
  --os-reachability
```

You can also run container scans with OS reachability using GitHub Actions. See [Scan containers with OS reachability](/setup-deployment/ci-cd/scan-with-github-actions#scan-containers-with-os-reachability) for details.

### Image qualification

Before dynamic profiling begins, Endor Labs performs a series of image qualification checks to determine if the container image is suitable for profiling. These checks include:

* **Image size** - Verifies that the uncompressed image size does not exceed the configured limit. The default limit is 10 GB. Use the `--profiling-max-size` flag to adjust this limit.

* **Runnability** - Runs the container to check that it starts without errors. If the container exits with an error, the error details appear in the CLI output and surfaced in the Endor Labs user interface.

If the image fails any of the qualification checks, the scan skips dynamic profiling and proceeds without reachability analysis.

### Container reachability options

You can run the `endorctl container scan --os-reachability` command with the following options.

<YamlTable>
  {`


    - Flag: \`--volume\`
    Environment_Variable: \`ENDOR_CONTAINER_SCAN_VOLUME\`
    Type: string
    Description: Bind mount a volume for the container during profiling, for example, \`--volume=/host/path:/container/path\`.
    - Flag: \`--publish\`
    Environment_Variable: \`ENDOR_CONTAINER_SCAN_PUBLISH\`
    Type: string
    Description: Publish a container's port to the host for profiling in the format \`host_port:container_port\`, for example, \`--publish=8080:80\`.
    - Flag: \`--env\`
    Environment_Variable: \`ENDOR_CONTAINER_SCAN_ENV\`
    Type: string
    Description: Set environment variables for the container during profiling.
    - Flag: \`--entrypoint\`
    Environment_Variable: \`ENDOR_CONTAINER_SCAN_ENTRYPOINT\`
    Type: string
    Description: Override the container entry point for profiling, for example, \`--entrypoint=/app/start.sh\`.
    - Flag: \`--profiling-max-size\`
    Environment_Variable: \`ENDOR_CONTAINER_SCAN_PROFILING_MAX_SIZE\`
    Type: integer
    Description: Set the maximum allowed container image size in GB for dynamic profiling, for example, \`--profiling-max-size=15\`. The default is 10 GB and the minimum is 1 GB.
    - Flag: \`profiling-data-dir\`
    Environment_Variable: \`ENDOR_CONTAINER_SCAN_PROFILING_DATA_DIR\`
    Type: string
    Description: Path to a directory containing profiling data from an instrumented run. You can pass one or more directories. Requires \`--os-reachability\`.


    `}
</YamlTable>

## Container reachability status

The container reachability status indicates whether the application used a dependency during runtime profiling or whether Endor Labs could not determine its usage.

* **Reachable** - The dependency is observed in runtime signals or confidently inferred through correlation analysis.

* **Potentially reachable** - The dependency has not been observed during profiling, and there is no correlation evidence of its usage. However, its usage cannot be definitively ruled out without additional analysis, such as extended runtime monitoring.

* **Unreachable** - The dependency was not observed during profiling and has no path from the container image's entry point to it.

### Recommended approach for prioritizing remediation

Use reachability information to prioritize vulnerability remediation effectively. The following table provides recommended actions based on the combination of vulnerability severity and reachability status.

<YamlTable>
  {`


    - Severity: Critical
    Reachability_Status: Reachable
    Recommended_Action: Remediate immediately to mitigate active, high-risk vulnerabilities.
    - Severity: High
    Reachability_Status: Reachable
    Recommended_Action: Prioritize remediation as soon as possible.
    - Severity: Critical
    Reachability_Status: Potentially Reachable
    Recommended_Action: Review and verify reachability before scheduling remediation.
    - Severity: Medium or Low
    Reachability_Status: Reachable
    Recommended_Action: Plan remediation as part of regular maintenance activities.
    - Severity: Medium or Low
    Reachability_Status: Potentially Reachable
    Recommended_Action: Low priority, monitor and reassess as needed.


    `}
</YamlTable>

## View container reachability results

After running a container scan with reachability analysis, you can view the profiling status, reachability results, and error details for each container image in the Endor Labs user interface.

1. Select **Inventory** from the left sidebar.

2. Select **Containers**.

3. Select a project. The **Container Reachability Status** column shows the profiling status for each container image.

   <Note>
     **Important**

     The following table describes the status icons for container reachability.

     | Status                                    | Description                                               |
     | ----------------------------------------- | --------------------------------------------------------- |
     | <span style={{color: '#28a745'}}>●</span> | Container profiling succeeded.                            |
     | <span style={{color: '#dc3545'}}>▲</span> | Container profiling failed.                               |
     | N/A                                       | Container was not profiled.                               |
     | <span style={{color: '#ffc107'}}>⊖</span> | Container image requires additional configuration to run. |
   </Note>

4. Select a container image to view its details.

5. Select **Reachability Analysis** to view profiling details such as status, profiling type, application type, duration, and entry point package.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/rtiT4oc3TglKX_HY/images/scan/containers/container-reachability-success.webp?fit=max&auto=format&n=rtiT4oc3TglKX_HY&q=85&s=d88d51d5115969747fd557387ccf3310" alt="Reachability analysis with successful container profiling" style={{ width: '60%' }} width="1064" height="944" data-path="images/scan/containers/container-reachability-success.webp" />

   If profiling failed, the error details are also displayed.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/rtiT4oc3TglKX_HY/images/scan/containers/container-reachability-error.webp?fit=max&auto=format&n=rtiT4oc3TglKX_HY&q=85&s=ebc964a94f45002cc896f5733594f9ee" alt="Reachability analysis with profiling error details" style={{ width: '80%' }} width="1052" height="1052" data-path="images/scan/containers/container-reachability-error.webp" />

6. Select **View Details** to inspect the findings associated with the container image. Each finding displays the reachability status of its dependency as an attribute label.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/rtiT4oc3TglKX_HY/images/scan/containers/reachability-labels-in-findings.webp?fit=max&auto=format&n=rtiT4oc3TglKX_HY&q=85&s=26af850d57828efbf4e3c2d15ca0a7c1" alt="Findings with reachability attribute labels" style={{ width: '80%' }} width="2296" height="1304" data-path="images/scan/containers/reachability-labels-in-findings.webp" />

## Filter container findings by reachability

You can filter findings across all projects by their reachability status.

1. Select **Findings** from the left sidebar.
2. Select **Attributes**, and in the **Reachable Dependency** filter, select **Yes**, **Potentially**, or **No** to narrow down findings by reachability status.

## Limitations

Keep the following limitations in mind when using container reachability analysis:

* **Code coverage**: The scan does not detect dependencies accessed after the profiling window ends.

* **OS packages**: Container reachability analysis applies only to OS-level packages. It identifies which OS packages the application uses at runtime but does not analyze specific vulnerable functions within those packages. Use [Software Composition Analysis reachability](/scan/sca/reachability-analysis) to assess the runtime relevance of application dependencies.

* **Windows not supported**: Container scanning and reachability are not supported on Windows.

* **Tar image paths**: Dynamic profiling is not supported for container images referenced with a tar path.

## Troubleshoot issues

<AccordionGroup>
  <Accordion title="Container fails to start and crashes during profiling">
    * Verify that the container image runs successfully by using the `docker run` command.

    * Check whether the container requires specific environment variables or mounted volumes to start correctly. Use the `--env` and `--volume` flags to provide them during the scan.

    * Ensure that the container does not depend on interactive input during startup or execution.

    * Use the `--entrypoint` flag to override the container entry point if the default entry point causes issues.
  </Accordion>

  <Accordion title="Container profiling times out due to slow container startup or execution">
    * Check the container's startup performance to ensure it initializes within the expected time frame.

    * Verify that the container has network connectivity if it depends on external services.

    * Review the container logs to identify any errors or issues that occur during startup.
  </Accordion>

  <Accordion title="Are Windows containers and container reachability on Windows supported?">
    No. Container reachability is not supported on Windows hosts or for Windows container images.
  </Accordion>

  <Accordion title="Profiling is skipped because the image is too large">
    * Check whether the uncompressed image size exceeds the configured limit. The default limit is 10 GB.

    * Use the `--profiling-max-size` flag to increase the limit if needed.
  </Accordion>

  <Accordion title="Known active dependencies are shown as potentially reachable">
    **Possible Causes**:

    * The application might access the dependency only after the profiling window ends.

    * The dependency may require specific HTTP endpoints or actions that are not triggered during profiling.

    * The dependency might be loading slowly or initialized only under specific conditions.

    * Startup optimizations may delay the actual use of the dependency until after profiling completes.

    * The dependency may not have a direct path from the container image's entry point.

    **Solutions**:

    * Review the typical application startup time to determine whether the application loads dependencies later in the process.

    * Consider whether specific operations or workflows trigger the use of these dependencies.

    * Use the `--env`, `--volume`, or `--publish` flags to provide the container with the configuration it may require to exercise more code paths during profiling.

    * Use container reachability results together with threat modeling to better assess overall security risk.
  </Accordion>
</AccordionGroup>
