---
title: "Auditing Your Dependencies"
date: 2026-02-17
description: "A practical guide to building a security pipeline for third-party code, including LLM-assisted review."
tags: ["security","supply-chain","llms","devops"]
readingTime: "8 min read"
url: https://alexmoening.com/dev-thoughts/auditing-your-dependencies.html
markdownUrl: https://alexmoening.com/dev-thoughts/auditing-your-dependencies.md
---

# Auditing Your Dependencies

[← Back to /dev/thoughts](/dev-thoughts/)

<p class="lead">Self-hosting is only half the solution. You also need to verify what you're hosting.</p>

This is Part 2 of a series on supply chain security. [Part 1](supply-chain-roulette.html) covers why CDN embeds are risky and tells the story of a real supply chain attack I witnessed at Akamai.

### Implementing a Security Pipeline

<p class="section-summary">Five layers of verification before code reaches production.</p>

#### 1. Fetch with Version Pinning

<pre class="terminal"><code><span class="ansi-gray">#!/bin/bash</span>
<span class="ansi-gray"># Pin versions explicitly - no @latest in production</span>
<span class="ansi-yellow">P5_VERSION</span><span class="ansi-white">=</span><span class="ansi-cyan">"1.7.0"</span>
<span class="ansi-blue">curl</span> <span class="ansi-bright-blue">-sSL</span> <span class="ansi-cyan">"https://cdnjs.cloudflare.com/ajax/libs/p5.js/</span><span class="ansi-yellow">${P5_VERSION}</span><span class="ansi-cyan">/p5.min.js"</span> \
    <span class="ansi-bright-blue">-o</span> <span class="ansi-cyan">"public/lib/p5.min.js"</span>

<span class="ansi-gray"># Generate SRI hash for integrity verification</span>
<span class="ansi-yellow">SRI_HASH</span><span class="ansi-white">=</span><span class="ansi-white">$(</span><span class="ansi-blue">openssl</span> dgst <span class="ansi-bright-blue">-sha384</span> <span class="ansi-bright-blue">-binary</span> <span class="ansi-cyan">"public/lib/p5.min.js"</span> | <span class="ansi-blue">openssl</span> base64 <span class="ansi-bright-blue">-A</span><span class="ansi-white">)</span>
<span class="ansi-blue">echo</span> <span class="ansi-cyan">"sha384-</span><span class="ansi-yellow">${SRI_HASH}</span><span class="ansi-cyan">"</span> > <span class="ansi-cyan">"public/lib/p5.min.js.sri"</span></code></pre>

#### 2. Baseline Comparison

Store a SHA256 hash of each library when you first audit it. On subsequent fetches, compare:

<pre class="terminal"><code><span class="ansi-gray"># First audit: create baseline</span>
<span class="ansi-blue">shasum</span> <span class="ansi-bright-blue">-a</span> <span class="ansi-red">256</span> p5.min.js > .security-baselines/p5.min.js.sha256

<span class="ansi-gray"># Later fetches: detect changes</span>
<span class="ansi-yellow">current_hash</span><span class="ansi-white">=</span><span class="ansi-white">$(</span><span class="ansi-blue">shasum</span> <span class="ansi-bright-blue">-a</span> <span class="ansi-red">256</span> p5.min.js | <span class="ansi-blue">awk</span> <span class="ansi-cyan">'{print $1}'</span><span class="ansi-white">)</span>
<span class="ansi-yellow">baseline_hash</span><span class="ansi-white">=</span><span class="ansi-white">$(</span><span class="ansi-blue">cat</span> .security-baselines/p5.min.js.sha256<span class="ansi-white">)</span>

<span class="ansi-magenta ansi-bold">if</span> [ <span class="ansi-cyan">"</span><span class="ansi-yellow">$current_hash</span><span class="ansi-cyan">"</span> != <span class="ansi-cyan">"</span><span class="ansi-yellow">$baseline_hash</span><span class="ansi-cyan">"</span> ]; <span class="ansi-magenta ansi-bold">then</span>
    <span class="ansi-blue">echo</span> <span class="ansi-cyan">"</span><span class="ansi-yellow">WARNING:</span><span class="ansi-cyan"> Library content has changed!"</span>
    <span class="ansi-gray"># Require manual review before proceeding</span>
