<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Diego Cornejo (RamdomTechGuy)]]></title><description><![CDATA[A journey from a random guy to a random tech guy :D]]></description><link>https://blog.diegocornejo.com</link><image><url>https://cdn.hashnode.com/uploads/logos/600edd853b349540feba0413/bcf97ef2-9f23-42b1-8b6e-423e9b3c1849.jpg</url><title>Diego Cornejo (RamdomTechGuy)</title><link>https://blog.diegocornejo.com</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 14 May 2026 06:37:53 GMT</lastBuildDate><atom:link href="https://blog.diegocornejo.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Deepsec (Vercel Security Harness): a practical guide to setup, troubleshooting, and triaging findings]]></title><description><![CDATA[A practical guide to deepsec
A complete guide to using deepsec — Vercel's security harness — covering what each command does, how to configure it correctly to use your Claude Code subscription (instea]]></description><link>https://blog.diegocornejo.com/deepsec-vercel-security-harness-practical-guide</link><guid isPermaLink="true">https://blog.diegocornejo.com/deepsec-vercel-security-harness-practical-guide</guid><category><![CDATA[Security]]></category><category><![CDATA[AI]]></category><category><![CDATA[Vercel]]></category><category><![CDATA[deepsec ]]></category><category><![CDATA[claude]]></category><category><![CDATA[guide]]></category><dc:creator><![CDATA[Diego Cornejo]]></dc:creator><pubDate>Wed, 06 May 2026 21:42:36 GMT</pubDate><content:encoded><![CDATA[<h1>A practical guide to deepsec</h1>
<p>A complete guide to using <a href="https://github.com/vercel-labs/deepsec">deepsec</a> — Vercel's security harness — covering what each command does, how to configure it correctly to use your Claude Code subscription (instead of routing through Vercel AI Gateway), and how to triage the findings it produces.</p>
<blockquote>
<p><strong>Context:</strong> deepsec is a security analysis tool that uses coding agents (Claude or Codex) to investigate vulnerabilities in your codebase. It uses Opus 4.7 at maximum thinking by default, which makes it expensive — but also remarkably capable.</p>
</blockquote>
<blockquote>
<p><strong>This guide covers deepsec ≥ 2.0.2.</strong> Earlier versions had a critical billing bug fixed in <a href="https://github.com/vercel-labs/deepsec/pull/43">PR #43</a>. If you're on a previous version, update with <code>pnpm update deepsec</code> before continuing.</p>
</blockquote>
<hr />
<h2>TL;DR — the correct flow</h2>
<pre><code class="language-bash"># 1. Clean setup
claude login                                   # your Pro/Max subscription

# 2. Init and bootstrap
cd ~/projects/my-app
npx deepsec init
cd .deepsec
pnpm install

# 3. Pipeline (in order)
pnpm deepsec scan                              # free, no AI
pnpm deepsec process --project-id my-app       # EXPENSIVE, uses AI
pnpm deepsec revalidate                        # EXPENSIVE, uses AI
pnpm deepsec export --format md-dir --out ./findings   # free, no AI
</code></pre>
<blockquote>
<p>📝 <strong>Requires deepsec ≥ 2.0.2.</strong> Earlier versions had a bug that routed traffic through Vercel AI Gateway even when the user hadn't opted in. If you're on an older version, update with <code>pnpm update deepsec</code>.</p>
</blockquote>
<hr />
<h2>Historical context: behavior change in v2.0.2</h2>
<blockquote>
<p>✅ If your deepsec version is ≥ 2.0.2, you can skip this section. It's documented only for users coming from earlier versions and to explain why some repos may have leftover artifacts like a <code>.vercel/</code> stub inside <code>.deepsec/</code>.</p>
</blockquote>
<p>In pre-2.0.2 versions, the local authentication logic had counterintuitive behavior. The README stated that deepsec would automatically use local <code>claude</code> or <code>codex</code> subscriptions, but in practice the priority order meant that having Vercel CLI logged in (common in Next.js projects) directed all traffic to Vercel AI Gateway, billing the user's account without explicit opt-in.</p>
<p>The internal flow was roughly:</p>
<ol>
<li>Deepsec called <code>getVercelOidcToken()</code> during preflight</li>
<li>That function walked up from <code>.deepsec/</code> looking for a <code>.vercel/project.json</code></li>
<li>If found, it read the Vercel CLI auth</li>
<li>Requested a fresh OIDC token</li>
<li>Expanded it to <code>ANTHROPIC_AUTH_TOKEN</code> + <code>ANTHROPIC_BASE_URL=https://ai-gateway.vercel.sh</code></li>
</ol>
<p>Typical result for users without Vercel AI Gateway credit:</p>
<pre><code>Agent SDK error: Claude Code returned an error result:
API Error: 402 Insufficient funds. Please add credits to your account...
Visit https://vercel.com/d?to=...
</code></pre>
<p>Additionally, trying to avoid the Gateway by setting <code>ANTHROPIC_BASE_URL=https://api.anthropic.com</code> without a token produced a <strong>silent failure mode</strong>: each file completed in ~5 seconds with <code>0 tokens, 0 turns, 0 findings</code>, marked as <code>analyzed</code>. The tool reported the scan as successful without ever calling the AI.</p>
<p><strong>Both behaviors changed in <a href="https://github.com/vercel-labs/deepsec/pull/43">PR #43</a> ("Use the vercel OIDC token for the gateway if no primary API token is present"), merged on May 5, 2026 and shipped in deepsec 2.0.2.</strong></p>
<p>Starting in v2.0.2, the OIDC token is only used as an explicit fallback when no other credential is present, and the local <code>claude</code> CLI subscription takes precedence. This means you can have Vercel CLI logged in and deepsec will correctly use your Claude Code subscription without diverting traffic to the Gateway.</p>
<hr />
<h2>How to configure deepsec to use your Claude Code subscription</h2>
<h3>Step 1: Clean environment setup</h3>
<pre><code class="language-bash"># Verify there are no conflicting environment variables
env | grep -iE "anthropic|openai|gateway"
# Should return nothing relevant

# Verify claude CLI is authenticated with your subscription
claude --version
claude --print "test"
# Should respond normally
</code></pre>
<h3>Step 2: Remove previous installations if any</h3>
<p>If you've tried running deepsec before and it failed, residual state may contaminate the setup:</p>
<pre><code class="language-bash">cd your-project
rm -rf .deepsec
</code></pre>
<h3>Step 3: Init from scratch</h3>
<pre><code class="language-bash">npx deepsec init
cd .deepsec
pnpm install
</code></pre>
<p><strong>Don't run <code>cp .env.example .env</code></strong> or edit <code>.env.local</code>. The <code>init</code> will tell you:</p>
<blockquote>
<p><code># Set AI_GATEWAY_API_KEY in .env.local (or skip if claude/codex CLI is logged in)</code></p>
</blockquote>
<p><strong>Skip that step.</strong> Your logged-in <code>claude</code> CLI should be sufficient.</p>
<h3>Step 4: Verify it's using your subscription</h3>
<p>Before running the full pipeline, do a small test:</p>
<pre><code class="language-bash">pnpm deepsec process --project-id my-app --filter apps/api/src/middleware --limit 2
</code></pre>
<p>You should see something like:</p>
<pre><code>Investigating 2 file(s) with Claude Agent SDK (claude-opus-4-7)
Turn 1 (15s, 4 tool calls)
Turn 2 (22s, 3 tool calls)
</code></pre>
<p>What you <strong>DON'T</strong> want to see:</p>
<ul>
<li><code>Turn 1 (4s, 0 tool calls)</code> repeated (silent failure — would indicate an old version)</li>
<li><code>402 Insufficient funds</code> with a Vercel link (Gateway active — would indicate an old version)</li>
</ul>
<p>If you see either of those, you're probably on an old deepsec version. Update with <code>pnpm update deepsec</code> and verify with <code>cat node_modules/deepsec/package.json | grep version</code>.</p>
<hr />
<h2>Pipeline commands: what each one does</h2>
<h3><code>scan</code> — Static analysis with regex</h3>
<pre><code class="language-bash">pnpm deepsec scan
</code></pre>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>Detail</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Uses AI?</strong></td>
<td>❌ No</td>
</tr>
<tr>
<td><strong>Cost</strong></td>
<td>Free</td>
</tr>
<tr>
<td><strong>Time</strong></td>
<td>~15s for 2k files</td>
</tr>
<tr>
<td><strong>Output</strong></td>
<td>Files marked as <code>pending</code> with <code>candidates</code></td>
</tr>
</tbody></table>
<p><strong>What it does:</strong> Runs ~110 predefined regex patterns across your codebase and flags suspicious files. Doesn't produce final findings — just "candidates" worth investigating with AI.</p>
<p><strong>What to expect:</strong> A list of files with candidates (e.g., "this file has template literals in HTML, possible XSS"). No verdict, no severity.</p>
<hr />
<h3><code>process</code> — AI-powered investigation</h3>
<pre><code class="language-bash">pnpm deepsec process --project-id my-app
</code></pre>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>Detail</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Uses AI?</strong></td>
<td>✅ Yes (Opus 4.7 at max thinking)</td>
</tr>
<tr>
<td><strong>Cost</strong></td>
<td>$$$ — burns subscription tokens</td>
</tr>
<tr>
<td><strong>Time</strong></td>
<td>Minutes to hours depending on size</td>
</tr>
<tr>
<td><strong>Output</strong></td>
<td>Files with <code>findings[]</code> and <code>analyzed</code> status</td>
</tr>
</tbody></table>
<p><strong>What it does:</strong> For each candidate file from <code>scan</code>, it launches an AI agent that <strong>reads the code, traces data flows, looks for mitigations, considers context</strong> and produces findings with severity (CRITICAL, HIGH, MEDIUM, LOW, BUG).</p>
<p><strong>What to expect:</strong> A list of findings per file, each with:</p>
<ul>
<li>Vulnerability category (<code>xss</code>, <code>sql-injection</code>, <code>auth-bypass</code>, etc.)</li>
<li>Severity</li>
<li>Reasoning</li>
<li>Affected lines</li>
</ul>
<p><strong>Useful flags:</strong></p>
<pre><code class="language-bash"># Process only files in a specific path (useful for testing)
pnpm deepsec process --project-id my-app --filter apps/api/src

# Process at most N files
pnpm deepsec process --project-id my-app --limit 10

# Re-analyze everything from scratch (DELETES previous findings, use carefully)
pnpm deepsec process --project-id my-app --reinvestigate

# Change concurrency (default is usually fine)
pnpm deepsec process --project-id my-app --concurrency 5 --batch-size 5
</code></pre>
<p><strong>Idempotency:</strong> <code>process</code> without <code>--reinvestigate</code> only processes files in <code>pending</code> state. If interrupted (rate limit, dead session), just rerun the command and it resumes where it left off.</p>
<hr />
<h3><code>revalidate</code> — Reduces false positives</h3>
<pre><code class="language-bash">pnpm deepsec revalidate --project-id my-app
</code></pre>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>Detail</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Uses AI?</strong></td>
<td>✅ Yes (same model, similar cost to <code>process</code>)</td>
</tr>
<tr>
<td><strong>Cost</strong></td>
<td>$$$ — comparable to process</td>
</tr>
<tr>
<td><strong>Time</strong></td>
<td>Similar to process</td>
</tr>
<tr>
<td><strong>Output</strong></td>
<td>Findings tagged with verdict</td>
</tr>
</tbody></table>
<p><strong>What it does:</strong> For each finding generated, launches the agent to re-evaluate it: is it actually exploitable? Is it mitigated upstream? Was it fixed in recent commits? Assigns a verdict:</p>
<ul>
<li><strong>TP</strong> (True Positive) — real bug, worth fixing</li>
<li><strong>FP</strong> (False Positive) — the <code>process</code> agent got it wrong</li>
<li><strong>Fixed</strong> — already fixed in some commit</li>
<li><strong>Uncertain</strong> — can't determine without more context</li>
</ul>
<p><strong>What to expect:</strong></p>
<pre><code>Revalidation complete. Run: 20260506180136-...
TP: 116  FP: 4  Fixed: 0  Uncertain: 0
</code></pre>
<p><strong>Useful flags:</strong></p>
<pre><code class="language-bash"># Re-revalidate specific findings (by path)
pnpm deepsec revalidate --filter apps/api/src/routes/auth

# Force re-validation of already revalidated findings
pnpm deepsec revalidate --force
</code></pre>
<p><strong>Idempotency:</strong> revalidate without <code>--force</code> only processes findings without a verdict yet. If it ended with "120/122 revalidated", running it again only processes the 2 missing ones.</p>
<hr />
<h3><code>export</code> — Export findings to readable files</h3>
<pre><code class="language-bash">pnpm deepsec export --format md-dir --out ./findings
</code></pre>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>Detail</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Uses AI?</strong></td>
<td>❌ No</td>
</tr>
<tr>
<td><strong>Cost</strong></td>
<td>Free</td>
</tr>
<tr>
<td><strong>Time</strong></td>
<td>Seconds</td>
</tr>
<tr>
<td><strong>Output</strong></td>
<td>Folder with one .md per finding</td>
</tr>
</tbody></table>
<p><strong>What it does:</strong> Reads findings + verdicts from <code>data/&lt;id&gt;/files/</code> and exports them in a human-readable format. No AI, no cost.</p>
<p><strong>Available formats:</strong></p>
<ul>
<li><code>md-dir</code> — one Markdown per finding (recommended for human review)</li>
<li><code>json</code> — single JSON with all findings (for pipelines / tooling)</li>
</ul>
<hr />
<h3><code>status</code> — View project state</h3>
<pre><code class="language-bash">pnpm deepsec status --project-id my-app
</code></pre>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>Detail</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Uses AI?</strong></td>
<td>❌ No</td>
</tr>
<tr>
<td><strong>Cost</strong></td>
<td>Free</td>
</tr>
</tbody></table>
<p>Shows:</p>
<ul>
<li>How many files in each status (<code>analyzed</code>, <code>pending</code>, <code>processing</code>)</li>
<li>Finding counts by severity</li>
<li>Revalidation results</li>
<li>Recent runs with their equivalent cost</li>
</ul>
<p><strong>Expected output when pipeline finishes:</strong></p>
<pre><code>Project: my-app
  Files tracked: 650
  Status
    analyzed:   650    ← 100%
    pending:    0
  Findings
    CRITICAL: 1  |  HIGH: 11  |  MEDIUM: 41  |  BUG: 64
    Revalidated: 122/122  TP: 116  FP: 4  Fixed: 0  Uncertain: 0
