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

# Sign artifacts

> Learn how to use Endor Labs to sign container images and build artifacts in the CI pipeline.

export const GithubActionSha = ({action = 'scan', owner = 'endorlabs', repo = 'github-action'}) => {
  const CACHE_TTL = 60 * 60 * 1000;
  const ACTION_LABELS = {
    scan: 'Scan',
    sign: 'Sign',
    verify: 'Verify'
  };
  const actionPath = action === 'scan' ? '' : '/' + action;
  const actionLabel = ACTION_LABELS[action] || action;
  const isFull = action === 'scan';
  const [isDark, setIsDark] = useState(false);
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [copyStates, setCopyStates] = useState({});
  useEffect(() => {
    const check = () => {
      const r = document.documentElement;
      setIsDark(r.dataset.theme === 'dark' || r.classList.contains('dark'));
    };
    check();
    const obs = new MutationObserver(check);
    obs.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['data-theme', 'class']
    });
    return () => obs.disconnect();
  }, []);
  useEffect(() => {
    let cancelled = false;
    const cacheKey = 'gha-sha-' + owner + '/' + repo;
    const regKey = owner + '/' + repo;
    if (!globalThis.__ghaRegistry) {
      globalThis.__ghaRegistry = {};
    }
    const registry = globalThis.__ghaRegistry;
    const getCached = () => {
      try {
        const raw = sessionStorage.getItem(cacheKey);
        if (!raw) return null;
        const cached = JSON.parse(raw);
        if (Date.now() - cached.ts > CACHE_TTL) {
          sessionStorage.removeItem(cacheKey);
          return null;
        }
        return cached.data;
      } catch (e) {
        console.debug('gha: sessionStorage read failed', e);
        return null;
      }
    };
    const setCache = d => {
      try {
        sessionStorage.setItem(cacheKey, JSON.stringify({
          ts: Date.now(),
          data: d
        }));
      } catch (e) {
        console.debug('gha: sessionStorage write failed', e);
      }
    };
    const fetchRelease = async () => {
      const cached = getCached();
      if (cached) return cached;
      if (registry[regKey]) return registry[regKey];
      const promise = (async () => {
        const releaseResp = await fetch('https://api.github.com/repos/' + owner + '/' + repo + '/releases/latest');
        if (!releaseResp.ok) throw new Error('HTTP ' + releaseResp.status);
        const release = await releaseResp.json();
        const tagName = release.tag_name;
        const refResp = await fetch('https://api.github.com/repos/' + owner + '/' + repo + '/git/ref/tags/' + encodeURIComponent(tagName));
        if (!refResp.ok) throw new Error('HTTP ' + refResp.status);
        const ref = await refResp.json();
        let sha = ref.object.sha;
        if (ref.object.type === 'tag') {
          const tagResp = await fetch('https://api.github.com/repos/' + owner + '/' + repo + '/git/tags/' + sha);
          if (tagResp.ok) {
            const tag = await tagResp.json();
            sha = tag.object.sha;
          }
        }
        const result = {
          version: tagName,
          sha: sha
        };
        setCache(result);
        return result;
      })();
      registry[regKey] = promise;
      promise.finally(() => {
        delete registry[regKey];
      });
      return promise;
    };
    fetchRelease().then(d => {
      if (!cancelled) setData(d);
    }).catch(() => {
      if (!cancelled) setError(true);
    });
    return () => {
      cancelled = true;
    };
  }, [owner, repo]);
  const handleCopy = (text, key) => {
    if (!text) return;
    const flashSuccess = () => {
      setCopyStates(prev => ({
        ...prev,
        [key]: true
      }));
      setTimeout(() => setCopyStates(prev => ({
        ...prev,
        [key]: false
      })), 2000);
    };
    const fallbackCopy = () => {
      try {
        const ta = document.createElement('textarea');
        ta.value = text;
        ta.setAttribute('readonly', '');
        ta.style.cssText = 'position:fixed;left:-9999px;opacity:0';
        document.body.appendChild(ta);
        ta.select();
        const ok = document.execCommand('copy');
        ta.remove();
        if (ok) flashSuccess();
      } catch (e) {
        console.debug('gha: fallback copy failed', e);
      }
    };
    if (navigator.clipboard?.writeText) {
      navigator.clipboard.writeText(text).then(flashSuccess).catch(fallbackCopy);
    } else {
      fallbackCopy();
    }
  };
  const bgColor = isDark ? '#0d1117' : '#ffffff';
  const bgLight = isDark ? '#161b22' : '#f6f8fa';
  const textColor = isDark ? '#e6edf3' : '#1f2937';
  const cardBorder = '2px solid rgba(38,208,124,' + (isDark ? '0.4' : '0.5') + ')';
  const cardShadow = '0 2px 8px rgba(38,208,124,' + (isDark ? '0.05' : '0.1') + ')';
  const S = {
    copyBtn: copied => ({
      marginLeft: 'auto',
      padding: '0.25rem 0.5rem',
      background: copied ? '#28a745' : 'linear-gradient(to bottom, rgba(50,225,140,0.92), rgba(30,190,110,0.92))',
      color: isDark ? '#000' : '#fff',
      border: '1px solid rgba(38,208,124,0.5)',
      borderRadius: '6px',
      fontSize: '0.75rem',
      cursor: 'pointer',
      fontWeight: 500,
      whiteSpace: 'nowrap',
      flexShrink: 0
    }),
    infoRow: {
      display: 'flex',
      alignItems: 'center',
      gap: '0.75rem',
      padding: '0.5rem 0.75rem',
      background: bgLight,
      border: '1px solid rgba(38,208,124,0.2)',
      borderRadius: '8px',
      marginBottom: '0.5rem'
    },
    label: {
      fontWeight: 600,
      fontSize: '0.8rem',
      color: textColor,
      minWidth: '5.5rem',
      flexShrink: 0
    },
    value: {
      fontSize: '0.85rem',
      color: textColor,
      flex: 1,
      minWidth: 0,
      overflow: 'hidden',
      textOverflow: 'ellipsis'
    },
    mono: {
      fontFamily: "'Monaco','Menlo','Ubuntu Mono',monospace",
      fontSize: '0.8rem'
    },
    usageBlock: {
      background: bgLight,
      border: '1px solid rgba(38,208,124,0.3)',
      borderRadius: '8px',
      padding: '0.75rem'
    },
    previewHeader: {
      display: 'flex',
      alignItems: 'center',
      color: textColor,
      fontWeight: 600,
      margin: '0 0 0.5rem 0',
      fontSize: '0.85rem'
    },
    codeBlock: {
      background: bgColor,
      color: textColor,
      padding: '0.6rem 0.75rem',
      borderRadius: '6px',
      fontFamily: "'Monaco','Menlo','Ubuntu Mono',monospace",
      fontSize: '0.8rem',
      lineHeight: 1.4,
      overflowX: 'auto',
      margin: 0,
      whiteSpace: 'pre-wrap',
      wordBreak: 'break-all',
      border: '1px solid rgba(38,208,124,0.1)'
    },
    errorBox: {
      marginTop: '0.75rem',
      padding: '0.6rem 0.75rem',
      background: isDark ? '#3b1111' : '#fef2f2',
      border: '1px solid ' + (isDark ? '#7f1d1d' : '#fecaca'),
      borderRadius: '8px',
      color: isDark ? '#fecaca' : '#7f1d1d',
      fontSize: '0.8rem',
      lineHeight: 1.5
    },
    errorLink: {
      color: isDark ? '#fecaca' : '#7f1d1d',
      textDecoration: 'underline'
    },
    loading: {
      color: textColor,
      opacity: 0.5,
      fontStyle: 'italic'
    }
  };
  const refText = data ? owner + '/' + repo + actionPath + '@' + data.sha : null;
  const releasesUrl = 'https://github.com/' + owner + '/' + repo + '/releases/latest';
  const renderUsageBlock = label => <div style={S.usageBlock}>
      <div style={S.previewHeader}>
        <span>{label}</span>
        <button type="button" onClick={() => handleCopy(refText, 'ref')} disabled={!data} style={{
    ...S.copyBtn(copyStates.ref),
    ...data ? {} : {
      opacity: 0.5,
      cursor: 'not-allowed'
    }
  }}>
          {copyStates.ref ? '\u2713 Copied!' : '\uD83D\uDCCB Copy'}
        </button>
      </div>
      <pre style={S.codeBlock}>
        {data ? refText : <span style={S.loading}>Loading...</span>}
      </pre>
    </div>;
  const renderError = compact => <div style={{
    ...S.errorBox,
    ...compact ? {
      marginTop: '0.5rem',
      fontSize: '0.75rem'
    } : {}
  }}>
      <span>{'\u26A0\uFE0F'} Could not fetch the latest commit SHA automatically. </span>
      {compact ? <a href={releasesUrl} target="_blank" rel="noopener" style={S.errorLink}>View releases</a> : <div style={{
    marginTop: '0.4rem'
  }}>
          To find the SHA manually: go to{' '}
          <a href={releasesUrl} target="_blank" rel="noopener" style={S.errorLink}>Releases</a>,
          click the release tag's commit, and copy the full 40-character SHA from the URL.
        </div>}
    </div>;
  if (isFull) {
    return <div className="not-prose" style={{
      margin: '1.5rem 0',
      fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif'
    }}>
        <div style={{
      background: bgColor,
      border: cardBorder,
      borderRadius: '16px',
      padding: '1.25rem',
      color: textColor,
      boxShadow: cardShadow
    }}>
          <div style={{
      marginBottom: '1rem',
      display: 'flex',
      alignItems: 'center',
      gap: '0.75rem'
    }}>
            <span style={{
      color: '#26D07C',
      fontSize: '1.1rem',
      flexShrink: 0
    }}>{'\uD83D\uDEE1\uFE0F'}</span>
            <div>
              <div style={{
      color: textColor,
      marginBottom: '0.25rem',
      fontWeight: 600,
      fontSize: '1.1rem'
    }}>Latest GitHub Action Release</div>
              <p style={{
      color: textColor,
      opacity: 0.8,
      margin: 0,
      fontSize: '0.85rem',
      lineHeight: 1.4
    }}>Pin to an immutable commit SHA for enhanced security.</p>
            </div>
          </div>

          {!error && <div>
              <div style={S.infoRow}>
                <span style={S.label}>Version</span>
                <span style={S.value}>
                  {data ? data.version : <span style={S.loading}>Loading...</span>}
                </span>
              </div>
              <div style={S.infoRow}>
                <span style={S.label}>Commit SHA</span>
                <span style={{
      ...S.value,
      ...S.mono
    }}>
                  {data ? data.sha : <span style={S.loading}>Loading...</span>}
                </span>
                <button type="button" onClick={() => handleCopy(data?.sha, 'sha')} disabled={!data} style={{
      ...S.copyBtn(copyStates.sha),
      ...data ? {} : {
        opacity: 0.5,
        cursor: 'not-allowed'
      }
    }}>
                  {copyStates.sha ? '\u2713 Copied!' : '\uD83D\uDCCB Copy SHA'}
                </button>
              </div>
              <div style={{
      marginTop: '0.75rem'
    }}>
                {renderUsageBlock('Use in your workflow')}
              </div>
            </div>}

          {error && renderError(false)}
        </div>
      </div>;
  }
  return <div className="not-prose" style={{
    margin: '1rem 0',
    fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif'
  }}>
      {!error && renderUsageBlock('Latest ' + actionLabel + ' action \u2014 pin to commit SHA')}
      {error && renderError(true)}
    </div>;
};

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 enhances software supply chain security by providing transparent mechanisms for signing and verifying software artifacts.