<span class="ansi-magenta ansi-bold">fi</span></code></pre>

#### 3. Static Pattern Scanning

<pre class="terminal"><code><span class="ansi-gray"># High-risk patterns</span>
<span class="ansi-yellow">HIGH_RISK_PATTERNS</span><span class="ansi-white">=(</span>
    <span class="ansi-cyan">'document\.write\s*\('</span>              <span class="ansi-gray"># DOM clobbering</span>
    <span class="ansi-cyan">'eval\s*\([^)]*\$'</span>                  <span class="ansi-gray"># eval with dynamic input</span>
    <span class="ansi-cyan">'new\s+Function\s*\([^)]*\+'</span>        <span class="ansi-gray"># Function constructor</span>
    <span class="ansi-cyan">'document\.createElement.*script.*src\s*='</span>  <span class="ansi-gray"># Dynamic script injection</span>
<span class="ansi-white">)</span>

<span class="ansi-magenta ansi-bold">for</span> pattern <span class="ansi-magenta ansi-bold">in</span> <span class="ansi-cyan">"</span><span class="ansi-yellow">${HIGH_RISK_PATTERNS[@]}</span><span class="ansi-cyan">"</span>; <span class="ansi-magenta ansi-bold">do</span>
    <span class="ansi-magenta ansi-bold">if</span> <span class="ansi-blue">grep</span> <span class="ansi-bright-blue">-qE</span> <span class="ansi-cyan">"</span><span class="ansi-yellow">$pattern</span><span class="ansi-cyan">"</span> <span class="ansi-cyan">"</span><span class="ansi-yellow">$library_file</span><span class="ansi-cyan">"</span>; <span class="ansi-magenta ansi-bold">then</span>
        <span class="ansi-blue">echo</span> <span class="ansi-cyan">"</span><span class="ansi-red">HIGH RISK:</span><span class="ansi-cyan"> Found pattern '</span><span class="ansi-yellow">$pattern</span><span class="ansi-cyan">'"</span>
    <span class="ansi-magenta ansi-bold">fi</span>
<span class="ansi-magenta ansi-bold">done</span></code></pre>

#### 4. Domain Extraction

<pre class="terminal"><code><span class="ansi-gray"># Extract URLs and check against allowlist</span>
<span class="ansi-yellow">urls</span><span class="ansi-white">=</span><span class="ansi-white">$(</span><span class="ansi-blue">grep</span> <span class="ansi-bright-blue">-oE</span> <span class="ansi-cyan">'https?://[a-zA-Z0-9][a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'</span> <span class="ansi-cyan">"</span><span class="ansi-yellow">$file</span><span class="ansi-cyan">"</span><span class="ansi-white">)</span>
<span class="ansi-yellow">ALLOWED_DOMAINS</span><span class="ansi-white">=(</span><span class="ansi-cyan">"github.com"</span> <span class="ansi-cyan">"jsdelivr.net"</span><span class="ansi-white">)</span>

<span class="ansi-magenta ansi-bold">for</span> url <span class="ansi-magenta ansi-bold">in</span> <span class="ansi-yellow">$urls</span>; <span class="ansi-magenta ansi-bold">do</span>
    <span class="ansi-magenta ansi-bold">if</span> ! is_allowed <span class="ansi-cyan">"</span><span class="ansi-yellow">$url</span><span class="ansi-cyan">"</span>; <span class="ansi-magenta ansi-bold">then</span>
        <span class="ansi-blue">echo</span> <span class="ansi-cyan">"</span><span class="ansi-yellow">WARNING:</span><span class="ansi-cyan"> Unknown domain in library"</span>
    <span class="ansi-magenta ansi-bold">fi</span>
<span class="ansi-magenta ansi-bold">done</span></code></pre>

#### 5. Deploy Gate

<pre class="terminal"><code><span class="ansi-gray"># In deploy.sh - fail fast if audit fails</span>
<span class="ansi-blue">echo</span> <span class="ansi-cyan">"Running security audit..."</span>
./scripts/security-audit-libs.sh || <span class="ansi-magenta ansi-bold">exit</span> <span class="ansi-red">1</span>