</code></pre>
<blockquote>
<p><strong>Important:</strong> The USD costs shown (<code>\(24.10</code>, <code>\)10.93</code>) are the <strong>equivalent value</strong> if you were paying the API directly, not what Anthropic actually charged you. If you use a subscription, everything comes from your quota — <code>$0</code> actually billed.</p>
</blockquote>
<hr />
<h2>Verifying the analysis was real (not a Gateway bug zombie)</h2>
<p>After <code>process</code>, especially if you had previous Gateway issues, verify that files were actually analyzed:</p>
<pre><code class="language-bash"># Count files marked as "analyzed" but with 0 tokens (zombies from the bug)
find data/&lt;project-id&gt;/files -name "*.json" -type f -exec python3 -c "
import json, sys
try:
    d = json.load(open(sys.argv[1]))
    h = d.get('analysisHistory', [])
    if h and d.get('status') == 'analyzed':
        last = h[-1]
        if last.get('usage', {}).get('inputTokens', 0) == 0:
            print(sys.argv[1])
except: pass
" {} \; 2&gt;/dev/null | wc -l
</code></pre>
<p>If this count is <strong>&gt; 0</strong>, those files were marked as analyzed without actually going through the AI. Re-process the project:</p>
<pre><code class="language-bash">pnpm deepsec process --project-id my-app --reinvestigate
</code></pre>
<hr />
<h2>Estimated cost</h2>
<p>For a medium codebase (~650 files):</p>
<table>
<thead>
<tr>
<th>Stage</th>
<th>Tokens (approx)</th>
<th>$ equivalent</th>
</tr>
</thead>
<tbody><tr>
<td><code>scan</code></td>
<td>0</td>
<td>$0</td>
</tr>
<tr>
<td><code>process</code></td>
<td>300k - 500k</td>
<td>\(20 - \)30</td>
</tr>
<tr>
<td><code>revalidate</code></td>
<td>100k - 200k</td>
<td>\(8 - \)15</td>
</tr>
<tr>
<td><code>export</code></td>
<td>0</td>
<td>$0</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><strong>~500k - 700k</strong></td>
<td><strong>~\(30 - \)45</strong></td>
</tr>
</tbody></table>
<p>If you use a <strong>Claude Max (\(100/month) subscription</strong>, this pipeline fits comfortably and leaves you headroom for normal use the rest of the month. <strong>Pro (\)20/month)</strong> will likely max out partway through a large project.</p>
<hr />
<h2>Triaging findings: what to do after exporting</h2>
<p>With 116 confirmed TPs (true positives), you need a systematic review plan:</p>
<h3>Prioritize by severity</h3>
<ol>
<li><strong>CRITICAL</strong> → review today, doesn't wait</li>
<li><strong>HIGH</strong> → this week</li>
<li><strong>MEDIUM</strong> → next two weeks</li>
<li><strong>BUG / HIGH_BUG</strong> → code quality backlog (don't block security)</li>
</ol>
<h3>Group by category, not by file</h3>
<p>Many findings are the same bug repeated across multiple files. Grouping by <code>vulnSlug</code> (e.g., all <code>xss</code> together) lets you fix 5 findings with one change (e.g., adding sanitization to a shared helper).</p>
<h3>Filter by real attack surface</h3>
<p>A finding in <code>apps/api/src/routes/public.ts</code> (receives input from the internet) weighs much more than one in <code>apps/web/src/components/InternalDashboard.tsx</code>. Critical question for each finding: <strong>is the input arriving here from an attacker?</strong></p>
<h3>For each TP, three questions</h3>
<ol>
<li>Is it <strong>really</strong> exploitable, not theoretical?</li>
<li>Is there <strong>upstream mitigation</strong> the agent didn't see? (middleware validation, rate limiting, auth)</li>
<li>Does the <strong>fix introduce regressions</strong>? (are there tests covering the path?)</li>
</ol>
<hr />
<h2>Common errors and solutions</h2>
<h3>"402 Insufficient funds" with vercel.com link</h3>
<p><strong>Cause:</strong> You're on an old deepsec version (pre-2.0.2) where the OIDC token fallback was active by default.</p>
<p><strong>Solution:</strong> Update deepsec:</p>
<pre><code class="language-bash">pnpm update deepsec
cat node_modules/deepsec/package.json | grep version
# Should be &gt;= 2.0.2
</code></pre>
<h3>"0 tool calls, 0 tokens" in every batch</h3>
<p><strong>Cause:</strong> The SDK has no valid credentials and is failing silently. Common in pre-2.0.2 versions when trying to avoid the Gateway with <code>ANTHROPIC_BASE_URL=https://api.anthropic.com</code> without a token.</p>
<p><strong>Solution:</strong></p>
<ol>
<li>Update to deepsec ≥ 2.0.2 (<code>pnpm update deepsec</code>)</li>
<li>Verify <code>claude --print "test"</code> responds normally</li>
<li>Make sure you DON'T have <code>ANTHROPIC_BASE_URL</code> or <code>ANTHROPIC_AUTH_TOKEN</code> in <code>.env.local</code> or shell (unless you actually want to use the Gateway or a direct API key)</li>
</ol>
<h3>Claude rate limit during <code>process</code></h3>
<p><strong>Cause:</strong> Your plan hit its usage limit (5h on Pro, longer windows on Max).</p>
<p><strong>Solution:</strong> Wait for the window to reset and re-run <code>pnpm deepsec process</code>. It's idempotent and resumes where it left off.</p>
<h3>Files stuck in <code>processing</code> forever</h3>
<p><strong>Cause:</strong> A previous run died without releasing locks.</p>
<p><strong>Solution:</strong></p>
<pre><code class="language-bash"># Clean up zombie runs
rm -rf data/&lt;project-id&gt;/runs/*
# Re-run process — files in "processing" will be reprocessed
pnpm deepsec process --project-id my-app
</code></pre>
<hr />
<h2>What to commit and what not to</h2>
<p><code>.deepsec/</code> contains a mix of configuration files (which DO go to git) and run output (which should NOT go to git for security reasons). The <code>init</code> already generates a reasonable <code>.gitignore</code>, but you need to verify it covers exports too.</p>
<h3>What DOES get committed</h3>
<p>These files are the "scan recipe" and let any team member replicate the setup:</p>
<ul>
<li><code>deepsec.config.ts</code> — project config (matchers, paths, plugins)</li>
<li><code>package.json</code> + <code>pnpm-lock.yaml</code> — for reproducibility</li>
<li><code>pnpm-workspace.yaml</code></li>
<li><code>AGENTS.md</code> — instructions for coding agents</li>
<li><code>README.md</code></li>
<li><code>.gitignore</code></li>
<li><code>data/&lt;id&gt;/INFO.md</code> — project context injected into prompts (the most valuable file!)</li>
<li><code>data/&lt;id&gt;/SETUP.md</code> — per-project instructions</li>
<li><code>data/&lt;id&gt;/config.json</code> (if it exists) — <code>priorityPaths</code>, <code>promptAppend</code>, <code>ignorePaths</code></li>
</ul>
<h3>What does NOT get committed</h3>
<pre><code class="language-gitignore"># .deepsec/.gitignore
node_modules/
.env*.local
.vercel/

# Scan output — regenerated by `deepsec scan` / `process`. INFO.md
# and SETUP.md (manually edited) stay tracked.
data/*/files/
data/*/runs/
data/*/reports/
data/*/project.json

# Auto-detected metadata (regenerated each run, contains absolute paths)
data/*/tech.json

# Exported findings (regenerated by `deepsec export`)
findings/
exports/
</code></pre>
<p><strong><code>init</code> generates the first sections automatically, but does NOT include <code>tech.json</code>, <code>findings/</code>, or <code>exports/</code></strong>. Add them yourself:</p>
<pre><code class="language-bash">cat &gt;&gt; .deepsec/.gitignore &lt;&lt; 'EOF'

# Auto-detected metadata (regenerated each run, contains absolute paths)
data/*/tech.json

# Exported findings (regenerated by `deepsec export`)
findings/
exports/
EOF
</code></pre>
<blockquote>
<p>💡 <strong>Why ignore <code>tech.json</code>:</strong> It contains the absolute <code>rootPath</code> of the machine where the scan was run (e.g., <code>/Users/jdoe/projects/my-app</code>), which reveals the developer's username and local structure. It also regenerates on every run, so versioning it just creates noise in diffs.</p>
</blockquote>
<h3>Why findings and exports should NOT go to git</h3>
<p>The files in <code>data/&lt;id&gt;/files/</code> and the export folder contain:</p>
<ul>
<li><strong>Exact lines of vulnerable code</strong> with snippets</li>
<li><strong>Detailed agent reasoning</strong> about how to exploit each vuln</li>
<li><strong>Verdicts</strong> from revalidate</li>
</ul>
<p>If your repo is public, that's basically a <strong>step-by-step attack guide for your app</strong>. Even in private repos, consider that git history is forever — a finding that's fixed today still shows in the commit history how the bug looked.</p>
<h3>Verify nothing leaked into tracking</h3>
<p>If you committed something before configuring the gitignore properly:</p>
<pre><code class="language-bash">git ls-files .deepsec/ | grep -E "findings/|/files/|/runs/|/reports/"
</code></pre>
<p>If it returns anything, untrack them:</p>
<pre><code class="language-bash">git rm -r --cached .deepsec/findings/ 2&gt;/dev/null
git rm -r --cached .deepsec/data/*/files/ 2&gt;/dev/null
git rm -r --cached .deepsec/data/*/runs/ 2&gt;/dev/null
git rm -r --cached .deepsec/data/*/reports/ 2&gt;/dev/null
git commit -m "chore: untrack deepsec output files"
</code></pre>
<blockquote>
<p>⚠️ <code>git rm --cached</code> only removes them from future tracking, <strong>not from history</strong>. If findings expose unfixed vulns and the repo has many collaborators, consider cleaning history with <code>git filter-repo</code> or BFG.</p>
</blockquote>
<h3>Where do findings live then?</h3>
<p>Exported findings are <strong>local and temporary</strong>. Three common patterns to manage them:</p>
<ol>
<li><strong>Local without persisting</strong> — for one-off scans, each person reviews their own</li>
<li><strong>Separate private repo</strong> — <code>my-project-security/</code> with restricted access</li>
<li><strong>Ticket system</strong> (recommended) — relevant TP findings become Linear/Jira/GitHub issues with assignees and deadlines; FPs are discarded</li>
</ol>
<p>For serious teams, option 3 is the standard: deepsec generates the list, humans triage, and important findings flow into normal workstreams.</p>
<hr />
<h2>Resources</h2>
<ul>
<li>Official repo: <a href="https://github.com/vercel-labs/deepsec">https://github.com/vercel-labs/deepsec</a></li>
<li>Docs: <a href="https://github.com/vercel-labs/deepsec/tree/main/docs">https://github.com/vercel-labs/deepsec/tree/main/docs</a></li>
<li>Launch blog post: <a href="https://vercel.com/blog/introducing-deepsec-find-and-fix-vulnerabilities-in-your-code-base">https://vercel.com/blog/introducing-deepsec-find-and-fix-vulnerabilities-in-your-code-base</a></li>
</ul>
<hr />
<h2>Best practices for teams</h2>
<p>General recommendations based on the experience of implementing deepsec on a real project.</p>
<h3>Designate an owner</h3>
<p>Although anyone on the team can run deepsec following this guide (the files in <code>.deepsec/</code> are versioned and the setup is reproducible with <code>pnpm install</code>), it helps to designate someone as process owner to avoid duplication and keep triaging consistent.</p>
<h3>When to scan</h3>
<p>Typical cases where running a full scan makes sense:</p>
<ul>
<li>After merging a large feature touching auth, input handling, or payment/sensitive data flows</li>
<li>Before a production release with significant changes</li>
<li>When a vuln is reported in one of the stack's critical dependencies</li>
<li>Periodically (every 3-6 months) as general hygiene</li>
<li>When deciding to re-evaluate previous findings after applying fixes</li>
</ul>
<p>For small or isolated changes, scanning the modified directory is enough:</p>
<pre><code class="language-bash">pnpm deepsec process --project-id my-app --filter apps/api/src/routes/&lt;module&gt;
</code></pre>
<h3>Findings management</h3>
<p>We recommend integrating critical and high findings into the team's ticket system (Linear, Jira, private GitHub Issues, internal system, etc). After each scan, the owner triages the TPs and creates entries for the ones requiring action, assigning them to the corresponding devs.</p>
<p>Exported findings (<code>./findings/</code>) should stay <strong>only local</strong> on the machine of whoever scanned — they don't get committed to the repo (see "What to commit and what not to" section).</p>
<h3>Expected costs</h3>
<p>For a medium codebase (~650 files), a full pipeline (<code>process</code> + <code>revalidate</code>) consumes between <strong>$30-50 in API equivalent</strong> and takes <strong>15-30 minutes</strong> of wall-clock.</p>
<p>Options to cover the cost:</p>
<ul>
<li><strong>Personal Claude Max subscription (\(100/month)</strong> — viable for ad-hoc team scans. \)0 actually billed if it fits in your quota.</li>
<li><strong>Vercel AI Gateway with <code>AI_GATEWAY_API_KEY</code></strong> — recommended for CI/CD or frequent scans. Allows spending caps and attribution to the corporate account.</li>
<li><strong>Direct Anthropic API key</strong> — if the team already pays for direct API for other purposes.</li>
</ul>
<h3>Escalation policy</h3>
<p>For <strong>CRITICAL</strong> or <strong>HIGH</strong> severity findings, it helps to have a direct escalation channel to the security owner before any release. <strong>MEDIUM</strong> and <strong>BUG</strong> findings can follow the normal ticket flow.</p>
<hr />
<h2>Final notes</h2>
<ul>
<li><strong>If you find secrets in findings</strong>, rotate them immediately. The presence of a secret in a finding implies it was in the source code.</li>
<li><strong>The USD cost report</strong> that deepsec shows is always the <strong>API equivalent</strong>, not what was charged to your account. If you use a subscription, the <code>$</code> are reference values.</li>
<li><strong><code>--reinvestigate</code> deletes previous work.</strong> Only use it when you really want to start over.</li>
</ul>
<hr />
<p><em>This guide covers deepsec ≥ 2.0.2 — last updated: May 2026.</em></p>
]]></content:encoded></item><item><title><![CDATA[Deepsec (Vercel Security Harness): guía práctica de configuración, troubleshooting y triaje de findings]]></title><description><![CDATA[Guía práctica de deepsec
Guía completa para usar deepsec — el security harness de Vercel — entendiendo qué hace cada comando, cómo configurarlo correctamente para usar tu suscripción de Claude Code (e]]></description><link>https://blog.diegocornejo.com/deepsec-vercel-security-harness-guia-practica</link><guid isPermaLink="true">https://blog.diegocornejo.com/deepsec-vercel-security-harness-guia-practica</guid><category><![CDATA[Security]]></category><category><![CDATA[Vercel]]></category><category><![CDATA[claude]]></category><category><![CDATA[AI]]></category><category><![CDATA[guide]]></category><category><![CDATA[deepsec ]]></category><dc:creator><![CDATA[Diego Cornejo]]></dc:creator><pubDate>Wed, 06 May 2026 21:11:14 GMT</pubDate><content:encoded><![CDATA[<h1>Guía práctica de deepsec</h1>
<p>Guía completa para usar <a href="https://github.com/vercel-labs/deepsec">deepsec</a> — el security harness de Vercel — entendiendo qué hace cada comando, cómo configurarlo correctamente para usar tu suscripción de Claude Code (en vez del AI Gateway de Vercel), y cómo triar los findings que produce.</p>
<blockquote>
<p><strong>Contexto:</strong> deepsec es una herramienta de análisis de seguridad que usa coding agents (Claude o Codex) para investigar vulnerabilidades en tu codebase. Usa Opus 4.7 a thinking máximo por defecto, lo que la hace cara — pero también muy capaz.</p>
</blockquote>
<blockquote>
<p><strong>Esta guía cubre deepsec ≥ 2.0.2.</strong> Versiones anteriores tenían un bug crítico de billing que se resolvió en <a href="https://github.com/vercel-labs/deepsec/pull/43">PR #43</a>. Si estás en versión previa, actualiza con <code>pnpm update deepsec</code> antes de seguir.</p>
</blockquote>
<hr />
<h2>TL;DR del flujo correcto</h2>
<pre><code class="language-bash"># 1. Setup limpio
claude login                                   # tu suscripción Pro/Max

# 2. Init y bootstrap
cd ~/proyectos/mi-app
npx deepsec init
cd .deepsec
pnpm install

# 3. Pipeline (en orden)
pnpm deepsec scan                              # gratis, sin IA
pnpm deepsec process --project-id mi-app       # CARO, usa IA
pnpm deepsec revalidate                        # CARO, usa IA
pnpm deepsec export --format md-dir --out ./findings   # gratis, sin IA
</code></pre>
<blockquote>
<p>📝 <strong>Requiere deepsec ≥ 2.0.2.</strong> Versiones anteriores tenían un bug que ruteaba el tráfico al Vercel AI Gateway aunque el usuario no lo hubiera elegido. Si estás en una versión previa, actualiza con <code>pnpm update deepsec</code>.</p>
</blockquote>
<hr />
<h2>Contexto histórico: cambio de comportamiento en v2.0.2</h2>
<blockquote>
<p>✅ Si tu versión de deepsec es ≥ 2.0.2, puedes saltarte esta sección. Se documenta solo para usuarios que vengan de versiones previas y para explicar por qué algunos repos pueden tener residuos como un <code>.vercel/</code> stub dentro de <code>.deepsec/</code>.</p>
</blockquote>
<p>En versiones pre-2.0.2, la lógica de autenticación local tenía un comportamiento poco intuitivo. El README indicaba que deepsec usaría las suscripciones locales de <code>claude</code> o <code>codex</code> automáticamente, pero en la práctica el orden de prioridad hacía que la presencia del Vercel CLI logueado (común en proyectos Next.js) dirigiera todo el tráfico al AI Gateway de Vercel, cobrando contra la cuenta del usuario sin opt-in explícito.</p>
<p>El flujo interno era aproximadamente:</p>
<ol>
<li><p>Deepsec llamaba a <code>getVercelOidcToken()</code> durante preflight</p>
</li>
<li><p>Esa función caminaba hacia arriba desde <code>.deepsec/</code> buscando un <code>.vercel/project.json</code></p>
</li>
<li><p>Si lo encontraba, leía la auth del CLI de Vercel</p>
</li>
<li><p>Solicitaba un OIDC token fresco</p>
</li>
<li><p>Lo expandía a <code>ANTHROPIC_AUTH_TOKEN</code> + <code>ANTHROPIC_BASE_URL=https://ai-gateway.vercel.sh</code></p>
</li>
</ol>
<p>Resultado típico para usuarios sin crédito en Vercel AI Gateway:</p>
<pre><code class="language-plaintext">Agent SDK error: Claude Code returned an error result:
API Error: 402 Insufficient funds. Please add credits to your account...
Visit https://vercel.com/d?to=...
</code></pre>
<p>Adicionalmente, intentar evitar el Gateway poniendo <code>ANTHROPIC_BASE_URL=https://api.anthropic.com</code> sin token producía un <strong>modo de falla silenciosa</strong>: cada archivo terminaba en ~5 segundos con <code>0 tokens, 0 turns, 0 findings</code>, marcado como <code>analyzed</code>. La herramienta reportaba el escaneo como exitoso sin haber llamado a la IA.</p>
<p><strong>Ambos comportamientos cambiaron en</strong> <a href="https://github.com/vercel-labs/deepsec/pull/43"><strong>PR #43</strong></a> <strong>("Use the vercel OIDC token for the gateway if no primary API token is present"), mergeado el 5 de mayo de 2026 y publicado en deepsec 2.0.2.</strong></p>
<p>A partir de v2.0.2, el OIDC token solo se usa como fallback explícito cuando ninguna otra credencial está presente, y la suscripción local de <code>claude</code> CLI tiene precedencia. Esto significa que puedes tener Vercel CLI logueado y deepsec correctamente usará tu suscripción de Claude Code sin desviar tráfico al Gateway.</p>
<hr />
<h2>Cómo configurar para usar tu suscripción de Claude Code</h2>
<h3>Paso 1: Setup limpio del entorno</h3>
<pre><code class="language-bash"># Verifica que no haya variables de entorno conflictivas
env | grep -iE "anthropic|openai|gateway"
# No debería retornar nada relevante

# Verifica que claude CLI esté autenticado con tu suscripción
claude --version
claude --print "test"
# Debería responder normalmente
</code></pre>
<h3>Paso 2: Borrar instalaciones previas si las hay</h3>
<p>Si ya intentaste correr deepsec antes y falló, hay residuos que pueden contaminar el setup:</p>
<pre><code class="language-bash">cd tu-proyecto
rm -rf .deepsec
</code></pre>
<h3>Paso 3: Init desde cero</h3>
<pre><code class="language-bash">npx deepsec init
cd .deepsec
pnpm install
</code></pre>
<p><strong>No hagas</strong> <code>cp .env.example .env</code> ni edites <code>.env.local</code>. El <code>init</code> te dirá:</p>
<blockquote>
<p><code># Set AI_GATEWAY_API_KEY in .env.local (or skip if claude/codex CLI is logged in)</code></p>
</blockquote>
<p><strong>Saltea ese paso.</strong> Tu <code>claude</code> CLI logueado debería ser suficiente.</p>
<h3>Paso 4: Verificar que está usando tu suscripción</h3>
<p>Antes de correr el pipeline completo, haz una prueba pequeña:</p>
<pre><code class="language-bash">pnpm deepsec process --project-id mi-app --filter apps/api/src/middleware --limit 2
</code></pre>
<p>Espera ver algo como:</p>
<pre><code class="language-plaintext">Investigating 2 file(s) with Claude Agent SDK (claude-opus-4-7)
Turn 1 (15s, 4 tool calls)
Turn 2 (22s, 3 tool calls)
</code></pre>
<p>Lo que <strong>NO</strong> quieres ver:</p>
<ul>
<li><p><code>Turn 1 (4s, 0 tool calls)</code> repetido (silent failure — indicaría versión antigua)</p>
</li>
<li><p><code>402 Insufficient funds</code> con link a Vercel (Gateway activo — indicaría versión antigua)</p>
</li>
</ul>
<p>Si ves uno de esos dos, probablemente estás en una versión vieja de deepsec. Actualiza con <code>pnpm update deepsec</code> y verifica con <code>cat node_modules/deepsec/package.json | grep version</code>.</p>
<hr />
<h2>Pipeline de comandos: qué hace cada uno</h2>
<h3><code>scan</code> — Análisis estático con regex</h3>
<pre><code class="language-bash">pnpm deepsec scan
</code></pre>
<table>
<thead>
<tr>
<th>Aspecto</th>
<th>Detalle</th>
</tr>
</thead>
<tbody><tr>
<td><strong>¿Usa IA?</strong></td>
<td>❌ No</td>
</tr>
<tr>
<td><strong>Costo</strong></td>
<td>Gratis</td>
</tr>
<tr>
<td><strong>Tiempo</strong></td>
<td>~15s para 2k archivos</td>
</tr>
<tr>
<td><strong>Output</strong></td>
<td>Archivos marcados como <code>pending</code> con <code>candidates</code></td>
</tr>
</tbody></table>
<p><strong>Qué hace:</strong> Recorre tu codebase con ~110 patrones regex predefinidos y marca archivos sospechosos. No produce findings finales — solo "candidatos" que merecen investigación con IA.</p>
<p><strong>Qué esperar:</strong> Lista de archivos con candidatos (ej: "este archivo tiene template literals en HTML, posible XSS"). Sin veredicto, sin severidad.</p>
<hr />
<h3><code>process</code> — Investigación con IA</h3>
<pre><code class="language-bash">pnpm deepsec process --project-id mi-app
</code></pre>
<table>
<thead>
<tr>
<th>Aspecto</th>
<th>Detalle</th>
</tr>
</thead>
<tbody><tr>
<td><strong>¿Usa IA?</strong></td>
<td>✅ Sí (Opus 4.7 a thinking máximo)</td>
</tr>
<tr>
<td><strong>Costo</strong></td>
<td>$$$ — quemas tokens de tu suscripción</td>
</tr>
<tr>
<td><strong>Tiempo</strong></td>
<td>Minutos a horas según tamaño</td>
</tr>
<tr>
<td><strong>Output</strong></td>
<td>Archivos con <code>findings[]</code> y status <code>analyzed</code></td>
</tr>
</tbody></table>
<p><strong>Qué hace:</strong> Por cada archivo candidato del <code>scan</code>, lanza un agente de IA que <strong>lee el código, traza el flujo de datos, busca mitigaciones, considera el contexto</strong> y produce findings con severidad (CRITICAL, HIGH, MEDIUM, LOW, BUG).</p>
<p><strong>Qué esperar:</strong> Lista de hallazgos por archivo, cada uno con:</p>
<ul>
<li><p>Categoría de vuln (<code>xss</code>, <code>sql-injection</code>, <code>auth-bypass</code>, etc.)</p>
</li>
<li><p>Severidad</p>
</li>
<li><p>Razonamiento</p>
</li>
<li><p>Líneas afectadas</p>
</li>
</ul>
<p><strong>Flags útiles:</strong></p>
<pre><code class="language-bash"># Solo procesar archivos en una ruta específica (útil para testing)
pnpm deepsec process --project-id mi-app --filter apps/api/src

# Procesar máximo N archivos
pnpm deepsec process --project-id mi-app --limit 10

# Re-analizar todo desde cero (BORRA findings previos, usar con cuidado)
pnpm deepsec process --project-id mi-app --reinvestigate

# Cambiar concurrencia (default suele estar bien)
pnpm deepsec process --project-id mi-app --concurrency 5 --batch-size 5
</code></pre>
<p><strong>Idempotencia:</strong> <code>process</code> sin <code>--reinvestigate</code> solo procesa archivos en estado <code>pending</code>. Si se interrumpe (rate limit, sesión muerta), simplemente vuelves a correr el comando y reanuda donde quedó.</p>
<hr />
<h3><code>revalidate</code> — Reduce falsos positivos</h3>
<pre><code class="language-bash">pnpm deepsec revalidate --project-id mi-app
</code></pre>
<table>
<thead>
<tr>
<th>Aspecto</th>
<th>Detalle</th>
</tr>
</thead>
<tbody><tr>
<td><strong>¿Usa IA?</strong></td>
<td>✅ Sí (mismo modelo, similar costo a <code>process</code>)</td>
</tr>
<tr>
<td><strong>Costo</strong></td>
<td>$$$ — comparable a process</td>
</tr>
<tr>
<td><strong>Tiempo</strong></td>
<td>Similar a process</td>
</tr>
<tr>
<td><strong>Output</strong></td>
<td>Findings etiquetados con veredicto</td>
</tr>
</tbody></table>
<p><strong>Qué hace:</strong> Por cada finding generado, lanza al agente a re-evaluarlo: ¿es realmente explotable? ¿Está mitigado upstream? ¿Se arregló en commits recientes? Asigna un veredicto:</p>
<ul>
<li><p><strong>TP</strong> (True Positive) — bug real, vale la pena arreglar</p>
</li>
<li><p><strong>FP</strong> (False Positive) — el agente del <code>process</code> se equivocó</p>
</li>
<li><p><strong>Fixed</strong> — ya fue arreglado en algún commit</p>
</li>
<li><p><strong>Uncertain</strong> — no se puede determinar sin más contexto</p>
</li>
</ul>
<p><strong>Qué esperar:</strong></p>
<pre><code class="language-plaintext">Revalidation complete. Run: 20260506180136-...
TP: 116  FP: 4  Fixed: 0  Uncertain: 0
</code></pre>
<p><strong>Flags útiles:</strong></p>
<pre><code class="language-bash"># Re-revalidar findings específicos (por path)
pnpm deepsec revalidate --filter apps/api/src/routes/auth

# Forzar re-revalidación de findings ya revalidados
pnpm deepsec revalidate --force
</code></pre>
<p><strong>Idempotencia:</strong> revalidate sin <code>--force</code> solo procesa findings que aún no tienen veredicto. Si terminó con "120/122 revalidated", correrlo de nuevo procesa solo los 2 faltantes.</p>
<hr />
<h3><code>export</code> — Exportar findings a archivos legibles</h3>
<pre><code class="language-bash">pnpm deepsec export --format md-dir --out ./findings
</code></pre>
<table>
<thead>
<tr>
<th>Aspecto</th>
<th>Detalle</th>
</tr>
</thead>
<tbody><tr>
<td><strong>¿Usa IA?</strong></td>
<td>❌ No</td>
</tr>
<tr>
<td><strong>Costo</strong></td>
<td>Gratis</td>
</tr>
<tr>
<td><strong>Tiempo</strong></td>
<td>Segundos</td>
</tr>
<tr>
<td><strong>Output</strong></td>
<td>Carpeta con un .md por finding</td>
</tr>
</tbody></table>
<p><strong>Qué hace:</strong> Lee los findings + veredictos del <code>data/&lt;id&gt;/files/</code> y los exporta a un formato leíble para humanos. Sin IA, sin costo.</p>
<p><strong>Formatos disponibles:</strong></p>
<ul>
<li><p><code>md-dir</code> — un Markdown por finding (recomendado para revisión humana)</p>
</li>
<li><p><code>json</code> — un solo JSON con todos los findings (para pipeline / herramientas)</p>
</li>
</ul>
<hr />
<h3><code>status</code> — Ver estado del proyecto</h3>
<pre><code class="language-bash">pnpm deepsec status --project-id mi-app
</code></pre>
<table>
<thead>
<tr>
<th>Aspecto</th>
<th>Detalle</th>
</tr>
</thead>
<tbody><tr>
<td><strong>¿Usa IA?</strong></td>
<td>❌ No</td>
</tr>
<tr>
<td><strong>Costo</strong></td>
<td>Gratis</td>
</tr>
</tbody></table>
<p>Muestra:</p>
<ul>
<li><p>Cuántos archivos hay en cada status (<code>analyzed</code>, <code>pending</code>, <code>processing</code>)</p>
</li>
<li><p>Conteo de findings por severidad</p>
</li>
<li><p>Resultados de revalidation</p>
</li>
<li><p>Runs recientes con su costo equivalente</p>
</li>
</ul>
<p><strong>Output esperado al terminar pipeline:</strong></p>
<pre><code class="language-plaintext">Project: mi-app
  Files tracked: 650
  Status
    analyzed:   650    ← 100%
    pending:    0
  Findings
    CRITICAL: 1  |  HIGH: 11  |  MEDIUM: 41  |  BUG: 64
    Revalidated: 122/122  TP: 116  FP: 4  Fixed: 0  Uncertain: 0
</code></pre>
<blockquote>
<p><strong>Importante:</strong> Los costos en USD que muestra (<code>\(24.10</code>, <code>\)10.93</code>) son <strong>valor equivalente</strong> si pagaras la API directa, no lo que te cobró Anthropic. Si usas tu suscripción, todo sale de tu cuota — <code>$0</code> reales.</p>
</blockquote>
<hr />
<h2>Verificar que el análisis fue real (no zombi del bug del Gateway)</h2>
<p>Después de <code>process</code>, especialmente si tuviste problemas previos con el Gateway, verifica que los archivos hayan sido analizados de verdad:</p>
<pre><code class="language-bash"># Cuenta archivos "analyzed" pero con 0 tokens (zombis del bug)
find data/&lt;project-id&gt;/files -name "*.json" -type f -exec python3 -c "
import json, sys
try:
    d = json.load(open(sys.argv[1]))
    h = d.get('analysisHistory', [])
    if h and d.get('status') == 'analyzed':
        last = h[-1]
        if last.get('usage', {}).get('inputTokens', 0) == 0:
            print(sys.argv[1])
except: pass
" {} \; 2&gt;/dev/null | wc -l
</code></pre>
<p>Si este conteo es <strong>&gt; 0</strong>, esos archivos fueron marcados como analizados sin haber pasado por la IA. Re-procesa el proyecto:</p>
<pre><code class="language-bash">pnpm deepsec process --project-id mi-app --reinvestigate
</code></pre>
<hr />
<h2>Costo estimado</h2>
<p>Para una codebase mediana (~650 archivos):</p>
<table>
<thead>
<tr>
<th>Stage</th>
<th>Tokens (aprox)</th>
<th>$ equivalente</th>
</tr>
</thead>
<tbody><tr>
<td><code>scan</code></td>
<td>0</td>
<td>$0</td>
</tr>
<tr>
<td><code>process</code></td>
<td>300k - 500k</td>
<td>\(20 - \)30</td>
</tr>
<tr>
<td><code>revalidate</code></td>
<td>100k - 200k</td>
<td>\(8 - \)15</td>
</tr>
<tr>
<td><code>export</code></td>
<td>0</td>
<td>$0</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><strong>~500k - 700k</strong></td>
<td><strong>~\(30 - \)45</strong></td>
</tr>
</tbody></table>
<p>Si usas tu suscripción de <strong>Claude Max (\(100/mes)</strong>, este pipeline cabe cómodamente y te deja margen para uso normal el resto del mes. Plan <strong>Pro (\)20/mes)</strong> probablemente se sature en mitad de un proyecto grande.</p>
<hr />
<h2>Triaje de findings: qué hacer después de exportar</h2>
<p>Con 116 TPs (true positives) confirmados, necesitas un plan de revisión sistemático:</p>
<h3>Prioriza por severidad</h3>
<ol>
<li><p><strong>CRITICAL</strong> → revísalo hoy, no espera</p>
</li>
<li><p><strong>HIGH</strong> → esta semana</p>
</li>
<li><p><strong>MEDIUM</strong> → próximas 2 semanas</p>
</li>
<li><p><strong>BUG / HIGH_BUG</strong> → backlog de calidad de código (no bloquean seguridad)</p>
</li>
</ol>
<h3>Agrupa por categoría, no por archivo</h3>
<p>Muchos findings son el mismo bug repetido en múltiples archivos. Agrupar por <code>vulnSlug</code> (ej: todos los <code>xss</code> juntos) te permite arreglar 5 findings con un solo cambio (ej: agregar sanitización en el helper compartido).</p>
<h3>Filtra por superficie de ataque real</h3>
<p>Un finding en <code>apps/api/src/routes/public.ts</code> (recibe input de internet) pesa muchísimo más que uno en <code>apps/web/src/components/InternalDashboard.tsx</code>. Pregunta crítica para cada finding: <strong>¿el input que llega aquí viene de un atacante?</strong></p>
<h3>Para cada TP, tres preguntas</h3>
<ol>
<li><p>¿Es <strong>realmente</strong> explotable, no teórico?</p>
</li>
<li><p>¿Hay <strong>mitigación upstream</strong> que el agente no vio? (validación en middleware, rate limiting, auth)</p>
</li>
<li><p>¿El <strong>arreglo introduce regresiones</strong>? (¿hay tests que cubran el camino?)</p>
</li>
</ol>
<hr />
<h2>Errores comunes y soluciones</h2>
<h3>"402 Insufficient funds" con link a vercel.com</h3>
<p><strong>Causa:</strong> Estás en una versión vieja de deepsec (pre-2.0.2) donde el OIDC token fallback estaba activo por defecto.</p>
<p><strong>Solución:</strong> Actualiza deepsec:</p>
<pre><code class="language-bash">pnpm update deepsec
cat node_modules/deepsec/package.json | grep version
# Debe ser &gt;= 2.0.2
</code></pre>
<h3>"0 tool calls, 0 tokens" en cada batch</h3>
<p><strong>Causa:</strong> El SDK no tiene credenciales válidas y está fallando en silencio. Suele pasar en versiones pre-2.0.2 cuando se intentaba evitar el Gateway con <code>ANTHROPIC_BASE_URL=https://api.anthropic.com</code> sin token.</p>
<p><strong>Solución:</strong></p>
<ol>
<li><p>Actualiza a deepsec ≥ 2.0.2 (<code>pnpm update deepsec</code>)</p>
</li>
<li><p>Verifica <code>claude --print "test"</code> responde normalmente</p>
</li>
<li><p>Asegúrate de NO tener <code>ANTHROPIC_BASE_URL</code> ni <code>ANTHROPIC_AUTH_TOKEN</code> en <code>.env.local</code> ni en el shell (a menos que sí quieras usar el Gateway o una API key directa)</p>
</li>
</ol>
<h3>Rate limit de Claude durante <code>process</code></h3>
<p><strong>Causa:</strong> Tu plan se topó con el límite de uso (5h Pro, ventanas más largas Max).</p>
<p><strong>Solución:</strong> Espera a que se resetee la ventana y vuelve a correr <code>pnpm deepsec process</code>. Es idempotente y reanuda donde quedó.</p>
<h3>Archivos quedan en <code>processing</code> para siempre</h3>
<p><strong>Causa:</strong> Run anterior murió sin liberar locks.</p>
<p><strong>Solución:</strong></p>
<pre><code class="language-bash"># Limpia los runs zombi
rm -rf data/&lt;project-id&gt;/runs/*
# Re-corre process — los archivos en "processing" se re-procesarán
pnpm deepsec process --project-id mi-app
</code></pre>
<hr />
<h2>Qué commitear y qué no</h2>
<p><code>.deepsec/</code> contiene una mezcla de archivos de configuración (que sí van a git) y output de runs (que NO debe ir a git por seguridad). El <code>init</code> ya genera un <code>.gitignore</code> razonable, pero hay que verificar que cubra los exports también.</p>
<h3>Lo que SÍ se commitea</h3>
<p>Estos archivos son la "receta" del escaneo y permiten que cualquier persona del equipo replique el setup:</p>
<ul>
<li><p><code>deepsec.config.ts</code> — config del proyecto (matchers, paths, plugins)</p>
</li>
<li><p><code>package.json</code> + <code>pnpm-lock.yaml</code> — para reproducibilidad</p>
</li>
<li><p><code>pnpm-workspace.yaml</code></p>
</li>
<li><p><code>AGENTS.md</code> — instrucciones para coding agents</p>
</li>
<li><p><code>README.md</code></p>
</li>
<li><p><code>.gitignore</code></p>
</li>
<li><p><code>data/&lt;id&gt;/INFO.md</code> — contexto del proyecto inyectado a los prompts (¡el archivo más valioso!)</p>
</li>
<li><p><code>data/&lt;id&gt;/SETUP.md</code> — instrucciones por proyecto</p>
</li>
<li><p><code>data/&lt;id&gt;/config.json</code> (si existe) — <code>priorityPaths</code>, <code>promptAppend</code>, <code>ignorePaths</code></p>
</li>
</ul>
<h3>Lo que NO se commitea</h3>
<pre><code class="language-gitignore"># .deepsec/.gitignore
node_modules/
.env*.local
.vercel/

# Scan output — regenerated by `deepsec scan` / `process`. INFO.md
# and SETUP.md (manually edited) stay tracked.
data/*/files/
data/*/runs/
data/*/reports/
data/*/project.json

# Auto-detected metadata (regenerated each run, contains absolute paths)
data/*/tech.json

# Exported findings (regenerated by `deepsec export`)
findings/
exports/
</code></pre>
<p><strong>El</strong> <code>init</code> <strong>genera las primeras secciones automáticamente, pero NO incluye</strong> <code>tech.json</code><strong>,</strong> <code>findings/</code> <strong>ni</strong> <code>exports/</code>. Agrégalos tú:</p>
<pre><code class="language-bash">cat &gt;&gt; .deepsec/.gitignore &lt;&lt; 'EOF'

# Auto-detected metadata (regenerated each run, contains absolute paths)
data/*/tech.json

# Exported findings (regenerated by `deepsec export`)
findings/
exports/
EOF
</code></pre>
<blockquote>
<p>💡 <strong>Por qué ignorar</strong> <code>tech.json</code><strong>:</strong> Contiene el <code>rootPath</code> absoluto de la máquina donde se corrió el scan (ej: <code>/Users/jdoe/proyectos/mi-app</code>), lo cual revela el username y estructura local del desarrollador. Además se regenera en cada run, así que versionarlo solo crea ruido en diffs.</p>
</blockquote>
<h3>Por qué los findings y exports NO van a git</h3>
<p>Los archivos en <code>data/&lt;id&gt;/files/</code> y la carpeta de export contienen:</p>
<ul>
<li><p><strong>Líneas exactas de código vulnerable</strong> con snippets</p>
</li>
<li><p><strong>Razonamiento detallado del agente</strong> sobre cómo explotar cada vuln</p>
</li>
<li><p><strong>Veredictos</strong> de revalidate</p>
</li>
</ul>
<p>Si tu repo es público, eso es básicamente una <strong>guía paso a paso para atacar tu app</strong>. Incluso en repos privados, considera que el historial de git es para siempre — un finding puede estar fixeado hoy pero el commit sigue mostrando cómo era el bug.</p>
<h3>Verifica que no haya residuos en tracking</h3>
<p>Si ya commiteaste algo antes de configurar el gitignore correctamente:</p>
<pre><code class="language-bash">git ls-files .deepsec/ | grep -E "findings/|/files/|/runs/|/reports/"
</code></pre>
<p>Si retorna algo, sácalos del trackeo:</p>
<pre><code class="language-bash">git rm -r --cached .deepsec/findings/ 2&gt;/dev/null
git rm -r --cached .deepsec/data/*/files/ 2&gt;/dev/null
git rm -r --cached .deepsec/data/*/runs/ 2&gt;/dev/null
git rm -r --cached .deepsec/data/*/reports/ 2&gt;/dev/null
git commit -m "chore: untrack deepsec output files"
</code></pre>
<blockquote>
<p>⚠️ <code>git rm --cached</code> solo los saca del trackeo futuro, <strong>no los borra del historial</strong>. Si los findings exponen vulns aún sin arreglar y el repo tiene muchos colaboradores, considera limpiar el historial con <code>git filter-repo</code> o BFG.</p>
</blockquote>
<h3>Dónde viven los findings entonces</h3>
<p>Los findings exportados son <strong>locales y temporales</strong>. Tres patrones comunes para gestionarlos:</p>
<ol>
<li><p><strong>Locales sin persistir</strong> — para escaneos puntuales, cada quien revisa los suyos</p>
</li>
<li><p><strong>Repo privado separado</strong> — <code>mi-proyecto-security/</code> con acceso restringido</p>
</li>
<li><p><strong>Sistema de tickets</strong> (recomendado) — los findings TP relevantes se convierten en issues de Linear/Jira/GitHub con responsable y deadline; los FPs se descartan</p>
</li>
</ol>
<p>Para equipos serios, la opción 3 es la estándar: deepsec genera la lista, humanos triamos, y los hallazgos importantes pasan al flujo normal de trabajo.</p>
<hr />
<h2>Recursos</h2>
<ul>
<li><p>Repo oficial: <a href="https://github.com/vercel-labs/deepsec">https://github.com/vercel-labs/deepsec</a></p>
</li>
<li><p>Docs: <a href="https://github.com/vercel-labs/deepsec/tree/main/docs">https://github.com/vercel-labs/deepsec/tree/main/docs</a></p>
</li>
<li><p>Blog post de lanzamiento: <a href="https://vercel.com/blog/introducing-deepsec-find-and-fix-vulnerabilities-in-your-code-base">https://vercel.com/blog/introducing-deepsec-find-and-fix-vulnerabilities-in-your-code-base</a></p>
</li>
</ul>
<hr />
<h2>Buenas prácticas para equipos</h2>
<p>Recomendaciones generales basadas en la experiencia de implementar deepsec en un proyecto real.</p>
<h3>Designar un responsable</h3>
<p>Aunque cualquier persona del equipo puede correr deepsec siguiendo esta guía (los archivos en <code>.deepsec/</code> están versionados y el setup es reproducible con <code>pnpm install</code>), conviene designar a alguien como responsable del proceso para evitar duplicaciones y mantener consistencia en el triaje.</p>
<h3>Cuándo escanear</h3>
<p>Casos típicos donde tiene sentido correr un escaneo completo:</p>
<ul>
<li><p>Después de mergear una feature grande que toca auth, manejo de inputs, o flujos de pago/datos sensibles</p>
</li>
<li><p>Antes de un release a producción que incluya cambios significativos</p>
</li>
<li><p>Cuando se reporta una vuln en alguna de las dependencias críticas del stack</p>
</li>
<li><p>Periódicamente (cada 3-6 meses) como higiene general</p>
</li>
<li><p>Cuando se decida re-evaluar findings previos después de aplicar fixes</p>
</li>
</ul>
<p>Para cambios pequeños o aislados, basta con escanear el directorio modificado:</p>
<pre><code class="language-bash">pnpm deepsec process --project-id mi-app --filter apps/api/src/routes/&lt;modulo&gt;
</code></pre>
<h3>Gestión de findings</h3>
<p>Recomendamos integrar los findings críticos y altos al sistema de tickets que use el equipo (Linear, Jira, GitHub Issues privados, sistema interno, etc). Después de cada escaneo, el responsable triagea los TPs y crea entradas para los que requieren acción, asignándolos a los devs correspondientes.</p>
<p>Los findings exportados (<code>./findings/</code>) deben mantenerse <strong>solo localmente</strong> en la máquina de quien escaneó — no se commitean al repo (ver sección "Qué commitear y qué no").</p>
<h3>Costos esperados</h3>
<p>Para una codebase mediana (~650 archivos), un pipeline completo (<code>process</code> + <code>revalidate</code>) consume entre <strong>$30-50 de equivalente API</strong> y toma <strong>15-30 minutos</strong> de wall-clock.</p>
<p>Opciones para cubrir el costo:</p>
<ul>
<li><p><strong>Suscripción personal de Claude Max (\(100/mes)</strong> — viable para escaneos ad-hoc del equipo. \)0 reales si cabe en la cuota.</p>
</li>
<li><p><strong>AI Gateway de Vercel con</strong> <code>AI_GATEWAY_API_KEY</code> — recomendado para CI/CD o escaneos frecuentes. Permite cap de gasto y atribución a la cuenta corporativa.</p>
</li>
<li><p><strong>API key directa de Anthropic</strong> — si el equipo ya paga API directa para otros propósitos.</p>
</li>
</ul>
<h3>Política de escalación</h3>
<p>Para findings de severidad <strong>CRITICAL</strong> o <strong>HIGH</strong>, conviene tener un canal directo de escalación al responsable de seguridad antes de cualquier release. Los <strong>MEDIUM</strong> y <strong>BUG</strong> pueden seguir el flujo normal del sistema de tickets.</p>
<hr />
<h2>Notas finales</h2>
<ul>
<li><p><strong>Si encuentras secretos en findings</strong>, rótalos inmediatamente. La presencia de un secreto en un finding implica que estaba en el código fuente.</p>
</li>
<li><p><strong>El reporte de costos en USD</strong> que muestra deepsec es siempre el <strong>equivalente API</strong>, no lo cargado a tu cuenta. Si usas suscripción, los <code>$</code> son referenciales.</p>
</li>
<li><p><code>--reinvestigate</code> <strong>borra trabajo previo.</strong> Solo úsalo cuando realmente quieras re-empezar.</p>
</li>
</ul>
<hr />
<p><em>Esta guía cubre deepsec ≥ 2.0.2 — última actualización: mayo 2026.</em></p>
]]></content:encoded></item><item><title><![CDATA[Setting Up a Monitoring Stack in Docker Compose]]></title><description><![CDATA[This guide provides instructions on setting up a comprehensive monitoring stack using Grafana, Prometheus, Node Exporter, cAdvisor and Loki. These components are orchestrated with Docker Compose and exposed via an NGINX reverse proxy, making them acc...]]></description><link>https://blog.diegocornejo.com/setting-up-a-monitoring-stack-in-docker-compose</link><guid isPermaLink="true">https://blog.diegocornejo.com/setting-up-a-monitoring-stack-in-docker-compose</guid><category><![CDATA[Grafana]]></category><category><![CDATA[Docker]]></category><category><![CDATA[nginx]]></category><category><![CDATA[loki]]></category><category><![CDATA[Docker compose]]></category><category><![CDATA[#prometheus]]></category><dc:creator><![CDATA[Diego Cornejo]]></dc:creator><pubDate>Tue, 23 Jul 2024 06:16:43 GMT</pubDate><content:encoded><![CDATA[<p>This guide provides instructions on setting up a comprehensive monitoring stack using Grafana, Prometheus, Node Exporter, cAdvisor and Loki. These components are orchestrated with Docker Compose and exposed via an NGINX reverse proxy, making them accessible through a single domain.</p>
<h2 id="heading-components">Components</h2>
<ul>
<li><p><strong>Grafana</strong>: The analytics and monitoring solution with support for multiple data sources, including Prometheus.</p>
</li>
<li><p><strong>Prometheus</strong>: The monitoring and alerting toolkit, collecting metrics from configured targets at specified intervals.</p>
</li>
<li><p><strong>Node Exporter</strong>: A Prometheus exporter for hardware and OS metrics exposed by *NIX kernels.</p>
</li>
<li><p><strong>cAdvisor</strong>: Analyzes resource usage and performance characteristics of running containers.</p>
</li>
<li><p><strong>Loki</strong>: A horizontally-scalable, highly-available, multi-tenant log aggregation system.</p>
</li>
<li><p><strong>NGINX</strong>: Used as a reverse proxy to expose Grafana on the internet securely.</p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>Docker and Docker Compose installed on your host machine.</p>
</li>
<li><p>Domain name configured to point to the host machine (for NGINX configuration).</p>
</li>
</ul>
<h2 id="heading-configuration-files-overview">Configuration Files Overview</h2>
<ul>
<li><p><code>docker-compose.yml</code>: Defines the services, networks, and volumes for the Docker containers.</p>
</li>
<li><p><code>prometheus.yml</code>: Configuration file for Prometheus to define scrape targets and intervals.</p>
</li>
<li><p><code>grafana.ini</code>: Configuration file for Grafana's settings, including SMTP for email notifications.</p>
</li>
<li><p><code>loki-config.yml</code>: Configuration file for Loki to define the storage backend and other settings.</p>
</li>
<li><p><code>domain.conf</code>: NGINX configuration for proxying requests to Grafana and handling WebSocket connections.</p>
</li>
</ul>
<h2 id="heading-installation-steps">Installation Steps</h2>
<ol>
<li><p><strong>Configure Docker Daemon</strong>: Enable Docker metrics by adding the following to <code>/etc/docker/daemon.json</code> and restarting Docker:</p>
<pre><code class="lang-bash"> {
   <span class="hljs-string">"metrics-addr"</span>: <span class="hljs-string">"127.0.0.1:9323"</span>
 }
 <span class="hljs-comment"># Then reload Docker configurations:</span>
 sudo systemctl reload docker
