> ## 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.

# Configure the Package Firewall with Google Artifact Registry

> Route package installation requests through the Package Firewall by configuring Google Artifact Registry remote repositories.

export const LicenseBadge = ({sku, skus, relation = 'any'}) => {
  const DATA_URL = '/snippets/license-sku-data.json';
  const CACHE_KEY = 'license-sku-data';
  const CACHE_TTL_MS = 60 * 60 * 1000;
  const REGISTRY_KEY = '__licenseSkuRegistry';
  const FALLBACK_LICENSES_URL = '/introduction/licenses';
  const ACCENT = '#26D07C';
  const CONJUNCTION = {
    any: 'or',
    all: 'and'
  };
  const FONT_STACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif';
  const [isDark, setIsDark] = useState(false);
  const [data, setData] = useState(null);
  const [hasFetchError, setHasFetchError] = useState(false);
  const skuList = useMemo(() => {
    if (Array.isArray(skus) && skus.length > 0) {
      return skus.filter(code => typeof code === 'string' && code.trim()).map(c => c.trim());
    }
    if (typeof sku === 'string' && sku.trim()) {
      return [sku.trim()];
    }
    return [];
  }, [sku, skus]);
  useEffect(() => {
    const check = () => {
      const root = document.documentElement;
      setIsDark(root.dataset.theme === 'dark' || root.classList.contains('dark'));
    };
    check();
    const observer = new MutationObserver(check);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['data-theme', 'class']
    });
    return () => observer.disconnect();
  }, []);
  useEffect(() => {
    let cancelled = false;
    const readCache = () => {
      try {
        const raw = sessionStorage.getItem(CACHE_KEY);
        if (!raw) return null;
        const parsed = JSON.parse(raw);
        if (Date.now() - parsed.ts > CACHE_TTL_MS) {
          sessionStorage.removeItem(CACHE_KEY);
          return null;
        }
        return parsed.data;
      } catch (e) {
        return null;
      }
    };
    const writeCache = value => {
      try {
        sessionStorage.setItem(CACHE_KEY, JSON.stringify({
          ts: Date.now(),
          data: value
        }));
      } catch (e) {}
    };
    const fetchSkuData = async () => {
      const cached = readCache();
      if (cached) return cached;
      if (!globalThis[REGISTRY_KEY]) {
        globalThis[REGISTRY_KEY] = {};
      }
      const registry = globalThis[REGISTRY_KEY];
      if (registry[CACHE_KEY]) return registry[CACHE_KEY];
      const promise = (async () => {
        const resp = await fetch(DATA_URL);
        if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
        const json = await resp.json();
        writeCache(json);
        return json;
      })();
      registry[CACHE_KEY] = promise;
      promise.finally(() => {
        delete registry[CACHE_KEY];
      });
      return promise;
    };
    fetchSkuData().then(d => {
      if (!cancelled) setData(d);
    }).catch(() => {
      if (!cancelled) setHasFetchError(true);
    });
    return () => {
      cancelled = true;
    };
  }, []);
  const textColor = isDark ? '#e6edf3' : '#1f2937';
  const textMuted = isDark ? 'rgba(230,237,243,0.65)' : 'rgba(31,41,55,0.65)';
  const bannerBackground = isDark ? '#161b22' : '#f6f8fa';
  const borderColor = isDark ? 'rgba(38,208,124,0.35)' : 'rgba(38,208,124,0.45)';
  const linkColor = isDark ? '#4ade80' : '#047857';
  const errorBackground = isDark ? '#3b1111' : '#fef2f2';
  const errorBorder = isDark ? '#7f1d1d' : '#fecaca';
  const errorText = isDark ? '#fecaca' : '#7f1d1d';
  const licensesUrl = data?.licensesPageUrl || FALLBACK_LICENSES_URL;
  const resolveSkuName = code => {
    const entry = data?.skus?.[code];
    if (entry?.name) return entry.name;
    return null;
  };
  const formatSkuLabel = code => {
    const name = resolveSkuName(code);
    if (name) return name;
    return code;
  };
  const joinWithConjunction = (codes, conjunction) => {
    const labels = codes.map(formatSkuLabel);
    if (labels.length === 0) return '';
    if (labels.length === 1) return labels[0];
    if (labels.length === 2) return `${labels[0]} ${conjunction} ${labels[1]}`;
    const head = labels.slice(0, -1).join(', ');
    const tail = labels[labels.length - 1];
    return `${head}, ${conjunction} ${tail}`;
  };
  const buildSkuSentence = codes => {
    const conj = CONJUNCTION[relation] || CONJUNCTION.any;
    const names = joinWithConjunction(codes, conj);
    const noun = codes.length > 1 ? 'licenses' : 'license';
    return `${names} ${noun}`;
  };
  const renderLink = text => <a href={licensesUrl} className="lic-link" style={{
    color: linkColor,
    fontSize: '0.75rem',
    fontWeight: 500,
    textDecoration: 'none',
    whiteSpace: 'nowrap'
  }}>
      {text} →
    </a>;
  const renderBanner = codes => <div className="lic-banner not-prose" style={{
    margin: '1rem 0',
    padding: '0.5rem 0.85rem',
    background: bannerBackground,
    border: `1px solid ${borderColor}`,
    borderLeft: `3px solid ${ACCENT}`,
    borderRadius: '6px',
    color: textColor,
    fontSize: '0.75rem',
    display: 'flex',
    alignItems: 'center',
    gap: '0.5rem',
    flexWrap: 'wrap',
    fontFamily: FONT_STACK,
    lineHeight: 1.5
  }}>
      <span style={{
    flex: 1,
    minWidth: 0
  }}>
        <span style={{
    color: textMuted,
    marginRight: '0.3rem'
  }}>Requires</span>
        <span style={{
    fontWeight: 600
  }}>{buildSkuSentence(codes)}</span>
      </span>
      {renderLink('Licenses')}
    </div>;
  const renderLoading = () => <div className="not-prose" style={{
    margin: '1rem 0',
    padding: '0.6rem 0.9rem',
    background: bannerBackground,
    border: `1px dashed ${borderColor}`,
    borderRadius: '6px',
    color: textMuted,
    fontSize: '0.85rem',
    fontStyle: 'italic',
    fontFamily: FONT_STACK
  }} role="status" aria-live="polite">
      Loading license info…
    </div>;
  const renderInputError = message => <div className="not-prose" style={{
    margin: '1rem 0',
    padding: '0.6rem 0.9rem',
    background: errorBackground,
    border: `1px solid ${errorBorder}`,
    borderRadius: '6px',
    color: errorText,
    fontSize: '0.85rem',
    fontFamily: FONT_STACK
  }} role="alert">
      {message}
    </div>;
  if (typeof sku === 'string' && Array.isArray(skus)) {
    return renderInputError('LicenseBadge: pass either `sku` or `skus`, not both.');
  }
  if (skuList.length === 0) {
    return renderInputError('LicenseBadge: `sku` or `skus` is required.');
  }
  if (hasFetchError) return renderBanner(skuList);
  if (!data) return renderLoading();
  return renderBanner(skuList);
};