<span class="ansi-gray"># Only proceed if audit passes</span>
<span class="ansi-blue">aws</span> s3 sync public/ s3://mybucket/</code></pre>

### The LLM Experiment

<p class="section-summary">LLMs can help spot suspicious patterns, but don't trust the hype. Real-world false positive rates are high.</p>

There's been a lot of excitement about using LLMs to detect malicious code. Some papers claim 99% precision. **Be skeptical.**

A 2025 study on project-scale vulnerability detection found real-world false positive rates of **63-97%** depending on the model.<sup><a href="#ref-llm-scale">[1]</a></sup> Another ICSE 2025 paper found that many benchmark datasets have poor label accuracy--meaning prior claims were evaluated on flawed data.<sup><a href="#ref-primevul">[2]</a></sup>

<table class="data-table">
    <thead>
        <tr>
            <th>Claim</th>
            <th>Reality</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>"99% precision"</td>
            <td class="negative">On curated benchmarks only; production FP rates much higher</td>
        </tr>
        <tr>
            <td>"LLMs understand obfuscation"</td>
            <td class="neutral">Partially true, but adversarial evasion bypasses most detection</td>
        </tr>
        <tr>
            <td>"Drop-in replacement for SAST"</td>
            <td class="negative">No. High cost (300K-300M tokens/project), inconsistent outputs</td>
        </tr>
    </tbody>
</table>

#### Where LLMs Actually Help

Despite the limitations, LLMs add value as *one layer* in a defense-in-depth strategy:

<table class="data-table">
    <thead>
        <tr>
            <th>Use Case</th>
            <th>Why It Works</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Explaining suspicious code</td>
            <td>LLMs describe what obfuscated code does in plain English</td>
        </tr>
        <tr>
            <td>Triaging SAST findings</td>
            <td>Helps filter false positives from static analysis</td>
        </tr>
        <tr>
            <td>Identifying data flows</td>
            <td>Traces data from sources to sinks</td>
        </tr>
    </tbody>
</table>

#### A Practical Prompt