</code></pre>
</li>
<li><p><strong>Install Loki plugin for Docker</strong>: Install the Loki plugin for Docker to enable log collection:</p>
<pre><code class="lang-bash"> docker plugin install grafana/loki-docker-driver:latest --<span class="hljs-built_in">alias</span> loki --grant-all-permissions
</code></pre>
</li>
<li><p><strong>Clone the Repository</strong>: Clone the repository to your host machine:</p>
<pre><code class="lang-bash"> git <span class="hljs-built_in">clone</span> https://gist.github.com/398e94fb0aebb994b25223d99e7ae769.git monitoring-stack
 <span class="hljs-built_in">cd</span> monitoring-stack
</code></pre>
</li>
<li><p><strong>Start the Services</strong>: Start the monitoring stack using Docker Compose:</p>
<pre><code class="lang-bash"> docker-compose up -d
</code></pre>
</li>
<li><p><strong>Access Grafana</strong>: Access Grafana at <code>http://&lt;your-domain&gt;</code> and log in with the default credentials (username: <code>admin</code>, password: <code>admin</code>).</p>
</li>
</ol>
<blockquote>
<p><strong>Important Notes:</strong></p>
<ul>
<li><p>Replace placeholder values in grafana.ini with your SMTP credentials to enable email notifications.</p>
</li>
<li><p>Adjust the domain.conf to match your domain and specific requirements.</p>
</li>
<li><p>Review Docker and NGINX logs in case of any issues during setup.</p>
</li>
</ul>
</blockquote>
<ol start="6">
<li><p>How to send container logs to loki:</p>
<pre><code class="lang-dockerfile"> docker <span class="hljs-keyword">run</span><span class="bash"> --name nginx-loki -d \
 -p 8080:80 \
 --log-driver=loki \
 --log-opt loki-url=http://localhost:3100/loki/api/v1/push \
 nginx:latest</span>
