pinner 0.0.10

Secure CI/CD workflows by pinning mutable tags to immutable SHA-1 hashes. A high-performance Rust CLI that preserves YAML formatting and comments. Supports GitHub, GitLab, Bitbucket, Forgejo, and Docker image pinning.
Documentation
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Pinner - Secure Your CI/CD Workflows</title>
    <link rel="icon" type="image/svg+xml" href="favicon.svg">
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        body {
            background-color: #0d1117;
            color: #c9d1d9;
        }
        .hero-gradient {
            background: radial-gradient(circle at 50% 50%, rgba(56, 189, 248, 0.1) 0%, rgba(13, 17, 23, 0) 50%);
        }
        .card {
            background-color: #161b22;
            border: 1px solid #30363d;
        }
        .terminal {
            background-color: #010409;
            border: 1px solid #30363d;
        }
        .tab-active {
            border-bottom: 2px solid #38bdf8;
            color: #f0f9ff;
        }
        .origin-quote {
            border-left: 2px solid #38bdf8;
            background: linear-gradient(90deg, rgba(56, 189, 248, 0.05) 0%, rgba(13, 17, 23, 0) 100%);
        }
    </style>
</head>
<body class="font-sans antialiased overflow-x-hidden">
    <!-- Navbar -->
    <nav class="flex items-center justify-between px-4 md:px-8 py-6 max-w-7xl mx-auto">
        <div class="flex items-center space-x-2">
            <a href="index.html" class="flex items-center space-x-2 group">
                <i class="fas fa-thumbtack text-sky-400 text-2xl group-hover:rotate-45 transition-transform"></i>
                <span class="text-2xl font-bold tracking-tight text-white">Pinner</span>
            </a>
        </div>
        <div class="flex items-center space-x-4 md:space-x-6 text-sm font-medium">
            <a href="getting-started.html" class="hover:text-sky-400 transition-colors" title="Getting Started"><i class="fas fa-rocket md:mr-2"></i><span class="hidden md:inline">Getting Started</span></a>
            <a href="configuration.html" class="hover:text-sky-400 transition-colors" title="Configuration"><i class="fas fa-cog md:mr-2"></i><span class="hidden md:inline">Configuration</span></a>
            <a href="https://github.com/ffalcinelli/pinner" class="hover:text-sky-400 transition-colors" title="GitHub"><i class="fab fa-github md:mr-2"></i><span class="hidden md:inline">GitHub</span></a>
            <a href="https://docs.rs/pinner" class="hover:text-sky-400 transition-colors" title="API Docs"><i class="fas fa-book md:mr-2"></i><span class="hidden md:inline">API Docs</span></a>
        </div>
    </nav>

    <!-- Hero Section -->
    <section class="relative pt-20 pb-32 hero-gradient">
        <div class="max-w-4xl mx-auto text-center px-4">
            <h1 class="text-5xl md:text-7xl font-extrabold text-white mb-6 leading-tight">
                Secure your CI/CD workflows with <span class="text-sky-400">Pinner</span>
            </h1>
            <p class="text-xl md:text-2xl text-gray-400 mb-8 max-w-2xl mx-auto">
                Automatically pin mutable tags to immutable SHA-1 hashes to prevent supply chain attacks.
            </p>
            
            <div class="max-w-2xl mx-auto mb-16 origin-quote py-6 px-4 md:px-8 text-left border-l-4 border-sky-500/30 rounded-r-2xl bg-sky-500/[0.02]">
                <div class="flex items-start space-x-6">
                    <div class="flex-shrink-0 mt-1">
                        <i class="fas fa-flask text-sky-400/60 text-3xl"></i>
                    </div>
                    <div>
                        <h3 class="text-white font-bold mb-2 flex items-center">
                            The Pinner Reaction <span class="ml-3 px-2 py-0.5 bg-sky-500/10 text-sky-400 text-[10px] rounded uppercase tracking-widest">Etymology</span>
                        </h3>
                        <p class="text-gray-400 text-sm leading-relaxed mb-4">
                            In organic chemistry, the <span class="text-sky-300 font-medium">Pinner reaction</span> involves the acid-catalyzed conversion of a reactive nitrile into a highly stable salt.
                        </p>
                        <p class="text-gray-300 italic text-base leading-relaxed border-t border-gray-800 pt-4">
                            "Just as the reaction transforms a volatile compound into a stable, fixed salt, this CLI transforms floating tags into secure, immutable commit SHAs."
                        </p>
                    </div>
                </div>
            </div>
            
            <div class="flex flex-col md:flex-row items-center justify-center gap-4 mb-16">
                <a href="getting-started.html" class="w-full md:w-auto px-4 md:px-8 py-4 bg-sky-500 hover:bg-sky-400 text-white font-bold rounded-lg transition-all transform hover:scale-105 shadow-lg shadow-sky-500/20">
                    Get Started
                </a>
                <a href="https://github.com/ffalcinelli/pinner" class="w-full md:w-auto px-4 md:px-8 py-4 bg-gray-800 hover:bg-gray-700 text-white font-bold rounded-lg transition-all border border-gray-700">
                    View on GitHub
                </a>
            </div>

            <!-- Terminal Mockup -->
            <div class="terminal rounded-xl overflow-hidden shadow-2xl text-left border border-gray-800">
                <div class="bg-gray-800/50 px-4 py-2 flex items-center justify-between">
                    <div class="flex items-center space-x-2">
                        <div class="w-3 h-3 bg-red-500/80 rounded-full"></div>
                        <div class="w-3 h-3 bg-yellow-500/80 rounded-full"></div>
                        <div class="w-3 h-3 bg-green-500/80 rounded-full"></div>
                    </div>
                    <span class="text-[10px] text-gray-500 uppercase tracking-widest font-bold">Terminal</span>
                </div>
                <div class="p-6 font-mono text-sm md:text-base leading-relaxed">
                    <p class="mb-2 text-gray-500">$ pinner pin</p>
                    <p class="mb-2 text-emerald-400">Searching for workflows in .github/workflows/...</p>
                    <p class="mb-2 text-white"><span class="text-sky-400">actions/checkout@v4</span> -> <span class="text-sky-400">actions/checkout@8f4b7f84...</span> <span class="text-gray-500"># v4</span> <span class="text-emerald-400 font-bold">[✓ vetted]</span></p>
                    <p class="mb-2 text-white"><span class="text-sky-400">dtolnay/rust-toolchain@master</span> -> <span class="text-sky-400">dtolnay/rust-toolchain@e97e2d8c...</span> <span class="text-gray-500"># master</span> <span class="text-yellow-400">[? not checked]</span></p>
                    <p class="text-emerald-400 font-bold mt-4 mb-6">Successfully pinned 12 actions across 3 files!</p>
                    
                    <p class="mb-2 text-gray-500">$ pinner scan</p>
                    <p class="mb-2 text-emerald-400">Scanning dependencies with OSV database...</p>
                    <p class="mb-2 text-emerald-400">✔ Clean Dependencies: actions/checkout@8f4b7f84...</p>
                    <p class="text-emerald-400 font-bold mt-2 mb-6">All scanned dependencies are secure! 🛡️</p>

                    <p class="mb-2 text-gray-500">$ pinner verify</p>
                    <p class="mb-2 text-emerald-400">Verifying action pinning...</p>
                    <p class="text-emerald-400 font-bold">All actions are correctly pinned! ✅</p>
                </div>
            </div>
        </div>
    </section>

    <!-- Features -->
    <section class="py-24 max-w-7xl mx-auto px-4 md:px-8">
        <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
            <div class="card p-8 rounded-2xl hover:border-sky-500/50 transition-colors">
                <div class="w-12 h-12 bg-sky-500/10 rounded-lg flex items-center justify-center mb-6">
                    <i class="fas fa-bolt text-sky-400 text-xl"></i>
                </div>
                <h3 class="text-xl font-bold text-white mb-4">High Performance</h3>
                <p class="text-gray-400 leading-relaxed">Built with Rust and `tree-sitter` for maximum speed and precision. Scan hundreds of actions in milliseconds.</p>
            </div>
            <div class="card p-8 rounded-2xl hover:border-emerald-500/50 transition-colors">
                <div class="w-12 h-12 bg-emerald-500/10 rounded-lg flex items-center justify-center mb-6">
                    <i class="fas fa-shield-halved text-emerald-400 text-xl"></i>
                </div>
                <h3 class="text-xl font-bold text-white mb-4">Secure by Design</h3>
                <p class="text-gray-400 leading-relaxed">Protects your CI/CD against tag-moving attacks. Native support for `verify` mode in your CI pipelines.</p>
            </div>
            <div class="card p-8 rounded-2xl hover:border-red-500/50 transition-colors">
                <div class="w-12 h-12 bg-red-500/10 rounded-lg flex items-center justify-center mb-6">
                    <i class="fas fa-file-shield text-red-400 text-xl"></i>
                </div>
                <h3 class="text-xl font-bold text-white mb-4">OSV Security Scan</h3>
                <p class="text-gray-400 leading-relaxed">Direct integration with OpenSSF OSV database to detect compromised commit hashes, check OCI image provenance signatures, and audit upgrade candidates.</p>
            </div>
            <div class="card p-8 rounded-2xl hover:border-purple-500/50 transition-colors">
                <div class="w-12 h-12 bg-purple-500/10 rounded-lg flex items-center justify-center mb-6">
                    <i class="fas fa-sliders text-purple-400 text-xl"></i>
                </div>
                <h3 class="text-xl font-bold text-white mb-4">Vetting & Policy</h3>
                <p class="text-gray-400 leading-relaxed">Configure trusted whitelists and blacklists in `.pinner.toml` with colored inline diff indicators.</p>
            </div>
        </div>
    </section>

    <!-- Installation -->
    <section class="py-24 bg-gray-900/30 border-y border-gray-800/50">
        <div class="max-w-4xl mx-auto px-4 md:px-8 text-center">
            <h2 class="text-3xl font-bold text-white mb-12 flex items-center justify-center">
                <i class="fas fa-download text-sky-400 mr-4"></i> Install Pinner
            </h2>
            
            <!-- OS Tabs -->
            <div class="flex justify-center mb-8">
                <div class="flex flex-col sm:flex-row w-full sm:w-auto p-1 bg-gray-800/50 border border-gray-700 rounded-xl space-y-1 sm:space-y-0 sm:space-x-1">
                    <button onclick="switchTab('linux')" id="tab-linux" class="flex items-center justify-center w-full px-6 py-2.5 text-xs font-bold uppercase tracking-widest transition-all rounded-lg tab-active">
                        <i class="fab fa-linux mr-2"></i><i class="fab fa-apple mr-2"></i> Linux / macOS
                    </button>
                    <button onclick="switchTab('windows')" id="tab-windows" class="flex items-center justify-center w-full px-6 py-2.5 text-xs font-bold uppercase tracking-widest text-gray-500 hover:text-gray-300 transition-all rounded-lg">
                        <i class="fab fa-windows mr-2"></i> Windows
                    </button>
                    <button onclick="switchTab('cargo')" id="tab-cargo" class="flex items-center justify-center w-full px-6 py-2.5 text-xs font-bold uppercase tracking-widest text-gray-500 hover:text-gray-300 transition-all rounded-lg">
                        <i class="fas fa-box-open mr-2"></i> Cargo
                    </button>
                </div>
            </div>

            <!-- Install Commands -->
            <div class="relative max-w-2xl mx-auto">
                <div id="cmd-linux" class="terminal rounded-lg p-1 flex items-center justify-between bg-black/40 border-gray-700/50 shadow-xl">
                    <code class="px-6 py-4 font-mono text-emerald-400 text-sm md:text-base flex-1 break-all">curl -LsSf https://raw.githubusercontent.com/ffalcinelli/pinner/main/install.sh | sh</code>
                    <button onclick="copyCmd('curl -LsSf https://raw.githubusercontent.com/ffalcinelli/pinner/main/install.sh | sh', this)" class="px-6 py-4 hover:bg-emerald-400/10 rounded-md transition-all text-gray-400 hover:text-emerald-400 flex-shrink-0 group">
                        <i class="far fa-copy group-active:scale-90 transition-transform"></i>
                    </button>
                </div>
                <div id="cmd-windows" class="terminal rounded-lg p-1 hidden items-center justify-between bg-black/40 border-gray-700/50 shadow-xl">
                    <code class="px-6 py-4 font-mono text-emerald-400 text-sm md:text-base flex-1 break-all">powershell -ExecutionPolicy ByPass -c "irm https://raw.githubusercontent.com/ffalcinelli/pinner/main/install.ps1 | iex"</code>
                    <button onclick="copyCmd('powershell -ExecutionPolicy ByPass -c \"irm https://raw.githubusercontent.com/ffalcinelli/pinner/main/install.ps1 | iex\"', this)" class="px-6 py-4 hover:bg-emerald-400/10 rounded-md transition-all text-gray-400 hover:text-emerald-400 flex-shrink-0 group">
                        <i class="far fa-copy group-active:scale-90 transition-transform"></i>
                    </button>
                </div>
                <div id="cmd-cargo" class="terminal rounded-lg p-1 hidden items-center justify-between bg-black/40 border-gray-700/50 shadow-xl">
                    <code class="px-6 py-4 font-mono text-emerald-400 text-sm md:text-base flex-1 break-all">cargo install pinner</code>
                    <button onclick="copyCmd('cargo install pinner', this)" class="px-6 py-4 hover:bg-emerald-400/10 rounded-md transition-all text-gray-400 hover:text-emerald-400 flex-shrink-0 group">
                        <i class="far fa-copy group-active:scale-90 transition-transform"></i>
                    </button>
                </div>
                <!-- Tooltip Feedback -->
                <div id="copy-feedback" class="absolute -top-12 left-1/2 -translate-x-1/2 px-4 py-2 bg-sky-500 text-white text-xs font-bold rounded shadow-lg opacity-0 pointer-events-none transition-all duration-300">
                    Copied to clipboard!
                </div>
            </div>
            <p class="mt-8 text-sm text-gray-500 font-medium">One-line installation for instant workflow security.</p>
        </div>
    </section>

    <!-- Pinnerception Meme / Recursive Joke -->
    <section class="py-16 max-w-4xl mx-auto px-4">
        <div class="relative overflow-hidden rounded-2xl border border-sky-500/10 bg-sky-500/[0.02] p-8 md:p-12 shadow-xl">
            <div class="absolute -right-12 -bottom-12 opacity-5 pointer-events-none select-none">
                <i class="fas fa-rotate text-sky-400 text-[180px] animate-spin" style="animation-duration: 20s;"></i>
            </div>
            <div class="relative z-10 flex flex-col md:flex-row items-center md:items-start md:space-x-8 text-center md:text-left">
                <div class="flex-shrink-0 p-4 bg-sky-500/10 rounded-full mb-6 md:mb-0">
                    <i class="fas fa-rotate text-sky-400 text-3xl animate-spin" style="animation-duration: 6s;"></i>
                </div>
                <div>
                    <h3 class="text-xl md:text-2xl font-extrabold text-white mb-3 flex items-center justify-center md:justify-start">
                        Pinnerception: The Recursive Paradox 🌀
                    </h3>
                    <p class="text-gray-400 text-sm md:text-base leading-relaxed mb-4">
                        Remember to pin the pinner! If you use the Pinner GitHub Action in your workflows, you should hash-pin <strong>Pinner</strong> using Pinner itself. 
                    </p>
                    <div class="border-l-2 border-sky-500/40 pl-4 py-1.5 mb-4 italic text-gray-300 text-sm leading-relaxed">
                        "Trusting a security action to verify your pinned dependencies using a mutable tag is like hiring a security guard who leaves the keys under the doormat."
                    </div>
                    <p class="text-xs md:text-sm text-sky-300 font-medium">
                        Remember: If you don't pin the pinner, who pins the pinner's pinners? Mind the recursion.
                    </p>
                </div>
            </div>
        </div>
    </section>

    <!-- Footer -->
    <footer class="py-16 text-center text-gray-500">
        <div class="flex items-center justify-center space-x-4 mb-6">
            <a href="https://github.com/ffalcinelli/pinner" class="hover:text-white transition-colors"><i class="fab fa-github text-xl"></i></a>
            <div class="h-4 w-px bg-gray-800"></div>
            <a href="https://docs.rs/pinner" class="hover:text-white transition-colors text-sm font-bold uppercase tracking-tighter">Docs.rs</a>
        </div>
        <p class="text-xs">&copy; 2026 Fabio Falcinelli. Released under the MIT License.</p>
    </footer>

    <script>
        function switchTab(os) {
            // Update tabs
            ['linux', 'windows', 'cargo'].forEach(id => {
                const tab = document.getElementById('tab-' + id);
                const cmd = document.getElementById('cmd-' + id);
                if (id === os) {
                    tab.classList.add('tab-active', 'bg-sky-500/10', 'text-sky-400');
                    tab.classList.remove('text-gray-500');
                    cmd.classList.remove('hidden');
                    cmd.classList.add('flex');
                } else {
                    tab.classList.remove('tab-active', 'bg-sky-500/10', 'text-sky-400');
                    tab.classList.add('text-gray-500');
                    cmd.classList.add('hidden');
                    cmd.classList.remove('flex');
                }
            });
        }

        function copyCmd(text, btn) {
            navigator.clipboard.writeText(text);
            
            const feedback = document.getElementById('copy-feedback');
            feedback.classList.remove('opacity-0');
            feedback.classList.add('opacity-100');
            
            setTimeout(() => {
                feedback.classList.remove('opacity-100');
                feedback.classList.add('opacity-0');
            }, 2000);
        }

        // Auto-detect OS
        window.onload = () => {
            const platform = window.navigator.platform.toLowerCase();
            if (platform.includes('win')) {
                switchTab('windows');
            } else {
                switchTab('linux');
            }
        };
    </script>
</body>
</html>