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

# Prerequisites for Coding Agent Governance

> Set up the role, API key, and tenant settings an admin needs before any developer machine runs a Coding Agent Governance hook.

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

<LicenseBadge sku="EL-AGNT-GOV" />

Coding Agent Governance is admin-driven. Before a developer machine can send a single event, an admin must turn on the feature and issue an API key with the right role. The admin then rolls hooks out to the supported coding agents in each developer environment. In-built policies ship enabled, so basic enforcement starts as soon as hooks run. You review the catalog and add custom rules after deployment.

Complete the following tasks to set up Coding Agent Governance.

1. Confirm Coding Agent Governance is available in your tenant.
2. Create an API key with the **AI Audit User** role to ship with hooks.
3. Deploy hooks on developer machines. See [Deploy hooks for Cursor](/agent-governance/cursor), [Claude Code](/agent-governance/claude-code), [Codex](/agent-governance/codex), and [GitHub Copilot](/agent-governance/copilot).
4. Review in-built policies and write any custom rules your organization needs. See [Write a policy](/agent-governance/policies).

Skipping any step delays results. Inventory and policy enforcement both need hooks to run. The in-built policy catalog enforces as soon as hooks ship. Custom policies layer on top for organization-specific rules.

## Confirm Coding Agent Governance is available

Select **Agent Governance** from the left sidebar. If the entry is missing, contact your account team to enable Coding Agent Governance for your tenant.

## Create an API key with the AI Audit User role

The **AI Audit User** role is a least-privilege system role for Coding Agent Governance hooks. An API key with this role can submit hook events, read your policy snapshot for local evaluation, read the Coding Agent Governance inventory, and write operational client logs. It cannot read policy violations or any Endor Labs data outside Coding Agent Governance.

<Warning>
  Treat the API key and secret as production credentials. Any host with these values can submit hook events as your tenant.
</Warning>

### Create the API key through the user interface

1. Select **Settings** > **Access Control** from the left sidebar.
2. Select **API Keys**, then click **Generate API Key**.
3. Enter a name that identifies the key, such as `agent-governance-hooks`.
4. Under **Role**, choose **AI Audit User**.
5. Choose an expiration that matches your rotation policy.
6. Click **Generate**, then copy the key and secret. Store both in your secret manager. Endor Labs shows the secret only once.

See [Manage API keys](/platform-administration/api-keys) to manage this key over time.

### Create the API key with endorctl

For MDM-driven rollouts or automated provisioning, create the key with [endorctl](/setup-deployment/cli). Replace `<namespace>` with the namespace that owns your Coding Agent Governance policies, `<key-name>` with a descriptive name for the key, and `<YYYY-MM-DDTHH:MM:SSZ>` with the expiration in ISO 8601 UTC format.

```bash theme={null}
endorctl api create -r APIKey -n "<namespace>" --data '{
  "meta": { "name": "<key-name>" },
  "spec": {
    "permissions": { "roles": ["SYSTEM_ROLE_AI_AUDIT"] },
    "expiration_time": "<YYYY-MM-DDTHH:MM:SSZ>"
  },
  "propagate": true
}'
```

The response contains the API key and secret under `spec.key` and `spec.secret`. Capture both before the response scrolls out of your terminal. Endor Labs returns the secret only once.

See [Create an API key through Endor Labs API](/platform-administration/api-keys#create-an-api-key-through-endor-labs-api) for the full API reference, including longer expirations and namespace propagation.

## Data collection

Each hook event carries the metadata Endor Labs needs for inventory and policy evaluation:

* Agent name, version, and (where available) model
* Session and sub-session IDs
* Device (endpoint) identifier
* Session configuration, such as permission mode and sandbox state
* User identifier when the agent provides one
* Repository name (not populated for Claude Code sessions) and working directory
* MCP server name, tool name, and call counts
* Shell command line (regex-matched against your policies)
* File path and operation type (read, write, delete, edit)
* Secrets check match (rule, line, column, and fingerprint, never the secret value)
* Policy match outcome and the action applied

### Secret redaction

Endor Labs runs every command line, file path, URL, environment value, and MCP tool input or output through a regex-based redactor before it leaves the developer's machine. Environment variables and keys named like a secret (such as `TOKEN`, `SECRET`, `API_KEY`, `PASSWORD`, or `AUTHORIZATION`) become `[REDACTED]`. The redactor also matches common secret formats in place: URL and database-connection credentials, AWS access keys, JWTs, PEM private keys, and provider tokens such as GitHub (`ghp_*`), OpenAI, and Anthropic keys.

Redaction of event payloads is best-effort regex pattern matching. A secret that does not match a known pattern or key name can still reach the backend. The [secrets check](/agent-governance/secure-coding) is separate from redaction: it detects secrets in file content on the developer's machine, and its violations carry the rule and location, never the value.

<Warning>
  On Cursor, the `sessionStart` hook returns the Endor Labs credential values to Cursor in plaintext under an `AGENT_HOOK_ENDOR_*` prefix, so subsequent hooks in the session inherit them. The values never leave the developer's machine, but anything that captures Cursor hook stdout (such as debug logging or screen recording) can see them.
</Warning>

## Distribute credentials to developer machines

Hooks read credentials from environment variables wherever they run:

* `ENDOR_API`: The Endor Labs API endpoint, for example `https://api.endorlabs.com`.
* `ENDOR_NAMESPACE`: The Endor Labs namespace that owns the policies and receives the events.
* `ENDOR_API_CREDENTIALS_KEY`: The API key value.
* `ENDOR_API_CREDENTIALS_SECRET`: The API secret.

For Cursor, Codex, and GitHub Copilot, set the variables in the environment that launches the agent. For Claude Code, set them in the `env` block of `settings.json`, as shown on [Deploy hooks for Claude Code](/agent-governance/claude-code). Use your MDM (Jamf, Intune, Kandji, JumpCloud) to deliver the values, and to ensure endorctl is on `PATH`, on every machine that runs an Endor Labs hook.

Do not distribute `ENDOR_TOKEN` alongside these variables. Hooks authenticate with the API key and secret only, and on older versions of endorctl the extra token triggered a mixed-authentication error that blocked `endorctl ai-audit`.

See [endorctl CLI](/setup-deployment/cli) to install endorctl.

## Confirm supported agents and platforms

Coding Agent Governance hooks support Cursor, Claude Code, Codex, and GitHub Copilot on macOS, Linux, and Windows developer machines. See [Supported agents and platforms](/agent-governance#supported-agents-and-platforms) for the full matrix, including hosted and CLI form factors.

## Next steps

After you complete the prerequisites, proceed to the next steps:

* See [Deploy hooks for Cursor](/agent-governance/cursor), [Claude Code](/agent-governance/claude-code), [Codex](/agent-governance/codex), and [GitHub Copilot](/agent-governance/copilot) to roll hooks out.
* See [Write a policy](/agent-governance/policies) to review the in-built catalog and add organization-specific rules.