</code></pre>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Deploy Jupyter Lite on Vercel / Netlify]]></title><description><![CDATA[Introduction:
Jupyter Notebook is an incredibly versatile tool for working with code and data interactively and collaboratively. In this article, I'll guide you through an exciting journey that begins with an introduction to Jupyter and its Notebooks...]]></description><link>https://blog.diegocornejo.com/deploy-your-own-jupyter-lite-on-vercel</link><guid isPermaLink="true">https://blog.diegocornejo.com/deploy-your-own-jupyter-lite-on-vercel</guid><dc:creator><![CDATA[Diego Cornejo]]></dc:creator><pubDate>Tue, 07 Nov 2023 04:20:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1729052689671/a09ad832-4459-400a-9928-fe55c376dedb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction"><strong>Introduction:</strong></h2>
<p>Jupyter Notebook is an incredibly versatile tool for working with code and data interactively and collaboratively. In this article, I'll guide you through an exciting journey that begins with an introduction to Jupyter and its Notebooks, moves on to using a GitHub template or forking, and finally, reaches the setup and deployment on Vercel.</p>
<h2 id="heading-what-jupyter-and-notebooks-are"><strong>What Jupyter and Notebooks Are</strong></h2>
<p>Jupyter Notebook is an open-source tool that allows you to create and share interactive documents that combine code, visualizations, and explanatory text all in one place. It's widely used in fields like data science, programming, scientific research, and education.</p>
<blockquote>
<p>Note: The Jupyter Lite team offers excellent documentation for deploying Notebooks on GitHub Pages <a target="_blank" href="https://jupyterlite.readthedocs.io/en/latest/quickstart/deploy.html">https://jupyterlite.readthedocs.io/en/latest/quickstart/deploy.html</a>. However, in this post, we'll show you how to achieve the same process on Vercel.</p>
</blockquote>
<h2 id="heading-using-the-github-template-or-forking"><strong>Using the GitHub Template or Forking</strong></h2>
<p>You can kickstart your Jupyter Lite Notebook project by using this Github template <a target="_blank" href="https://github.com/new?template_name=vercel-jupyter-lite&amp;template_owner=diegofcornejo">Vercel / Netlify Jupyter Lite Template</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699327152714/485d91bf-6a3d-420b-9b6c-85e091e83e5b.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699327219371/ac6dd6be-00ef-45f5-9228-5004521c1ad9.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-setting-up-on-vercel"><strong>Setting Up on Vercel</strong></h2>
<ol>
<li><p>Sign up for Vercel (if you haven't already).</p>
</li>
<li><p>Create a new project on Vercel and follow the instructions to connect your GitHub repository with Vercel.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699327326967/e7de8de7-d602-4b76-a0dd-b9ba3668fbc5.png" alt class="image--center mx-auto" /></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699327390773/014a64ed-9df7-4b81-8fd1-a0e37d93d6e9.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Configure deployment options and follow the steps to guide you through the deployment process.</p>
<ul>
<li><p>Set a name for your project</p>
</li>
<li><p>Open "Build and Outputs Settings"</p>
</li>
<li><p>Enable "Build Command" and add this text: <code>bash ./deploy.sh</code></p>
</li>
<li><p>Enable "Output Directory" and add this text: <code>dist</code></p>
</li>
<li><p>Click on "Deploy"</p>
</li>
</ul>
</li>
</ol>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699327478429/2dfe6e70-ec83-41d9-b648-a4edfdb87ff1.png" alt class="image--center mx-auto" /></p>
<ol start="4">
<li>Once deployment is complete, your Jupyter Notebook project will be online and accessible.</li>
</ol>
<h2 id="heading-setting-up-on-netlify"><strong>Setting Up on Netlify</strong></h2>
<ol>
<li><p>Sign up for Netlify (if you haven't already).</p>
</li>
<li><p>Create a new project on Netlify and follow the instructions to connect your GitHub repository with Netlify.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729050630017/63e7dc00-261f-445f-85ea-d00035e44c2e.png" alt class="image--center mx-auto" /></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729050565939/d702899d-1350-40f3-9c9a-e6ff9162a2ed.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Configure deployment options and follow the steps to guide you through the deployment process.</p>
<ul>
<li><p>Set a name for your project</p>
</li>
<li><p>Set "Build command" with this text: <code>bash ./deploy.sh</code></p>
</li>
<li><p>Set "Publish directory" with this text: <code>dist</code></p>
</li>
<li><p>Click on "Deploy {projectname}"</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729051563582/d32d826c-8621-4111-ac3f-dddfa2f3f0fd.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
<li><p>Once deployment is complete, your Jupyter Notebook project will be online and accessible.</p>
</li>
</ol>
<h2 id="heading-demo"><strong>Demo</strong></h2>
<p>Before starting your own project, we invite you to explore a demo of what you'll accomplish with <strong>Jupyter Lite Notebook on Vercel / Netlify</strong>.</p>
<p>You can see the demo in action at:</p>
<p><a target="_blank" href="https://jupyter.vercel.app">https://jupyter.vercel.app</a>.</p>
<p><a target="_blank" href="https://jupyterlite.netlify.app/">https://jupyterlite.netlify.app/</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699330614098/476d30cd-ca5a-4bab-a3cd-d82ff030c1f1.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699330631107/c92cf67c-96ca-4647-8e80-fe36c4b08743.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Dockerize a Rect App (vite) inside a Turborepo]]></title><description><![CDATA[Ultra-Light HTTP Server with Busybox HTTPD Applet
Description: This Docker image is designed to provide an ultra-lightweight HTTP server optimized for efficiently serving static files. It is based on the Busybox system and utilizes the Applet HTTPD f...]]></description><link>https://blog.diegocornejo.com/dockerize-a-rect-app-vite-inside-a-turborepo</link><guid isPermaLink="true">https://blog.diegocornejo.com/dockerize-a-rect-app-vite-inside-a-turborepo</guid><category><![CDATA[Docker]]></category><category><![CDATA[turborepo]]></category><category><![CDATA[React]]></category><category><![CDATA[vite]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Diego Cornejo]]></dc:creator><pubDate>Tue, 24 Oct 2023 13:38:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1698166613444/53130a14-0d4f-4f07-beab-3ae9a34176ef.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-ultra-light-http-server-with-busybox-httpd-applet">Ultra-Light HTTP Server with Busybox HTTPD Applet</h2>
<p><strong>Description:</strong> This Docker image is designed to provide an ultra-lightweight HTTP server optimized for efficiently serving static files. It is based on the Busybox system and utilizes the Applet HTTPD for web server functionality.</p>
<p><strong>General Image Details:</strong></p>
<ul>
<li><p><strong>Base Image:</strong> diegofcornejo/httpd-lite:latest</p>
</li>
<li><p><strong>Purpose:</strong> Efficiently serve static files.</p>
</li>
<li><p><strong>Size:</strong> The image size has been optimized to be as lightweight as possible, making it ideal for quick and efficient deployments of static content in containerized environments. (158kb)</p>
</li>
</ul>
<h3 id="heading-example-usage">Example Usage</h3>
<h4 id="heading-serving-static-files-from-your-host-machine">Serving Static Files from your Host Machine</h4>
<pre><code class="lang-bash"><span class="hljs-comment"># Serve static files from the current directory</span>
<span class="hljs-comment"># Create a configuration file if you don't have one</span>
touch httpd.conf 
<span class="hljs-comment"># Run the container with the following command:</span>
docker run -p 80:8080 --init --rm -ti -v <span class="hljs-string">"<span class="hljs-subst">$(PWD)</span>:/home/static"</span> diegofcornejo/httpd-lite:latest
</code></pre>
<h4 id="heading-using-in-a-dockerfile">Using in a Dockerfile</h4>
<p>Here's an example of how to use this image. The <code>CMD</code> instruction is optional but can be used to replace the parameters as needed:</p>
<pre><code class="lang-Dockerfile"><span class="hljs-keyword">FROM</span> diegofcornejo/httpd-lite:latest as production

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">8080</span>

<span class="hljs-comment"># Copy your static files</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"/busybox"</span>, <span class="hljs-string">"httpd"</span>, <span class="hljs-string">"-f"</span>, <span class="hljs-string">"-v"</span>, <span class="hljs-string">"-p"</span>, <span class="hljs-string">"8080"</span>, <span class="hljs-string">"-c"</span>, <span class="hljs-string">"httpd.conf"</span>]</span>
</code></pre>
<h4 id="heading-vite-react-app-built-inside-a-turborepo-full-example">(Vite) React App built inside a turborepo full example</h4>
<p>Assuming you have a project structure like this:</p>
<pre><code class="lang-plaintext">├── turbo.json
├── package.json
├── yarn.lock
├── README.md
└── apps
    └── myapp
        ├── src
        ├── index.html
        └── Dockerfile
</code></pre>
<pre><code class="lang-Dockerfile"><span class="hljs-comment"># Base image</span>
<span class="hljs-keyword">FROM</span> alpine:<span class="hljs-number">3.18</span>.<span class="hljs-number">4</span> as build

<span class="hljs-keyword">RUN</span><span class="bash"> apk update</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apk add --no-cache nodejs npm</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install -g yarn</span>

<span class="hljs-comment"># Create app directory</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-comment"># Copy the whole project</span>
<span class="hljs-keyword">COPY</span><span class="bash"> ../../ .</span>

<span class="hljs-comment"># Install dependencies</span>
<span class="hljs-keyword">RUN</span><span class="bash"> yarn install</span>

<span class="hljs-comment"># Build the app</span>
<span class="hljs-keyword">RUN</span><span class="bash"> yarn workspace myapp build</span>

<span class="hljs-comment"># Production stage</span>
<span class="hljs-keyword">FROM</span> diegofcornejo/httpd-lite:latest as production

<span class="hljs-comment"># Copy build artifacts from the build-vite stage</span>
<span class="hljs-keyword">COPY</span><span class="bash"> --from=build /app/apps/myapp/dist .</span>

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">8080</span>
</code></pre>
<h4 id="heading-build-image-and-run-container">Build image and run container</h4>
<pre><code class="lang-bash"><span class="hljs-comment"># To build the docker image, run the following command:</span>
docker build -f apps/myapp/Dockerfile -t myreactapp:latest .

<span class="hljs-comment"># To run the docker image, run the following command:</span>
docker run -p 80:8080 --init --rm -ti myreactapp:latest

<span class="hljs-comment"># Or in detach mode (background):</span>
docker run -p 80:8080 --init --rm -d myreactapp:latest

<span class="hljs-comment"># Now Open your browser and go to http://localhost:80</span>

<span class="hljs-comment"># To stop the docker image, run the following command:</span>
docker stop &lt;container_id&gt;
</code></pre>
<p>Note: When running the container, sending a TERM signal to your TTY won't get propagated due to how Busybox is built. Instead, you can call <code>docker stop</code> (or <code>docker kill</code> if you can't wait 15 seconds). Alternatively, you can run the container with <code>docker run -it --rm --init</code>, which will propagate signals to the process correctly.</p>
]]></content:encoded></item><item><title><![CDATA[Export AWS SSM Parameter Store as Environment Variables with Bash]]></title><description><![CDATA[1. Add parameters to AWS System Manager
aws ssm put-parameter --name "/my-app/my-param-name" --value "my-param-value" --type "String"

Replace "/my-app/my-param-name" with the desired path and name for the parameter. Note that the path must begin wit...]]></description><link>https://blog.diegocornejo.com/export-aws-ssm-parameter-store-as-environment-variables-with-bash</link><guid isPermaLink="true">https://blog.diegocornejo.com/export-aws-ssm-parameter-store-as-environment-variables-with-bash</guid><dc:creator><![CDATA[Diego Cornejo]]></dc:creator><pubDate>Fri, 28 Apr 2023 05:34:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1682659960431/0615fb6e-ccbb-44d9-a4f3-828b7ea69dca.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-1-add-parameters-to-aws-system-manager">1. Add parameters to AWS System Manager</h3>
<pre><code class="lang-sh">aws ssm put-parameter --name <span class="hljs-string">"/my-app/my-param-name"</span> --value <span class="hljs-string">"my-param-value"</span> --<span class="hljs-built_in">type</span> <span class="hljs-string">"String"</span>
</code></pre>
<p>Replace <code>"/my-app/my-param-name"</code> with the desired path and name for the parameter. Note that the path must begin with a forward slash (<code>/</code>) and can include multiple levels separated by forward slashes. Also, if the path does not exist, the <code>put-parameter</code> command will create it automatically.</p>
<p>Make sure you have permission to create parameters in the SSM parameter store, and specify any necessary additional parameters like <code>--region</code> or <code>--profile</code>.</p>
<h3 id="heading-2-create-the-bash-script">2.  Create the bash script</h3>
<p>The script sets some parameters like <code>APP</code>, <code>SERVICE</code>, <code>ENVIRONMENT</code>, and <code>REGION</code>. These variables are used to construct the SSM path to retrieve the parameters.</p>
<p>The script uses the <code>aws ssm get-parameters-by-path</code> command to retrieve all SSM parameter names under the given path. Then, it loops through each parameter and exports it as an environment variable. The script also appends the export statements to the <code>~/.bashrc</code> file so that they persist on future logins.</p>
<p>Finally, the script reloads the <code>~/.bashrc</code> file with the <code>source</code> command.</p>
<h4 id="heading-create-a-file-for-the-script">Create a file for the script</h4>
<pre><code class="lang-sh">nano export-ssm-params.sh
<span class="hljs-comment">#or</span>
vim export-ssm-params.sh
</code></pre>
<p>Copy/paste the following code.</p>
<p>Here's the script:</p>
<pre><code class="lang-sh"><span class="hljs-meta">#!/bin/bash</span>

