foxguard 0.9.0

A security scanner as fast as a linter, written in Rust. 170+ built-in rules across 11 languages.
Documentation
---
import OsecWordmark from './OsecWordmark.astro';

interface Props {
  backLink?: string;
  backLabel?: string;
}

const { backLink, backLabel } = Astro.props;
---

<nav class="flex items-center justify-between py-4">
  <div class="flex items-center gap-2.5">
    <a href={backLink || "/"} class="flex items-center gap-2.5 text-noir-400 hover:text-noir-100 transition-colors no-underline">
      <img src="/foxguard-logo.png" alt="" class="h-7 w-7 object-contain" loading="eager" decoding="async" />
      <span class="font-heading text-noir-50 text-lg">fox<span class="text-fox-light">guard</span></span>
      {backLabel && <span class="text-noir-600 text-sm">/ {backLabel}</span>}
    </a>
    <a
      href="https://0sec.ai"
      target="_blank"
      rel="noopener"
      aria-label="0sec"
      class="hidden sm:inline-flex items-baseline gap-1 border-l border-noir-800 pl-2 text-[11px] font-medium text-noir-500 hover:text-noir-300 transition-colors no-underline"
    >
      <span>by</span>
      <OsecWordmark />
    </a>
  </div>

  <div class="flex items-center gap-5 text-sm">
    <a href="/docs" class="hidden sm:block text-noir-500 hover:text-noir-100 transition-colors no-underline">Docs</a>
    <a href="/compare" class="hidden sm:block text-noir-500 hover:text-noir-100 transition-colors no-underline">Compare</a>
    <a href="/rules" class="hidden sm:block text-noir-500 hover:text-noir-100 transition-colors no-underline">Rules</a>
    <a href="/post-quantum" class="hidden sm:block text-noir-500 hover:text-noir-100 transition-colors no-underline">PQC</a>
    <a href="/blog" class="hidden sm:block text-noir-500 hover:text-noir-100 transition-colors no-underline">Blog</a>
    <a
      href="https://github.com/0sec-labs/foxguard"
      target="_blank"
      rel="noopener"
      class="flex items-center gap-1.5 rounded-lg border border-noir-700 px-3 py-1.5 text-noir-300 hover:border-noir-600 hover:text-noir-100 transition-colors no-underline"
    >
      <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z"/></svg>
      <span id="nav-star-count">GitHub</span>
    </a>
    <script>
      try {
        const cached = localStorage.getItem('fg-stars');
        const cachedAt = localStorage.getItem('fg-stars-at');
        const el = document.getElementById('nav-star-count');

        // Use cache if less than 1 hour old
        if (cached && cachedAt && Date.now() - Number(cachedAt) < 3600000) {
          if (el) el.innerHTML = '★ ' + cached;
        } else {
          const r = await fetch('https://api.github.com/repos/0sec-labs/foxguard');
          if (r.ok) {
            const d = await r.json();
            if (el && d.stargazers_count) {
              const count = d.stargazers_count.toLocaleString();
              el.innerHTML = '★ ' + count;
              localStorage.setItem('fg-stars', count);
              localStorage.setItem('fg-stars-at', String(Date.now()));
            }
          }
        }
      } catch(e) {}
    </script>
    <button
      id="nav-free-btn"
      class="flex items-center gap-1.5 rounded-lg bg-fox px-3.5 py-1.5 font-medium hover:bg-fox-light transition-colors cursor-pointer"
      style="color: #000;"
    >
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2.5"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
      Free
    </button>
    <script>
      document.getElementById('nav-free-btn')?.addEventListener('click', () => {
        const installBox = document.querySelector('[data-install-box]') as HTMLElement;
        // Pages without an install box (compare, github, rules, blog) send the
        // visitor to the homepage hero, where the install command lives.
        if (!installBox) {
          window.location.href = '/';
          return;
        }

        const rect = installBox.getBoundingClientRect();
        const inView = rect.top >= 0 && rect.bottom <= window.innerHeight;

        if (!inView) {
          installBox.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }

        const delay = inView ? 0 : 500;
        setTimeout(() => {
          // Create spotlight overlay
          const overlay = document.createElement('div');
          overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:30;transition:opacity 0.3s;opacity:0;cursor:pointer;';
          document.body.appendChild(overlay);
          requestAnimationFrame(() => overlay.style.opacity = '1');

          // Pop the install box above the overlay
          installBox.style.position = 'relative';
          installBox.style.zIndex = '35';
          installBox.style.borderColor = '#d97706';
          installBox.style.boxShadow = '0 0 40px rgba(217, 119, 6, 0.5)';

          // Add hint as overlay text below the box (absolute, doesn't push content)
          const hint = document.createElement('div');
          hint.style.cssText = 'position:absolute;left:50%;transform:translateX(-50%);z-index:35;font-size:13px;color:#78716C;opacity:0;transition:opacity 0.3s;white-space:nowrap;';
          hint.textContent = 'paste in your terminal to get started';
          const boxRect = installBox.getBoundingClientRect();
          hint.style.top = (boxRect.bottom + window.scrollY + 10) + 'px';
          document.body.appendChild(hint);
          requestAnimationFrame(() => hint.style.opacity = '1');

          // Click overlay or install box to dismiss
          const dismiss = () => {
            overlay.style.opacity = '0';
            hint.style.opacity = '0';
            installBox.style.position = '';
            installBox.style.zIndex = '';
            installBox.style.borderColor = '';
            installBox.style.boxShadow = '';
            setTimeout(() => { overlay.remove(); hint.remove(); }, 300);
          };
          overlay.addEventListener('click', dismiss);
          installBox.addEventListener('click', dismiss, { once: true });
          setTimeout(dismiss, 3000);
        }, 600);
      });
    </script>
  </div>
</nav>