Following [Anthropic's best practices](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/claude-4-best-practices): explicit instructions, XML structure, and uncertainty handling.

<pre class="terminal"><code><span class="ansi-magenta">&lt;role&gt;</span>
<span class="ansi-white">You are a security analyst reviewing third-party JavaScript for supply chain risks.</span>
<span class="ansi-white">Your job is to help a human reviewer understand the code faster, not to render verdicts.</span>
<span class="ansi-magenta">&lt;/role&gt;</span>

<span class="ansi-magenta">&lt;task&gt;</span>
<span class="ansi-white">Analyze the JavaScript code provided. Do NOT assign a malware verdict or confidence score.</span>
<span class="ansi-white">Instead, describe what the code does so a human can make an informed decision.</span>
<span class="ansi-magenta">&lt;/task&gt;</span>

<span class="ansi-magenta">&lt;analysis_structure&gt;</span>
<span class="ansi-white">1. BEHAVIOR: What does this code do? (plain English, 2-3 sentences)</span>
<span class="ansi-white">2. DATA SOURCES: What does it read? (files, env vars, DOM, cookies)</span>
<span class="ansi-white">3. DATA SINKS: Where does data go? (network, file writes, exec, eval)</span>
<span class="ansi-white">4. SUSPICIOUS PATTERNS: What looks unusual?</span>
<span class="ansi-white">5. COMPARISON: What would a malicious version look like?</span>
<span class="ansi-magenta">&lt;/analysis_structure&gt;</span>

<span class="ansi-magenta">&lt;uncertainty_handling&gt;</span>
<span class="ansi-white">If you cannot determine what code does, say "Unable to determine" and explain why.</span>
<span class="ansi-white">Do not guess or speculate.</span>
<span class="ansi-magenta">&lt;/uncertainty_handling&gt;</span></code></pre>

#### Defense in Depth

LLMs are *one layer*, not the whole stack:

<table class="data-table">
    <thead>
        <tr>
            <th>Layer</th>
            <th>Tool</th>
            <th>Catches</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>1</td>
            <td><code>Hash verification</code></td>
            <td>Any change from audited baseline</td>
        </tr>
        <tr>
            <td>2</td>
            <td><code>SAST / pattern scan</code></td>
            <td>Known bad patterns, CVEs</td>
        </tr>
        <tr>
            <td>3</td>
            <td><code>LLM triage</code></td>
            <td>Explains suspicious code in plain English</td>
        </tr>
        <tr>
            <td>4</td>
            <td><code>Human review</code></td>
            <td>Final judgment on context and intent</td>
        </tr>
    </tbody>
</table>

Skip a layer, and you're back to playing roulette.

### PCI DSS 4.0 Implications

<p class="section-summary">If you handle payments, script integrity is now regulation.</p>

PCI DSS 4.0 (effective March 2025) explicitly requires inventory and change-detection for payment page scripts. Self-hosting with integrity verification directly addresses both requirements.

### Getting Started

<p class="section-summary">Six steps from CDN-dependent to supply-chain secure.</p>

<table class="data-table steps">
    <thead>
        <tr>
            <th>Step</th>
            <th>Action</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>1</td>
            <td>Inventory your embeds--search codebase for external script tags</td>
        </tr>
        <tr>
            <td>2</td>
            <td>Download and pin versions--no <code>@latest</code> in production</td>
        </tr>
        <tr>
            <td>3</td>
            <td>Generate SRI hashes for integrity verification</td>
        </tr>
        <tr>
            <td>4</td>
            <td>Create SHA256 baselines for change detection</td>
        </tr>
        <tr>
            <td>5</td>
            <td>Add audit to CI/CD--verify before every deploy</td>
        </tr>
        <tr>
            <td>6</td>
            <td>Set quarterly reminders for security patch reviews</td>
        </tr>
    </tbody>
</table>

### Resources

#### Implementation

<table class="data-table">
    <thead>
        <tr>
            <th>Resource</th>
            <th>Topic</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><a href="https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity" target="_blank" rel="noopener">MDN</a></td>
            <td>Subresource Integrity</td>
        </tr>
        <tr>
            <td><a href="https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html" target="_blank" rel="noopener">OWASP</a></td>
            <td>Secrets Management Cheat Sheet</td>
        </tr>
        <tr>
            <td><a href="https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services" target="_blank" rel="noopener">GitHub Docs</a></td>
            <td>OIDC Federation with AWS</td>
        </tr>
        <tr>
            <td><a href="https://www.pcisecuritystandards.org/document_library/" target="_blank" rel="noopener">PCI SSC</a></td>
            <td>PCI DSS 4.0 Requirements</td>
        </tr>
    </tbody>
</table>

#### LLM Research

<table class="data-table">
    <thead>
        <tr>
            <th>Ref</th>
            <th>Paper</th>
            <th>Key Finding</th>
        </tr>
    </thead>
    <tbody>
        <tr id="ref-llm-scale">
            <td>[1]</td>
            <td><a href="https://arxiv.org/abs/2601.19239" target="_blank" rel="noopener">LLM-based Vulnerability Detection at Project Scale</a></td>
            <td class="negative">63-97% FP rates in production</td>
        </tr>
        <tr id="ref-primevul">
            <td>[2]</td>
            <td><a href="https://dl.acm.org/doi/10.1109/ICSE55347.2025.00038" target="_blank" rel="noopener">PrimeVul (ICSE 2025)</a></td>
            <td class="neutral">Benchmark datasets have poor label accuracy</td>
        </tr>
        <tr>
            <td>[3]</td>
            <td><a href="https://arxiv.org/abs/2512.21250" target="_blank" rel="noopener">CoTDeceptor</a></td>
            <td class="negative">Adversarial evasion bypasses 14/15 categories</td>
        </tr>
    </tbody>
</table>

[Back to Part 1: Supply Chain Roulette](supply-chain-roulette.html)

---

## Navigation

- [Home](/)
- [About](/about.html)
- [Projects](/projects.html)
- [Contact](/contact.html)
- [/dev/thoughts](/dev-thoughts/)

*Copyright 2026 Alex Moening. Opinions expressed are my own.*