<span class="hljs-comment"># This script will export all SSM parameters under a given path as environment variables</span>
<span class="hljs-comment"># It will also append the export statements to the ~/.bashrc file so that they are available on future logins</span>

<span class="hljs-comment"># Set some parameters</span>
APP=<span class="hljs-string">"myapp"</span>
SERVICE=<span class="hljs-string">"api"</span>
ENVIRONMENT=<span class="hljs-string">"production"</span>
REGION=<span class="hljs-string">"us-east-1"</span>

<span class="hljs-comment"># Set the SSM path</span>
SSM_PATH=<span class="hljs-string">"/<span class="hljs-variable">$APP</span>/<span class="hljs-variable">$SERVICE</span>/<span class="hljs-variable">$ENVIRONMENT</span>"</span>

<span class="hljs-comment"># Get all SSM parameter names under the given path</span>
SSM_PARAMETER_NAMES=$(aws ssm get-parameters-by-path \
  --region <span class="hljs-variable">$REGION</span> \
  --path <span class="hljs-string">"<span class="hljs-variable">$SSM_PATH</span>"</span> \
  --recursive \
  --with-decryption \
  --query <span class="hljs-string">'Parameters[].Name'</span> \
  --output text)

<span class="hljs-comment"># Loop through each parameter and export it as an environment variable</span>
<span class="hljs-keyword">for</span> name <span class="hljs-keyword">in</span> <span class="hljs-variable">$SSM_PARAMETER_NAMES</span>; <span class="hljs-keyword">do</span>
  value=$(aws ssm get-parameter \
    --region <span class="hljs-variable">$REGION</span> \
    --name <span class="hljs-variable">$name</span> \
    --with-decryption \
    --query <span class="hljs-string">'Parameter.Value'</span> \
    --output text)
  <span class="hljs-keyword">if</span> [ ! -z <span class="hljs-string">"<span class="hljs-variable">$name</span>"</span> ] &amp;&amp; [ ! -z <span class="hljs-string">"<span class="hljs-variable">$value</span>"</span> ]; <span class="hljs-keyword">then</span>
    name=$(<span class="hljs-built_in">echo</span> <span class="hljs-variable">$name</span> | awk -F/ <span class="hljs-string">'{print toupper($NF)}'</span>)
    <span class="hljs-built_in">export</span> <span class="hljs-string">"<span class="hljs-variable">$name</span>"</span>=<span class="hljs-string">"<span class="hljs-variable">$value</span>"</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Exported variable: <span class="hljs-variable">$name</span>=<span class="hljs-variable">$value</span>"</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"export <span class="hljs-variable">$name</span>=<span class="hljs-variable">$value</span>"</span> &gt;&gt; ~/.bashrc
  <span class="hljs-keyword">fi</span>