export const authTarget_0 = "Google Artifact Registry"

<LicenseBadge sku="EL-OSS-FWAL" />

Configure Google Artifact Registry to use the Package Firewall URL as the upstream source for a remote repository instead of the public package registries. Every package installation request flows through Endor Labs, so the Package Firewall can block known malicious packages before they reach your environment.

The Package Firewall evaluates each package request based on the malware check and the configured [Package Firewall policy](/package-firewall/policy) conditions, and handles each request in one of three ways:

* Block the installation and Google Artifact Registry returns `HTTP 403` if the package is found in the Endor Labs malware database, or if a policy condition matches with **Block**. The Package Firewall records a log with the package, version, and reason.

* Allow the installation if a policy condition matches with **Warn**. The Package Firewall records a warning log with the package, version, and reason.

* Allow the installation if the package passes all checks. No log is recorded.

Google Artifact Registry returns a generic `HTTP 403` when it blocks a request without surfacing the specific reason. To see why a package was blocked, review the [Package Firewall logs](/package-firewall/logs).

## Required Google Cloud permissions

Ensure that you have the following roles in the Google Cloud project that hosts Artifact Registry:

* [Artifact Registry Administrator](https://cloud.google.com/artifact-registry/docs/access-control) to create remote repositories.
* [Secret Manager Admin](https://cloud.google.com/secret-manager/docs/access-control) to create the secret and grant Artifact Registry access to it.
* [Service Usage Admin](https://cloud.google.com/service-usage/docs/access-control) to enable the Secret Manager API.

A project **Owner** or **Editor** role covers all of these.

## Configure the Package Firewall

Complete the following steps to integrate Google Artifact Registry with the Endor Labs Package Firewall:

1. [Create an API key for the Package Firewall](#create-an-api-key-for-the-package-firewall).
2. [Store the API secret in Secret Manager](#store-the-api-secret-in-secret-manager).
3. [Configure Google Artifact Registry](#configure-google-artifact-registry).
4. [Set up local package managers](#set-up-local-package-managers).
5. [Verify your setup](#verify-your-setup).

### Create an API key for the Package Firewall

Create an API key dedicated to the Package Firewall so that {authTarget_0} can authenticate to it. You can create it through one of the following methods:

* Using the Endor Labs user interface, with the **Package Firewall User** role. See [API keys](/platform-administration/api-keys#create-an-api-key-through-the-endor-labs-user-interface) to learn more.
* Using endorctl, with the `SYSTEM_ROLE_PACKAGE_FIREWALL` role. Make sure to [install and configure endorctl](/developers-api/cli/install-and-configure) before you create the key.

To create the key using endorctl, run the following command and replace:

* `<namespace>` with your namespace.
* `<API key name>` with the name of the API key for the Package Firewall use case.
* `<YYYY-MM-DDTHH:MM:SSZ>` with the API key expiration in ISO 8601 UTC format, for example `2026-12-31T23:59:59Z`.

```bash theme={null}
export NAMESPACE="<namespace>"
export KEY_NAME="<API key name>"

endorctl api create -r APIKey -n "$NAMESPACE" --data '{
  "meta": { "name": "'"$KEY_NAME"'" },
  "spec": {
    "permissions": { "roles": ["SYSTEM_ROLE_PACKAGE_FIREWALL"] },
    "expiration_time": "<YYYY-MM-DDTHH:MM:SSZ>"
  },
  "propagate": true
}'
```

From the response, save the following values in a secure location.

* **API key:** `spec.key`
* **API secret:** `spec.secret`

### Store the API secret in Secret Manager

Google Artifact Registry reads the upstream password from Secret Manager, so you must store your API secret there before you configure the remote repository.

1. Sign in to the [Google Cloud console](https://console.cloud.google.com) and select your project.
2. Search for and select **Secret Manager API**.
3. Click **Enable**. If you see **Manage** instead, the API is already enabled.
4. Search for and select **Secret Manager**.
5. Create a secret with a name, such as `endor-pkg-firewall-secret`, and set the **Secret value** to your API secret. Refer to [Google Cloud documentation](https://cloud.google.com/secret-manager/docs/create-secret-quickstart#create_a_secret_and_access_a_secret_version) to learn how to create a secret.

### Configure Google Artifact Registry

Configure a remote repository in Google Artifact Registry for each package type you want to route through the Package Firewall. A remote repository proxies an upstream source, so you set the Package Firewall URL as the custom upstream. Provide your API key as the username and the Secret Manager secret as the password.

The remaining repository settings, such as location and cleanup policies, are specific to Google Artifact Registry. Configure them based on your requirements.

<Note>
  Google Artifact Registry does not support custom upstream URL for Go remote repositories, so you cannot route Go modules through the Package Firewall with this integration. To route Go modules through the Package Firewall, use [JFrog Artifactory](/package-firewall/jfrog-artifactory) or [direct integration](/package-firewall/direct-integration).
</Note>

<AccordionGroup>
  <Accordion title="Configure Google Artifact Registry for an npm remote repository">
    1. In the Google Cloud console, go to **Artifact Registry** > **Repositories**.
    2. Click **Create Repository**.
    3. Enter a repository name, such as `endor-npm-repository`.
    4. Choose **npm** as the **Format**.
    5. Choose **Custom** in **Remote repository source**.
    6. Enter `https://factory.endorlabs.com/v1/namespaces/<namespace>/firewall/npm` as the custom repository URL. Replace `<namespace>` with your Endor Labs namespace.
    7. Choose **Authenticated** in **Remote repository authentication mode**.
    8. Enter your API key (`spec.key`) as the **Username for the upstream repository**.
    9. Under **Store your credentials in Secret Manager**, select the **Secret** you created.
    10. Under **Location type**, choose **Region** or **Multi-Region** and select a location. Refer to [Repository locations](https://cloud.google.com/artifact-registry/docs/repositories/repository-locations) for details.
    11. Click **Create**.
  </Accordion>

  <Accordion title="Configure Google Artifact Registry for a Python remote repository">
    1. In the Google Cloud console, go to **Artifact Registry** > **Repositories**.
    2. Click **Create Repository**.
    3. Enter a repository name, such as `endor-pypi-repository`.
    4. Choose **Python** as the **Format**.
    5. Choose **Custom** in **Remote repository source**.
    6. Enter `https://factory.endorlabs.com/v1/namespaces/<namespace>/firewall/pypi` as the custom repository URL. Replace `<namespace>` with your Endor Labs namespace.
    7. Choose **Authenticated** in **Remote repository authentication mode**.
    8. Enter your API key (`spec.key`) as the **Username for the upstream repository**.
    9. Under **Store your credentials in Secret Manager**, select the **Secret** you created.
    10. Under **Location type**, choose **Region** or **Multi-Region** and select a location. Refer to [Repository locations](https://cloud.google.com/artifact-registry/docs/repositories/repository-locations) for details.
    11. Click **Create**.
  </Accordion>

  <Accordion title="Configure Google Artifact Registry for a Maven remote repository">
    1. In the Google Cloud console, go to **Artifact Registry** > **Repositories**.
    2. Click **Create Repository**.
    3. Enter a repository name, such as `endor-maven-repository`.
    4. Choose **Maven** as the **Format**.
    5. Choose **Custom** in **Remote repository source**.
    6. Enter `https://factory.endorlabs.com/v1/namespaces/<namespace>/firewall/maven` as the custom repository URL. Replace `<namespace>` with your Endor Labs namespace.
    7. Choose **Authenticated** in **Remote repository authentication mode**.
    8. Enter your API key (`spec.key`) as the **Username for the upstream repository**.
    9. Under **Store your credentials in Secret Manager**, select the **Secret** you created.
    10. Under **Location type**, choose **Region** or **Multi-Region** and select a location. Refer to [Repository locations](https://cloud.google.com/artifact-registry/docs/repositories/repository-locations) for details.
    11. Click **Create**.
  </Accordion>
</AccordionGroup>

### Set up local package managers

Configure your local package managers to use the Google Artifact Registry repository as their source, and authenticate with your Google Cloud credentials. Refer to Google's client setup documentation for [npm](https://cloud.google.com/artifact-registry/docs/nodejs/store-nodejs), [Python](https://cloud.google.com/artifact-registry/docs/python/store-python), and [Maven](https://cloud.google.com/artifact-registry/docs/java/store-java).

### Verify your setup

To verify your setup, install a package that Endor Labs has classified as malware. The Package Firewall should block the installation and return an `HTTP 403`.

The following are examples of packages classified as malware by Endor Labs.

<AccordionGroup>
  <Accordion title="npm">
    Run the following command to test the Package Firewall with npm.

    ```bash theme={null}
    npm install endor-firewall-test
    ```

    When the Package Firewall blocks the package, the output looks similar to the following. The `E403` error code and `403 Forbidden` response confirm that the firewall blocked the package.

    ```bash theme={null}
    npm error code E403
    npm error 403 403 Forbidden - GET https://us-central1-npm.pkg.dev/johndoe-project/johndoe-npm/endor-firewall-test/-/endor-firewall-test-1.0.0.tgz - 403 Forbidden error returned by the external repository... this typically means your upstream credentials are invalid; url="https://factory.endorlabs.com/v1/namespaces/johndoe/firewall/npm/endor-firewall-test/-/endor-firewall-test-1.0.0.tgz"
    npm error 403 In most cases, you or one of your dependencies are requesting
    npm error 403 a package version that is forbidden by your security policy, or
    npm error 403 on a server you do not have access to.
    ```
  </Accordion>

  <Accordion title="PyPI">
    Run the following command to test the Package Firewall with PyPI.

    ```bash theme={null}
    pip install endor-firewall-test
    ```

    When the Package Firewall blocks the package, the output looks similar to the following. The `403` response confirms that the firewall blocked the package.

    ```bash theme={null}
    Looking in indexes: https://pypi.org/simple, https://oauth2accesstoken:****@us-central1-python.pkg.dev/johndoe-project/johndoe-pypi/simple/
    Collecting endor-firewall-test
    ERROR: HTTP error 403 while getting https://us-central1-python.pkg.dev/johndoe-project/johndoe-pypi/endor-firewall-test/endor_firewall_test-1.0.0-py3-none-any.whl#sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 (from https://us-central1-python.pkg.dev/johndoe-project/johndoe-pypi/simple/endor-firewall-test/) (requires-python:>=3.7)

    ERROR: Could not install requirement endor-firewall-test from https://us-central1-python.pkg.dev/johndoe-project/johndoe-pypi/endor-firewall-test/endor_firewall_test-1.0.0-py3-none-any.whl#sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 because of HTTP error 403 Client Error: Forbidden for url: https://us-central1-python.pkg.dev/johndoe-project/johndoe-pypi/endor-firewall-test/endor_firewall_test-1.0.0-py3-none-any.whl for URL https://us-central1-python.pkg.dev/johndoe-project/johndoe-pypi/endor-firewall-test/endor_firewall_test-1.0.0-py3-none-any.whl#sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 (from https://us-central1-python.pkg.dev/johndoe-project/johndoe-pypi/simple/endor-firewall-test/) (requires-python:>=3.7)
    ```
  </Accordion>

  <Accordion title="Maven">
    Add `io.github.endorlabs:endor-java-webapp-demo:4.1` as a dependency in your `pom.xml`, then run the following command to test the Package Firewall with Maven.

    ```bash theme={null}
    mvn dependency:resolve -s settings.xml
    ```

    When the Package Firewall blocks the package, the output looks similar to the following. The `403 Forbidden` response confirms that the firewall blocked the package.

    ```bash theme={null}
    Downloading from endor-firewall-gar: https://us-central1-maven.pkg.dev/johndoe-project/johndoe-maven/io/github/endorlabs/endor-java-webapp-demo/4.1/endor-java-webapp-demo-4.1.pom
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD FAILURE
    [INFO] ------------------------------------------------------------------------
    [ERROR] Failed to execute goal on project endor-firewall-maven-test: Could not resolve dependencies for project com.example:endor-firewall-maven-test:jar:1.0.0: Failed to collect dependencies at io.github.endorlabs:endor-java-webapp-demo:jar:4.1: Failed to read artifact descriptor for io.github.endorlabs:endor-java-webapp-demo:jar:4.1: Could not transfer artifact io.github.endorlabs:endor-java-webapp-demo:pom:4.1 from/to endor-firewall-gar (https://us-central1-maven.pkg.dev/johndoe-project/johndoe-maven): authorization failed for https://us-central1-maven.pkg.dev/johndoe-project/johndoe-maven/io/github/endorlabs/endor-java-webapp-demo/4.1/endor-java-webapp-demo-4.1.pom, status: 403 Forbidden -> [Help 1]
    ```
  </Accordion>
</AccordionGroup>

After you confirm that the Package Firewall blocks malware, you can view the recorded events. See [View Package Firewall logs](/package-firewall/logs) to learn more.

## Troubleshooting and FAQ

<AccordionGroup>
  <Accordion title="What if the repository fails to create?">
    Google Artifact Registry validates the upstream URL and credentials when you create a remote repository, and creates it only if the test passes. If it fails, check that the custom repository URL, upstream username, and Secret Manager secret are correct.

    To skip the check, select **Disable upstream validation** on the form.
  </Accordion>

  <Accordion title="What if Google Artifact Registry has already cached a package that is declared malicious later?">
    If Endor Labs flags a package as malware after Google Artifact Registry cached it, Google Artifact Registry continues to serve it until the cache expires. Use a short cache duration to reduce that window.
  </Accordion>

  <Accordion title="How do I troubleshoot connection issues?">
    * Verify that the Package Firewall URL set as the custom upstream is correct.
    * Ensure network connectivity from Google Artifact Registry to the Package Firewall.
    * Ensure the firewall rules allow outbound connections from Google Artifact Registry.
  </Accordion>

  <Accordion title="How do I troubleshoot authentication issues?">
    * Verify the API key and secret are correct and that the key has the **Package Firewall User** role.
    * Verify that the Secret Manager secret holds the API secret and that the repository can access it.
  </Accordion>

  <Accordion title="How do I troubleshoot cache issues?">
    * Set cache expiration to short durations so that more requests hit the Package Firewall.
    * Check the cache hit and miss rates. Clear the cache if you need to test with a fresh request.
  </Accordion>
</AccordionGroup>