* **Integrity of container images and build artifacts:** Using a cryptographic signature ensures that container images and other build artifacts are genuine and crafted by the organization. This adds an extra layer of security to the software supply chain, making sure that only authorized and unaltered items are scheduled for execution.

* **Traces across workflows:** Beyond just verification, the framework offers thorough traceability. Users can trace the roots of container images and build artifacts, navigating through workflows and environments. Complete traceability ensures transparency, enabling organizations to validate the entire lifecycle of their software, from creation to deployment.

* **Certificate validity:** Endor Labs uses a short-lived certificate with a validity period of 5 minutes to ensure that the signer signed the build artifact during this time frame. To further guarantee the signing occurred within the valid window, Endor Labs adds a timestamp alongside the certificate and signature, confirming the signing within the specified time frame.

## Sign artifacts

You can sign artifacts using GitHub Actions or with endorctl.

### Sign using GitHub Action

Use the Endor Labs [GitHub Actions](https://github.com/endorlabs/github-action/blob/main/README.md) to sign artifacts.

1. Set up authentication to Endor Labs.
   * (Recommended) If you are using GitHub Action keyless authentication, set an authorization policy in Endor Labs to allow your organization or repository to authenticate. See [Keyless Authentication](/setup-deployment/ci-cd/keyless-authentication) for more information.
   * Alternatively, authenticate with a GCP service account setup for keyless authentication from GitHub Actions or an Endor Labs API key added as a repository secret.
2. Checkout your code.
3. Install your build toolchain.
4. Build your code.
5. Sign your artifacts with Endor Labs.

Use the GitHub Action `endorlabs/github-action/sign` to sign your artifacts. Set the following input parameters.

<GithubActionSha action="sign" />

<YamlTable>
  {`


    - Options: \`artifact_name\`
    Description: Name of the artifact. For example, \`ghcr.io/org/image@sha256:digest\`.
    - Options: \`enable_github_action_token\`
    Description: Fetches build information from the GitHub Action OIDC token. Endor Labs uses this information to build provenance metadata for the signed artifacts. Set to \`true\` by default.


    `}
</YamlTable>

See the following example workflows to sign an artifact.

```yaml expandable theme={null}
name: Sign artifacts with Endor Labs
on: [push, workflow_dispatch]
jobs:
  ko-publish:
    name: Release ko artifact
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      packages: write
      contents: read
    steps:
      - uses: actions/setup-go@v4
        with:
          go-version: '1.20.x'
      - uses: actions/checkout@v3
      - uses: ko-build/setup-ko@v0.6
      - run: ko build
      - name: Login to the GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Publish
        run: KO_DOCKER_REPO=ghcr.io/endorlabs/hello-sign ko publish --bare github.com/endorlabs/hello-sign
      - name: Get Image Digest to Sign
        run: |
          IMAGE_SHA=$(docker inspect ghcr.io/endorlabs/hello-sign:latest | jq -r '.[].Id')
          SIGNING_TARGET="ghcr.io/endorlabs/hello-sign@$IMAGE_SHA"
          echo ARTIFACT="$SIGNING_TARGET" >> $GITHUB_ENV
      - name: Sign with Endor Labs
        uses: endorlabs/github-action/sign@version
        with:
          namespace: "example"
          artifact_name: ${{ env.ARTIFACT }}
```

### Sign using endorctl

Use the `endorctl` CLI to sign an artifact. Ensure you have downloaded the latest `endorctl` binary.

To sign an artifact, run the following command.

```bash theme={null}
endorctl artifact sign --name string --source-repository-ref string --certificate-oidc-issuer string
```

Specify the following options with the `endorctl artifact sign` command to include provenance information in your signed artifacts.

<YamlTable>
  {`


    - Options: \`--name string\`
    Required: Mandatory
    Description: Name of the artifact. For example, \`ghcr.io/org/image@sha256:digest\`.
    - Options: \`--build-config-digest string\`
    Required: Optional
    Description: Specific version of top-level/initiating build instructions. For example, \`workflow sha\`.
    - Options: \`--build-config-name string\`
    Required: Optional
    Description: Name of top-level/initiating build instructions. For example, \`workflow\`.
    - Options: \`--runner-environment string\`
    Required: Optional
    Description: Name of platform-hosted or self-hosted infrastructure. For example, \`self-hosted\`.
    - Options: \`--source-repository string\`
    Required: Optional
    Description: Source repository that the build used. For example, \`org/repo\`.
    - Options: \`--source-repository-digest string\`
    Required: Optional
    Description: Specific version of the source code that the build used. For example, \`commit sha\`.
    - Options: \`--source-repository-owner string\`
    Required: Optional
    Description: Owner of the source repository that the build used. For example, \`my-org\`.
    - Options: \`--source-repository-ref string\`
    Required: Mandatory
    Description: Source repository ref that the build run used.
    - Options: Certificate OIDC Issuer
    Required: Mandatory
    Description: Issuer of the OIDC certificate used for verification. For example, \`https://example.com/auth\` or \`https://token.actions.githubusercontent.com\`
    - Options: Certificate Identity
    Required: Optional
    Description: The identity expected in a valid certificate. For example, \`repo:org/monorepo:ref:refs/tags/v1.2.3\`.


    `}
</YamlTable>

### Provenance information in signed artifacts

The signed artifacts contain provenance metadata that describe the origin, history, and ownership of an artifact throughout its lifecycle. Including this information in signed artifacts enhances transparency, trustworthiness, and accountability.

The signed artifacts include the following provenance information.

<YamlTable>
  {`


    - Type: Build Config Digest
    Description: Specific version of the top-level/initiating build instructions (workflow SHA)
    Example: 729595ed884ce7600925633e585016a4f855929d
    - Type: Build Config Name
    Description: Name of the top-level/initiating build instructions (workflow)
    Example: Release
    - Type: Runner Environment
    Description: Name of the platform-hosted or self-hosted infrastructure
    Example: self-hosted
    - Type: Source Repository
    Description: The source repository that the build used
    Example: \`endorlabs/monorepo\`
    - Type: Source Repository Digest
    Description: Specific version of the source code that the build used (commit SHA)
    Example: 729595ed884ce7600925633e585016a4f855929d
    - Type: Source Repository Owner
    Description: Owner of the source repository that the build used
    Example: \`endorlabs\`
    - Type: Source Repository Ref
    Description: Source repository ref that the build used
    Example: \`refs/tags/v1.6.133\`
    - Type: Certificate OIDC Issuer
    Description: Issuer of the OIDC certificate used for verification
    Example: \`https://example.com/auth\` or \`https://token.actions.githubusercontent.com\`
    - Type: Certificate Identity
    Description: The identity expected in a valid certificate
    Example: \`repo:org/monorepo:ref:refs/tags/v1.2.3\`


    `}
</YamlTable>

### Understand the signing process

When you run the `endorctl artifact sign <image>` command, Endor Labs initiates the following processes:

* **Authentication:** Initiates regular authentication and retrieves a token from the OIDC or workflow provider while using an authentication option such as `--enable-github-action-token` or API keys.
* **Key Generation:** Generates a public and private key using ECDSA-256.
* **Certificate Request:** Sends a certificate request to the private Certificate Authority to obtain a short-lived certificate.
* **Provenance Inclusion:** Incorporates provenance information from the token (if available) or provided with the CLI, adding it as a set of extensions to the certificate using ASN.1 encoding.
* **Image Signing:** Uses the private key to actively sign the image.
* **Certificate Storage:** Stores the certificate containing provenance information along with the signature in the database.
* **Timestamp:** Adds a timestamp of the signing event.

## View the signed artifacts

To view the signed artifacts:

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

2. Select **Artifacts**.

   The list shows signed artifacts with **Name**, **Created**, and **Last Updated** details.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/Uc3T4mPoaFbRUPbf/images/scan/containers/artifact-signing/view-signed-artifacts.webp?fit=max&auto=format&n=Uc3T4mPoaFbRUPbf&q=85&s=6a06e0e7ad684cb869028f665e387adb" alt="Signed artifacts on the Artifacts tab in Inventory" style={{ width: '80%' }} width="2120" height="1130" data-path="images/scan/containers/artifact-signing/view-signed-artifacts.webp" />

3. Use the search bar to find artifacts by name, description, or tags.

   You can use the following filters:

   * **Artifact Types**: Filter by artifact type, for example container image.
   * **Created**: Filter by artifact creation date.

4. Select an artifact to see its signed artifact digests in the list and provenance information. The list shows Artifact Digest, Reference, Created, and Last Updated for each digest.

5. Select an artifact digest to open **Artifact Digest Details** and view the metadata, signature and certificate details, build configuration, and source repository information.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/Uc3T4mPoaFbRUPbf/images/scan/containers/artifact-signing/artifact-digest-details.webp?fit=max&auto=format&n=Uc3T4mPoaFbRUPbf&q=85&s=55cb09e917936bc07683396eacd009a3" alt="Artifact digest details with signature and provenance" style={{ width: '70%' }} width="1134" height="1346" data-path="images/scan/containers/artifact-signing/artifact-digest-details.webp" />

## Verify artifacts

You can verify artifacts using GitHub Actions or with endorctl.

### Verify using GitHub Action

Use the Endor Labs [GitHub Actions](https://github.com/endorlabs/github-action/blob/main/README.md) to verify signed artifacts in your CI pipeline.

<GithubActionSha action="verify" />

Use the GitHub Action `endorlabs/github-action/verify` to verify your artifacts. Set the following input parameters.

<YamlTable>
  {`


    - Options: \`artifact_name\`
    Description: Name of the artifact to verify. For example, \`ghcr.io/org/image@sha256:digest\`.
    - Options: \`certificate_oidc_issuer\`
    Description: Issuer of the OIDC certificate used for verification. For example, \`https://token.actions.githubusercontent.com\`.
    - Options: \`enable_github_action_token\`
    Description: Fetches build information from the GitHub Action OIDC token. Set to \`true\` by default.


    `}
</YamlTable>

See the following example workflow to verify a signed artifact.

```yaml expandable theme={null}
name: Verify artifacts with Endor Labs
on: [push, workflow_dispatch]
jobs:
  verify-artifact:
    name: Verify signed artifact
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      packages: read
      contents: read
    steps:
      - uses: actions/checkout@v3
      - name: Verify with Endor Labs
        uses: endorlabs/github-action/verify@version
        with:
          namespace: "example"
          artifact_name: "ghcr.io/endorlabs/hello-sign@sha256:digest"
          certificate_oidc_issuer: "https://token.actions.githubusercontent.com"
```

### Verify using endorctl

To verify a signed artifact, use the following command:

```bash theme={null}
endorctl artifact verify --name <artifact> --certificate-oidc-issuer <issuer>
```

Use the following command-line options with `endorctl artifact verify`:

<YamlTable>
  {`


    - Options: \`--name <name>\`
    Description: Name of the artifact to verify. For example, \`ghcr.io/org/image@sha256:digest\`
    - Options: \`--certificate-oidc-issuer <issuer>\`
    Description: Issuer of the OIDC certificate used for verification. For example, \`https://example.com/auth\` or \`https://token.actions.githubusercontent.com\`


    `}
</YamlTable>

#### Understand the verification process

When you run the `endorctl artifact verify --name <artifact> --certificate-oidc-issuer string` command, Endor Labs initiates the following verification processes:

* **Authentication:** Initiates regular authentication and retrieves a token from the OIDC or workflow provider while using an authentication option such as `--enable-github-action-token` or API keys.
* **Signature Retrieval:** Retrieves a signature entry from the database using the artifact name.
  * If the entry is not found, the verification process fails.
* **Certificate Authority Check:** Checks for a trusted Certificate Authority.
* **Image Signature Validation:** Validates the image signature using the public key from the certificate.
* **Timestamp Validation:** Validates that the timestamp in the signature entry is within the certificate's validity.
* **OIDC Issuer Verification:** Checks whether the issuer provided matches the contents of the certificate.
* **Provenance Verification:** Ensures that any provenance information from the CLI matches the ones in the certificate.

## Revoke the artifact signature

You can revoke a signature of a signed artifact for reasons such as a precautionary measure to safeguard against security risks, to maintain compliance, or to uphold trust and integrity.

To revoke a signature linked to an artifact and prevent its usage, use the following command:

```bash theme={null}
endorctl artifact revoke-signature --name <image> --source-repository-ref "ref"
```

Specify the following command-line options for `endorctl artifact revoke-signature`:

<YamlTable>
  {`


    - Options: \`--name string\`
    Required: Mandatory
    Description: Name of the artifact whose signature you want to revoke
    - Options: \`--source-repository-ref string\`
    Required: Mandatory
    Description: Reference to the source repository of the artifact. For example, \`refs/tags/v1.0.1\`. This identifies the specific signature and revokes it.


    `}
</YamlTable>

Revoking the artifact signature invalidates the corresponding database entry and ensures that any attempts to verify the signature will fail.

## Best practices

* While specifying the artifact name during the signing process, for the container images, adhere to the structure `registry.example.com/repository/image@sha256:digest`.
* The signing process does not support tags. Ensure that you specify a SHA256 digest with the artifact you are signing to represent a cryptographic hash of the image's content. This produces a unique digest for every minor alteration in the image.