<span class="hljs-keyword">done</span>

<span class="hljs-comment"># Reload the bashrc file</span>
<span class="hljs-built_in">source</span> ~/.bashrc
</code></pre>
<h3 id="heading-3-how-to-run-the-script">3.  How to Run the Script</h3>
<p>To use this script, you need to have the AWS client (<code>awscli</code>) installed and configured to authenticate to an AWS account. Then, simply execute the script with Bash in a terminal.</p>
<p>You can save the script in a file with <code>.sh</code> extension, for example <code>export-ssm-params.sh</code>. </p>
<pre><code class="lang-sh">nano export-ssm-params.sh
<span class="hljs-comment">#or</span>
vim export-ssm-params.sh
</code></pre>
<p>Don't forget add execution permission</p>
<pre><code class="lang-sh">chmod +x export-ssm-params.sh
</code></pre>
<p>Now you can run the script with the following command:</p>
<pre><code class="lang-sh">bash export-ssm-params.sh
<span class="hljs-comment">#or</span>
./export-ssm-params.sh
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In summary, this bash script is useful to export AWS Systems Manager parameters as environment variables. This can be helpful in different situations, for example, to avoid the need to repeatedly call the AWS API to get the parameter values.</p>
<p>Additionally, the script also adds the export statements to the <code>~/.bashrc</code> file, meaning that the environment variables will be available in future Bash terminal sessions.</p>
]]></content:encoded></item><item><title><![CDATA[Publish Express API to EKS Fargate]]></title><description><![CDATA[Important: This tutorial assume you already has installed and know how to use, aws cli, kubectl and eksctl
Create and Setup Cluster and required policies
1. Create Fargate Cluster
eksctl create cluster --region us-west-1 --name express-api --version ...]]></description><link>https://blog.diegocornejo.com/publish-express-api-to-eks-fargate</link><guid isPermaLink="true">https://blog.diegocornejo.com/publish-express-api-to-eks-fargate</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[EKS]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Express]]></category><category><![CDATA[aws-fargate]]></category><dc:creator><![CDATA[Diego Cornejo]]></dc:creator><pubDate>Sun, 26 Mar 2023 17:19:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1679850947261/a58e9f8d-94a1-49a8-80ca-23e2fc0b25a2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Important: This tutorial assume you already has installed and know how to use, aws cli, kubectl and eksctl</p>
<h3 id="heading-create-and-setup-cluster-and-required-policies">Create and Setup Cluster and required policies</h3>
<h4 id="heading-1-create-fargate-cluster">1. Create Fargate Cluster</h4>
<pre><code>eksctl create cluster --region us-west<span class="hljs-number">-1</span> --name express-api --version <span class="hljs-number">1.25</span> --fargate
</code></pre><p>Note: This command create a stack in cloudformation</p>
<h4 id="heading-2-enable-cluster-to-use-iam">2.  Enable cluster to use IAM</h4>
<pre><code>eksctl utils associate-iam-oidc-provider --region us-west<span class="hljs-number">-1</span> --cluster express-api --approve
</code></pre><h4 id="heading-3-download-the-iam-policy-to-allow-aws-load-balancer-controller-to-make-requests-to-aws-apis">3. Download the IAM policy to allow AWS Load Balancer Controller to make requests to AWS API's</h4>
<pre><code>curl -o iam_policy.json https:<span class="hljs-comment">//raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.4/docs/install/iam_policy.json</span>
</code></pre><h4 id="heading-4-create-an-iam-policy-with-the-file-downloaded-in-step-3">4. Create an IAM policy with the file downloaded in step 3</h4>
<pre><code>aws iam create-policy \
   --policy-name AWSLoadBalancerControllerIAMPolicy \
   --policy-<span class="hljs-built_in">document</span> file:<span class="hljs-comment">//iam_policy.json</span>
</code></pre><h4 id="heading-5-to-create-a-service-account-named-aws-load-balancer-controller-in-the-kube-system-namespace-for-the-aws-load-balancer-controller-run-the-following-command">5. To create a service account named aws-load-balancer-controller in the kube-system namespace for the AWS Load Balancer Controller, run the following command:</h4>
<pre><code>eksctl create iamserviceaccount \
  --cluster=express-api \
  --namespace=kube-system \
  --name=aws-load-balancer-controller \
  --attach-policy-arn=arn:aws:iam::{org-id}:policy/AWSLoadBalancerControllerIAMPolicy \
  --override-existing-serviceaccounts \
  --approve \
  --region us-west<span class="hljs-number">-1</span>
</code></pre><p>Note: This command create a stack in cloudformation</p>
<h4 id="heading-6-to-verify-that-the-new-service-role-is-created-run-one-of-the-following-commands">6. To verify that the new service role is created, run one of the following commands:</h4>
<pre><code>eksctl get iamserviceaccount --region us-west<span class="hljs-number">-1</span> --cluster express-api --name aws-load-balancer-controller --namespace kube-system
#or
kubectl get serviceaccount aws-load-balancer-controller --namespace kube-system
</code></pre><h3 id="heading-install-the-aws-load-balancer-controller-using-helm">Install the AWS Load Balancer Controller using Helm</h3>
<h4 id="heading-1-add-the-amazon-eks-chart-repo-to-helm">1.  Add the Amazon EKS chart repo to Helm</h4>
<pre><code>helm repo add eks https:<span class="hljs-comment">//aws.github.io/eks-charts</span>
</code></pre><h4 id="heading-2-install-the-targetgroupbinding-custom-resource-definitions-crds">2. Install the TargetGroupBinding custom resource definitions (CRDs)</h4>
<pre><code>kubectl apply -k <span class="hljs-string">"github.com/aws/eks-charts/stable/aws-load-balancer-controller//crds?ref=master"</span>
</code></pre><h4 id="heading-3-install-helm-chart">3. Install helm chart</h4>
<pre><code>helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
    --set clusterName=express-api \
    --set serviceAccount.create=<span class="hljs-literal">false</span> \
    --set region=us-west<span class="hljs-number">-1</span> \
    --set vpcId={vpc-id} \
    --set serviceAccount.name=aws-load-balancer-controller \
    -n kube-system
</code></pre><h3 id="heading-setup-aws-load-balancer-controller">Setup AWS Load Balancer Controller</h3>
<h4 id="heading-1-create-a-fargate-profile">1. Create a Fargate profile</h4>
<pre><code>eksctl create fargateprofile --cluster express-api --region us-west<span class="hljs-number">-1</span> --name express-api-profile --namespace express-api-namespace
</code></pre><h4 id="heading-2-deploy-yml-file">2. Deploy YML file</h4>
<p>Download: https://gist.github.com/diegofcornejo/5b271c7ec69b2e813804bdcbe2bd0684#file-express-api-alb-yml</p>
<pre><code>kubectl apply -f express-api-alb.yml
</code></pre><p>Note: If you want https on your load balancer, you need to create it before in the same region when you are deployed your cluster (us-west-1 for this example), then pass the certificate arn in the file.yml</p>
<h4 id="heading-3-verify-that-the-ingress-resource-was-created">3. Verify that the Ingress resource was created</h4>
<pre><code>kubectl get ingress/express-api-ingress -n express-api-namespace
#or
kubectl get ingresses.networking.k8s.io express-api-ingress -n express-api-namespace
</code></pre><p>Output:</p>
<pre><code>NAME                  CLASS   HOSTS   ADDRESS                                                                 PORTS   AGE
express-api-ingress   alb     *       k8s-expressa-expressa-xxxxxxxxxx-xxxxxxxx.us-west<span class="hljs-number">-2.</span>elb.amazonaws.com   <span class="hljs-number">80</span>      <span class="hljs-number">12</span>m
</code></pre><p>Note: If your Ingress isn't created after several minutes, view the AWS Load Balancer Controller logs by running the following command:</p>
<pre><code>kubectl logs -n kube-system deployment.apps/aws-load-balancer-controller
</code></pre><h4 id="heading-4-open-the-browser-and-paste-the-load-balancer-url-or-your-custom-domain">4. Open the browser and paste the load balancer url or your custom domain</h4>
<p>Note: Remember create an A record in route 53 or your domain admin point to load balancer</p>
<h4 id="heading-5-scale-your-deployment">5. Scale your deployment</h4>
<pre><code>kubectl scale deployments express-api-deployment --replicas=<span class="hljs-number">3</span> -n express-api-namespace
</code></pre><h5 id="heading-references">References</h5>
<p>https://repost.aws/knowledge-center/eks-alb-ingress-controller-fargate</p>
<p>https://aws.amazon.com/blogs/containers/how-to-expose-multiple-applications-on-amazon-eks-using-a-single-application-load-balancer/</p>
<h4 id="heading-delete-cluster">Delete Cluster</h4>
<pre><code>eksctl <span class="hljs-keyword">delete</span> cluster --region us-west<span class="hljs-number">-1</span> --name express-api
</code></pre>]]></content:encoded></item><item><title><![CDATA[Crear blog con Hexo, Github Pages, Cloudflare SSL]]></title><description><![CDATA[UPDATE 2021
Todos estos pasos son fácilmente reemplazables al importar tu repositorio hexo en vercel, pero si por alguna razón no quieres usar vercel, sigue leyendo.
Para esto necesitamos:

Una cuenta en godaddy.com con un dominio
Una cuenta en cloud...]]></description><link>https://blog.diegocornejo.com/crear-blog-con-hexo-github-pages-cloudflare-ssl</link><guid isPermaLink="true">https://blog.diegocornejo.com/crear-blog-con-hexo-github-pages-cloudflare-ssl</guid><category><![CDATA[blog]]></category><category><![CDATA[cloudflare]]></category><category><![CDATA[SSL]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[JAMstack]]></category><dc:creator><![CDATA[Diego Cornejo]]></dc:creator><pubDate>Fri, 20 Aug 2021 03:04:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1629428297247/YTJ_YsO-z.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="update-2021">UPDATE 2021</h2>
<p>Todos estos pasos son fácilmente reemplazables al importar tu repositorio hexo en <a target="_blank" href="https://vercel.com">vercel</a>, pero si por alguna razón no quieres usar vercel, sigue leyendo.</p>
<h2 id="para-esto-necesitamos"><strong>Para esto necesitamos:</strong></h2>
<ul>
<li>Una cuenta en godaddy.com con un dominio</li>
<li>Una cuenta en cloudflare.com</li>
<li>Una cuenta en github.com</li>
<li>Git Bash (https://git-scm.com/downloads) u otra bash en la que puedas ejecutar comandos git</li>
<li>Node JS (https://nodejs.org)</li>
</ul>
<h2 id="proceso"><strong>Proceso</strong></h2>
<h3 id="1-entrar-a-cloudflarecom-y-agregar-un-sitio">1. Entrar a cloudflare.com y agregar un sitio</h3>
<p><img src="https://randomtechguy.vercel.app/images/20190105/1-1.jpg" alt />
<img src="https://randomtechguy.vercel.app/images/20190105/1-2.PNG" alt />
<img src="https://randomtechguy.vercel.app/images/20190105/1-3.PNG" alt />
<img src="https://randomtechguy.vercel.app/images/20190105/1-4.PNG" alt />
<img src="https://randomtechguy.vercel.app/images/20190105/1-5.PNG" alt />
<img src="https://randomtechguy.vercel.app/images/20190105/1-6.jpg" alt />
<img src="https://randomtechguy.vercel.app/images/20190105/1-7.jpg" alt /></p>
<h3 id="2-entrar-a-godaddycom-y-configurar-nameservers">2. Entrar a godaddy.com y configurar nameservers</h3>
<h4 id="configurar-nameservers">Configurar Nameservers</h4>
<p>Click en "DNS"
<img src="https://randomtechguy.vercel.app/images/20190105/2-1.jpg" alt />
En la sección "Nameservers" click en "Change"
<img src="https://randomtechguy.vercel.app/images/20190105/2-2.jpg" alt /></p>
<p>Seleccionar custom y agregar nameservers obtenidos en cloudflare.com</p>
<p>Eliminar cualquier otro nameserver que exista.</p>
<p>Cloudflare nameservers</p>
<ul>
<li>joan.ns.cloudflare.com</li>
<li>nash.ns.cloudflare.com
<img src="https://randomtechguy.vercel.app/images/20190105/2-3.PNG" alt /></li>
</ul>
<h4 id="validar-nameservers">Validar Nameservers</h4>
<p>Según Cloudflare y Godaddy este proceso de cambio de nameservers puede tomar hasta 24 horas,  pero normalmente toma unos pocos minutos, para validar si el cambio se realizó podemos hacerlo de dos formas:</p>
<h4 id="desde-la-cli-o-linea-de-comandos">Desde la cli o línea de comandos</h4>
<p>Abrir la terminal o cmd y pegar el siguiente comando</p>
<pre><code class="lang-sh">$ nslookup -<span class="hljs-built_in">type</span>=ns randomtechguy.com
</code></pre>
<p><img src="https://randomtechguy.vercel.app/images/20190105/2-4.PNG" alt /></p>
<h4 id="desde-la-web">Desde la web</h4>
<p>Ingresar a https://dnschecker.org/
<img src="https://randomtechguy.vercel.app/images/20190105/2-5.PNG" alt /></p>
<h3 id="3-crear-repositorio-en-github">3. Crear repositorio en Github</h3>
<p>Ingresar a github.com y crear repositorio
<img src="https://randomtechguy.vercel.app/images/20190105/3-1.PNG" alt />
Agregar nombre al nuevo repositorio y hacer click en "Create repository"
<img src="https://randomtechguy.vercel.app/images/20190105/3-2.PNG" alt />
<img src="https://randomtechguy.vercel.app/images/20190105/3-3.PNG" alt /></p>
<h3 id="4-verificar-disponibilidad-de-certificado-de-seguridad-ssl">4. Verificar disponibilidad de certificado de seguridad (SSL)</h3>
<p>Para este punto nuestro dominio ya está siendo administrado por cloudflare por lo que contamos con un certificado SSL.
Ingresar a cloudflare.com
Click en nuestrio sitio creado en el paso 1
Click en Crypto y verificamos el estado del certificado, que debería estar activo como muestra la imagen abajo:
<img src="https://randomtechguy.vercel.app/images/20190105/4-1.PNG" alt />
Ir a sección "Always Use HTTPS" y marca la casilla en "On"
<img src="https://randomtechguy.vercel.app/images/20190105/4-2.PNG" alt /></p>
<h2 id="desde-aqui-usaremos-la-consola-o-terminal-o-como-la-llames-d">Desde aquí usaremos la consola o terminal (o como la llames) :D</h2>
<h3 id="5-instalar-hexo-httpshexoio">5. Instalar hexo (https://hexo.io/)</h3>
<p>Abrir la terminal en la que tienes acceso a los comandos git y npm</p>
<pre><code class="lang-sh"><span class="hljs-comment"># Instalar hexo globalmente</span>
$ npm install hexo-cli -g
<span class="hljs-comment"># Iniciar un proyecto hexo</span>
$ hexo init myawesomeblog
<span class="hljs-comment"># myawesomeblog es el nombre de la carpeta que se creará, pueden usar cualquier nombre pero para poder guiarnos mejor vamos a usar el mismo nombre del repositorio que creamos en el paso 3.</span>
<span class="hljs-comment"># Entrar a la carpeta del proyecto e instalar dependencias</span>
$ <span class="hljs-built_in">cd</span> myawesomeblog
$ npm install
<span class="hljs-comment"># Probar nuestro blog creado con hexo</span>
hexo server
<span class="hljs-comment"># Este comando debería retornar lo siguiente</span>
INFO  Start processing
INFO  Hexo is running at http://localhost:4000 . Press Ctrl+C to stop.
</code></pre>
<p>Abrir nuestro navegador en la dirección http://localhost:4000 para ver nuestro nuevo blog
No cierres la terminal la vamos a usar mas adelante.</p>
<p>Los siguientes pasos son basados en el excelente artículo de Jeff Ferrari
https://www.poweredbyjeff.com/2018/05/14/Deploying-Hexo-website-to-Github-Pages/</p>
<h3 id="6-configurar-hexo-para-subir-a-github">6. Configurar hexo para subir a Github</h3>
<h4 id="editar-archivo-configyml">Editar archivo _config.yml</h4>
<p>Editar URL
    url: https://randomtechguy.com #nuestro dominio con https
Editar public dir from public to docs
    public_dir: docs
Comentar configuracion de Deploy</p>
<pre><code><span class="hljs-comment">#deploy:</span>
<span class="hljs-comment">#   type:</span>
</code></pre><h3 id="7-generar-archivos-y-subir-a-github">7. Generar archivos y subir a Github</h3>
<p>Abrir la terminal en la que tienes acceso a los comandos git y npm</p>
<h4 id="generar-archivos">Generar archivos</h4>
<pre><code class="lang-sh"><span class="hljs-comment"># Remover Plugins</span>
$ npm uninstall hexo-generator-cname hexo-deployer-git
<span class="hljs-comment"># Crear archivo CNAME en carpeta source, usar nano, vim o el editor de su preferencia</span>
$ nano <span class="hljs-built_in">source</span>/CNAME <span class="hljs-comment">#Sin extensión todo en mayúsculas</span>
<span class="hljs-comment"># Escribir nuestro dominio en el archivo CNAME</span>
$ <span class="hljs-built_in">echo</span> myawesomeblog.com &gt; <span class="hljs-built_in">source</span>/CNAME
<span class="hljs-comment"># Generar archivos</span>
hexo generate
<span class="hljs-comment"># Este comando generará una carpeta docs en nuestro proyecto</span>
</code></pre>
<h4 id="agregar-origen-remoto-de-repositorio-y-hacer-el-primer-commit">Agregar origen remoto de repositorio y hacer el primer commit</h4>
<p>Puedes ver el artículo completo en la ayuda de Github
https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/</p>
<pre><code class="lang-sh">$ git init
$ git add .
$ git commit -m <span class="hljs-string">"First commit"</span>
<span class="hljs-comment"># Agregar origen remoto URL de nuestro repositorio creado en el paso 3</span>
$ git remote add origin remote https://github.com/diegofcornejo/myawesomeblog.git 
<span class="hljs-comment"># Verificar la nueva URL remota</span>
git remote -v 
<span class="hljs-comment"># Push cambios</span>
$ git push origin master
</code></pre>
<h3 id="8-configurar-github-pages">8. Configurar Github Pages</h3>
<p>Ingresar nuestro repositorio en la web
https://github.com/diegofcornejo/myawesomeblog
Veremos que ahora nuestro repositorio tiene archivos
<img src="https://randomtechguy.vercel.app/images/20190105/8-1.PNG" alt />
Click en el Tab "Settings"
<img src="https://randomtechguy.vercel.app/images/20190105/8-2.PNG" alt />
En la sección "Github Pages" Seleccionar master branch / docs folder y Salvar
<img src="https://randomtechguy.vercel.app/images/20190105/8-3.PNG" alt /></p>
<h3 id="9-finalmente-abrimos-nuestro-navegador-y-escribimos-nuestro-dominio-randomtechguycom-y-veremos-nuestro-blog-funcionando">9. Finalmente abrimos nuestro navegador y escribimos nuestro dominio (randomtechguy.com) y veremos nuestro blog funcionando.</h3>
]]></content:encoded></item><item><title><![CDATA[How to create a blog using Hexo, Github Pages, Cloudflare with SSL]]></title><description><![CDATA[UPDATE 2021
Those this steps are really easy replacement by import your hexo repo into  vercel , but if for any reason you don't want to use vercel, keep reading.
We need:

A godaddy.com account with a domain
A cloudflare.com account
A github.com acc...]]></description><link>https://blog.diegocornejo.com/how-to-create-a-blog-using-hexo-github-pages-cloudflare-with-ssl</link><guid isPermaLink="true">https://blog.diegocornejo.com/how-to-create-a-blog-using-hexo-github-pages-cloudflare-with-ssl</guid><category><![CDATA[blog]]></category><category><![CDATA[cloudflare]]></category><category><![CDATA[SSL]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[JAMstack]]></category><dc:creator><![CDATA[Diego Cornejo]]></dc:creator><pubDate>Sun, 15 Aug 2021 01:19:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1628990168690/8P4B6_WxP.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="update-2021">UPDATE 2021</h2>
<p>Those this steps are really easy replacement by import your hexo repo into  <a target="_blank" href="https://vercel.com">vercel</a> , but if for any reason you don't want to use vercel, keep reading.</p>
<h2 id="we-need"><strong>We need:</strong></h2>
<ul>
<li>A godaddy.com account with a domain</li>
<li>A cloudflare.com account</li>
<li>A github.com account</li>
<li>Git Bash (https://git-scm.com/downloads)</li>
<li>Node JS (https://nodejs.org)</li>
</ul>
<h2 id="process"><strong>Process</strong></h2>
<h3 id="1-enter-to-cloudflarecom-and-add-a-site">1. Enter to cloudflare.com and add a site</h3>
<p><img src="https://randomtechguy.vercel.app/images/20190105/1-1.jpg" alt />
<img src="https://randomtechguy.vercel.app/images/20190105/1-2.PNG" alt />
<img src="https://randomtechguy.vercel.app/images/20190105/1-3.PNG" alt />
<img src="https://randomtechguy.vercel.app/images/20190105/1-4.PNG" alt />
<img src="https://randomtechguy.vercel.app/images/20190105/1-5.PNG" alt />
<img src="https://randomtechguy.vercel.app/images/20190105/1-6.jpg" alt />
<img src="https://randomtechguy.vercel.app/images/20190105/1-7.jpg" alt /></p>
<h3 id="2-enter-to-godaddycom-and-config-nameservers">2. Enter to godaddy.com and config nameservers</h3>
<h4 id="setup-nameservers">Setup Nameservers</h4>
<p>Click on "DNS"
<img src="https://randomtechguy.vercel.app/images/20190105/2-1.jpg" alt />
In the section "Nameservers" click on "Change"
<img src="https://randomtechguy.vercel.app/images/20190105/2-2.jpg" alt /></p>
<p>Select custom and add cloudflare's nameservers</p>
<p>Delete any nameserver if exist.</p>
<p>Cloudflare nameservers</p>
<ul>
<li>joan.ns.cloudflare.com</li>
<li>nash.ns.cloudflare.com
<img src="https://randomtechguy.vercel.app/images/20190105/2-3.PNG" alt /></li>
</ul>
<h4 id="validate-nameservers">Validate Nameservers</h4>
<p>According to Cloudflare and Godaddy the process to change nameservers can take up to 24 hours,  but normally take a few minutes, you can validate in two ways:</p>
<h4 id="from-cli">From CLI</h4>
<p>Open the terminal and paste next command</p>
<pre><code class="lang-sh">$ nslookup -<span class="hljs-built_in">type</span>=ns randomtechguy.com
</code></pre>
<p><img src="https://randomtechguy.vercel.app/images/20190105/2-4.PNG" alt /></p>
<h4 id="from-web">From web</h4>
<p>Enter to https://dnschecker.org/
<img src="https://randomtechguy.vercel.app/images/20190105/2-5.PNG" alt /></p>
<h3 id="3-create-a-repo-in-github">3. Create a repo in Github</h3>
<p>Enter to github.com and create repo
<img src="https://randomtechguy.vercel.app/images/20190105/3-1.PNG" alt />
Add a name to your new repo and click on "Create repository"
<img src="https://randomtechguy.vercel.app/images/20190105/3-2.PNG" alt />
<img src="https://randomtechguy.vercel.app/images/20190105/3-3.PNG" alt /></p>
<h3 id="4-verifies-security-certs-availabilityssl">4. Verifies security certs availability(SSL)</h3>
<p>At this time your domain is management by cloudflare so we have an SSL certificate.
Enter to cloudflare.com
Click on your site created at step 1
Click on Crypto and verify certificate status, it should be active like under image:
<img src="https://randomtechguy.vercel.app/images/20190105/4-1.PNG" alt />
Go to section "Always Use HTTPS" and check "On"
<img src="https://randomtechguy.vercel.app/images/20190105/4-2.PNG" alt /></p>
<h2 id="from-here-we-will-use-the-terminal-d">From here we will use the terminal :D</h2>
<h3 id="5-install-hexo-httpshexoio">5. Install hexo (https://hexo.io/)</h3>
<p>Open the terminal</p>
<pre><code class="lang-sh"><span class="hljs-comment"># Install hexo globally</span>
$ npm install hexo-cli -g
<span class="hljs-comment"># Start a hexo project</span>
$ hexo init myawesomeblog
<span class="hljs-comment"># Enter to project folder and install dependencies</span>
$ <span class="hljs-built_in">cd</span> myawesomeblog
$ npm install
<span class="hljs-comment"># Test our new blog</span>
hexo server
<span class="hljs-comment"># This command should return</span>
INFO  Start processing
INFO  Hexo is running at http://localhost:4000 . Press Ctrl+C to stop.
</code></pre>
<p>Open a browser with url http://localhost:4000.
Don't close the terminal, we will use later</p>
<p>The next steps are based on a awesome post by Jeff Ferrari
https://www.poweredbyjeff.com/2018/05/14/Deploying-Hexo-website-to-Github-Pages/</p>
<h3 id="6-setup-hexo-for-upload-to-github">6. Setup hexo for upload to Github</h3>
<h4 id="edit-configyml">Edit _config.yml</h4>
<p>Edit URL
    url: https://randomtechguy.com #your domain with https</p>
<p>Edit public dir from public to docs
    public_dir: docs</p>
<p>Comment deploy config</p>
<pre><code><span class="hljs-comment">#deploy:</span>
<span class="hljs-comment">#   type:</span>
</code></pre><h3 id="7-generate-files-and-upload-to-github">7. Generate files and upload to Github</h3>
<p>Open the terminal</p>
<h4 id="generate-files">Generate files</h4>
<pre><code class="lang-sh"><span class="hljs-comment"># Remove plugins</span>
$ npm uninstall hexo-generator-cname hexo-deployer-git
<span class="hljs-comment"># Create CNAME file in source folder, use nano, vim or your prefer editor</span>
$ nano <span class="hljs-built_in">source</span>/CNAME <span class="hljs-comment">#no extension, all uppercase</span>
<span class="hljs-comment"># The contents of this file should be the domain of your website</span>
$ <span class="hljs-built_in">echo</span> myawesomeblog.com &gt; <span class="hljs-built_in">source</span>/CNAME
<span class="hljs-comment"># Generate files</span>
hexo generate
<span class="hljs-comment"># This commando should generate a folder named docs</span>
</code></pre>
<h4 id="add-remote-origin-and-make-first-commit">Add remote origin and make first commit</h4>
<p>You can see original post in Github Help
https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/</p>
<pre><code class="lang-sh">$ git init
$ git add .
$ git commit -m <span class="hljs-string">"First commit"</span>
<span class="hljs-comment"># Add remote origin URL, use URL from your repo created at the step 3</span>
$ git remote add origin remote https://github.com/diegofcornejo/myawesomeblog.git 
<span class="hljs-comment"># Verifies the new remote URL</span>
git remote -v 
<span class="hljs-comment"># Push changes</span>
$ git push origin master
</code></pre>
<h3 id="8-setup-github-pages">8. Setup Github Pages</h3>
<p>Open your repo on the web
Now should see files in your repo
<img src="https://randomtechguy.vercel.app/images/20190105/8-1.PNG" alt />
Click on Tab "Settings"
<img src="https://randomtechguy.vercel.app/images/20190105/8-2.PNG" alt />
On section "Github Pages" Select master branch / docs folder and Save
<img src="https://randomtechguy.vercel.app/images/20190105/8-3.PNG" alt /></p>
<h3 id="9-finally-open-a-browser-and-write-your-domain-will-see-your-blog-running-on-your-domain">9. Finally open a browser and write your domain, will see your blog running on your domain.</h3>
]]></content:encoded></item></channel></rss>