Skip to content

fix: command injection in scanner CLI exec() calls#5

Closed
reesthomas212 wants to merge 58 commits intojonradoff:masterfrom
reesthomas212:fix/002-command-injection
Closed

fix: command injection in scanner CLI exec() calls#5
reesthomas212 wants to merge 58 commits intojonradoff:masterfrom
reesthomas212:fix/002-command-injection

Conversation

@reesthomas212
Copy link
Copy Markdown

Summary

  • Replaced exec() with execFile() at all 3 locations in scanner/src/cli.ts where user-controlled --out path values were interpolated into shell commands
  • execFile() bypasses the shell entirely, preventing injection via crafted filenames like report.html"; rm -rf /; #
  • Affects the test, scan (single-domain), and scan (multi-domain) --open flag handlers

Resolves scout finding #2. See scout/findings/002-command-injection-cli.md for details.

Test plan

  • npx tsc --noEmit passes in scanner/
  • go build ./cmd/server/... passes in backend/
  • Manual: npx mcplens test --url <url> --open --out "test report.html" opens correctly on each platform
  • Manual: verify crafted --out values no longer execute shell commands

🤖 Generated with Claude Code

reesthomas212 and others added 30 commits March 23, 2026 05:38
- Fix missing PricingModel in plan seed (was causing startup crash)
- Add cmd/setup-win for Windows-compatible system initialization
- Initialize system with MCPLens root tenant and admin account
- Verify MongoDB connection, login, and Stripe config working

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace all user-facing "LastSaaS"/"lastsaas" strings with "MCPLens"/"mcplens".
Updates landing page to scanner positioning, renames localStorage/sessionStorage
keys, updates CLI usage strings, MCP server env vars (LASTSAAS_URL →
MCPLENS_URL), API docs title, welcome messages, config defaults, and branding
fallbacks. Go module paths left unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Copy scanner engine from agent-lens (TypeScript)
- Shopify-specific scenarios only (no pre-pivot generic scenarios)
- Standalone tsconfig (no monorepo dependency)
- Verified: scans allbirds.com, scores 100/100

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- internal/scanner/service.go: shells out to Node CLI, parses JSON results
- internal/scanner/store.go: MongoDB CRUD for scan results
- internal/scanner/models.go: Go structs matching scanner output
- handlers/scanner.go: POST /api/scan, GET /api/scan/:id, GET /api/scan/domain/:domain, GET /api/scans
- Rate limiting: 5/hour unauthenticated, 20/hour authenticated
- Write timeout raised to 90s for scan duration
- Tested: allbirds.com scan returns compositeScore 100, stored in MongoDB

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ScanPage.tsx: public scan input at /scan
- ScanResultPage.tsx: results with score circle, categories, alerts, share
- API client: triggerScan() and getLatestScan() functions
- Public routes (no auth required)
- TypeScript compiles clean

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Inline scanner input in hero (navigates to /scan)
- Stats section (5.5M stores, 15-30% conversion, 10 scenarios)
- How it works, scoring categories, agency CTA
- Pricing section (Free/Starter $19/Agency $49)
- CLI section with npx example
- Dark theme matching scanner report aesthetic

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- TrackedStore model with score trend tracking
- tracked_store.go: CRUD with entitlement enforcement
- API: POST/DELETE/GET /api/tracked-stores + history endpoint
- Entitlement check: max_tracked_stores per plan

Frontend:
- DashboardPage: tracked store grid with score rings, trend arrows
- StoreDetailPage: score timeline chart, scan history, category breakdown
- Add/remove stores, upgrade prompts for free users
- "Scan Now" button for on-demand rescans

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- GET /api/og/{domain}: returns a 1200x630 SVG image with domain name,
  colour-coded composite score, and "Powered by MCPLens" footer; serves
  a grey placeholder when the domain has not yet been scanned
- GET /api/badge/{domain}: returns shields.io-compatible JSON with
  brightgreen/yellow/red colour mapping (80+/50-79/<50)
- ScanResultPage: sets document.title and og:/twitter: meta tags on
  result load, and shows a README badge embed section with copy-to-
  clipboard Markdown

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…re CLI

Task 010 - Public API v1:
- POST /api/v1/scan, GET /api/v1/scan/:id, GET /api/v1/stores/:domain/latest
- All routes require Bearer lsk_ API key authentication via existing RequireAuth middleware

Task 011 - White-label agency branding:
- AgencyBranding model with logo, companyName, accentColor fields stored per tenant
- GET/PUT /api/branding/agency (auth + tenant + agency_branding entitlement)
- GET /api/scan/:id/report returns HTML report; ?branding=true applies agency branding via string replacement
- Added AgencyBrandings() collection accessor to db.MongoDB

Task 012 - Batch and compare CLI:
- scan command now accepts multiple domains: mcplens scan a.com b.com --compare
- Generates side-by-side comparison HTML report with per-category winner column
- batch command: mcplens batch --domains file.txt --output dir/ --delay 1000
  reads domains from text file, scans each with configurable delay, writes per-domain JSON

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
API v1:
- POST/GET /api/v1/scan, GET /api/v1/stores/:domain/latest
- Authenticated via lsk_ API keys

Agency branding:
- GET/PUT /api/branding/agency (Agency plan entitlement)
- GET /api/scan/:id/report?branding=true applies white-label

CLI batch/compare:
- mcplens scan domain1 domain2 --compare (side-by-side report)
- mcplens batch --domains file.txt --output dir/ --delay 1000

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Quick start, scoring methodology, CLI reference
- CI/CD integration example (GitHub Actions)
- Pricing table, web scanner link
- Compelling product narrative for HN/GitHub visitors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…and deployment

Dockerfile.saas builds all three components (Go backend, React frontend,
Node.js scanner) into a single image with Node.js in the runtime layer and
SCANNER_PATH=/app/scanner/dist baked in. fly.saas.toml points at the new
Dockerfile. deploy.md documents the full Fly.io and Railway deployment flow
with env var reference, DNS setup, and Stripe webhook instructions.
CLAUDE.md updated with the correct deploy cross-reference.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Multi-stage Dockerfile: Go backend + React frontend + Node scanner
- fly.saas.toml configured for mcplens app
- deploy.md: step-by-step for Fly.io and Railway
- Fixed Go builder version to 1.24-alpine (1.25 doesn't exist yet)
- Updated CLAUDE.md with MCPLens deployment instructions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Loss-aversion hero headline
- 'Why this matters now' section with sourced data
- Sample report preview mockup
- Trust section with real scan data
- Pricing with 'best for' labels
- Stronger agency ROI narrative
- Reassurance micro-copy throughout
- Reduced cognitive load for non-technical visitors
- Before/after narrative section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Slate-50 background, white cards with subtle shadows
- Inter font via Google Fonts (already present)
- Pill-shaped buttons, rounded-xl cards
- Score colors adapted for light background
- All 5 pages updated: Landing, Scan, ScanResult, Dashboard, StoreDetail
- Layout and ThemeContext default to light mode
- CSS theme tokens inverted: light is now default, dark is opt-in

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create new Stripe products/prices: Pro ($99/mo), Agency ($199/mo)
- Archive old Starter ($19) and Agency ($49) plans, create new Pro and
  Agency plans with updated entitlements (fix_instructions, ci_cd,
  agency_bonuses)
- Add email-gated fix instructions on scan result page: findings are
  shown with title/severity/impact (free), but "how to fix" requires
  email capture or paid plan
- Add POST /api/leads endpoint for email capture with token-based
  session unlock, plus GET /api/leads/verify for token validation
- Update landing page pricing to Free/$99 Pro/$199 Agency tiers
- Move CI/CD from free to Pro tier across README and landing page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Interactive 3-slider revenue impact calculator
- Real-time dollar loss estimation with sourced methodology
- Pricing rewritten: $2,500 anchor → $199 Agency → $99 Pro → Free
- Outcome framing, bonus stacking, guarantee copy
- 'Most Popular' badge on Agency tier
- 'Best for' labels on all tiers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Scroll-triggered fade-in-up on all landing page sections
- Staggered grid animations (75ms) for how-it-works, stats, pricing, scoring categories
- Hero text sequential reveal (h1 at 0.1s, p at 0.3s, form at 0.45s)
- Card hover lift effects on pricing, scoring, stats, and report preview cards
- Animated ScanDemo component: typewriter → shimmer loading → score circle + staggered results, loops ~8s
- ScanPage input card fades in on mount
- prefers-reduced-motion respected throughout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 3 cold email templates for agency outreach (initial, follow-up, competitor comparison)
- 10-slide white-label HTML pitch deck with MCPLens dark theme
- 10 common Shopify MCP fix snippets with Liquid/GraphQL/JS code
- /bonuses page with download links, gated behind Agency plan check

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace 'Sign in' with 'Get Started' button in nav (keep secondary Sign in link)
- Pricing card CTAs now link to /signup instead of /scan
- Add cross-links between login and register pages
- Pre-fill email on signup page from query param
- Connect email gate to registration flow with CTA after unlock
- Add /register -> /signup redirect preserving query params
- Add Terms of Service at /terms
- Add Privacy Policy at /privacy
- Footer links to legal pages from landing page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- gymshark.com (97 - excellent, affirms strong attributes)
- colourpop.com (11 - critical, shows price data fix instruction)
- allbirds.com (100 - perfect, affirms rich descriptions)
- ridgewallet.com (36 - needs work, shows description length fix)
- tentree.com (86 - good, suggests adding structured attributes)
- Indicator dots show which store is active
- Each finding is contextual: pass=affirmation, fail=specific fix

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
reesthomas212 and others added 27 commits March 23, 2026 13:27
…r logo

- Container height 380→440px (finding text no longer clipped)
- Removed indicator dots
- All timings slowed 50%: typewriter 55→82ms, scan msgs 650→975ms, results hold 3.5→5.25s, fade 900→1350ms
- Logo box sizes increased, rounded-xl→rounded-lg for squarer shape

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 4-tier structure: Free / Pro $50 / Max $200 / Agency $600
- Annual billing: save 2 months (default toggle)
- Founding user promos: 50% off first month, 4 months off annual
- Hormozi-structured layout: Agency anchor left, Max center (emphasized), Pro right
- Layer 2+3 features marked as Early Access on Max/Agency
- 30-day money-back guarantee
- Stripe products, prices, and promo codes created

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ng, legal updates

Scanner:
- AI quality assessment (ai-assessor.ts) using Gemini Flash via @google/genai SDK
- Simulated buyer agent (agent-simulator.ts) with function calling loop
- Intent generator, persona system (default/price/quality/speed)
- --assess and --simulate CLI flags on test + scan commands
- Read-only simulation (no cart/checkout mutations on Shopify stores)

Backend:
- AIAssessment + AgentSimulation Go structs in scanner models
- ScanOptions struct for assess/simulate/personas flag passthrough
- ai_assessment, ai_simulation, cross_agent entitlement checks in scanner handler
- Comprehensive test suite: auth flows (MFA, email verify, password reset, sessions),
  payment flows (free checkout, entitlement enforcement, plan upgrade),
  scanner endpoints (tracked stores, entitlement gates), E2E tests

Frontend:
- PlanContext providing global plan/entitlement awareness
- UpgradeGate component for tier-gated UI
- AI Assessment section in ScanResultPage (scores, query simulations, findings)
- Simulation transcript viewer (expandable scenarios, persona tabs, failure callouts)
- Upsell cards for non-Max users on both AI and simulation features
- Tier-aware nav (Bonuses visible Pro+), Bonuses page shows per-tier content
- Pricing cards: Free/Pro/Max grid + standalone Agency, fixed toggle, aligned layout
- FOUNDING50 coupon code in founding user banner

Legal:
- ToS: read-only MCP access, AI features disclosure (Gemini), store owner opt-out/claim
- Privacy: AI-generated analysis data collection, Google Gemini third-party, data retention
  policy (90 days free, subscription+30 paid), store owner data rights section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- placeholder-dark-500 → placeholder-dark-400 across 22 files (invisible placeholder
  text in dark theme inputs — affected every form in the authenticated app)
- disabled:opacity-50 → disabled:opacity-60 across 30 files (unreadable disabled
  buttons and inputs sitewide)
- placeholder-slate-400 → placeholder-slate-500 on public page inputs (borderline
  contrast on ScanPage, ScanResultPage, LandingPage)
- Announcement banner text-primary-300 → text-primary-200 (low contrast on tinted bg)
- PlanPage disabled button text-dark-300 → text-dark-200 with disabled:text-dark-400
- Terms/Privacy "Last updated" text-slate-400 → text-slate-500

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dead imports

- Exclude *.test.ts/tsx from tsconfig.app.json (tsc -b catches unused imports
  in test files that vitest globals mode handles differently)
- Remove unused imports in BonusesPage (useState, toast, plansApi, getErrorMessage,
  useTenant — all leftover from PlanContext refactor)
- Remove unused isPaid from Layout.tsx destructuring

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ead links

Root cause: app was built for dark mode but defaulted to light mode,
making all headings, buttons, and form values invisible on white backgrounds.

Critical fixes:
- Default theme to 'dark' instead of 'light' (ThemeContext.tsx)
- Both "Upgrade to Max" CTAs now use consistent blue-500 (were indigo vs purple)

Other fixes:
- Remove dead "Docs" footer link (was href="#")
- Fix "11-100" score range stat to "0-100"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
One-time localStorage migration: users who previously visited when the
default was 'light' have mcplens_theme='light' cached. This migration
forces them to 'dark' on first load after the update.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: app used hardcoded text-white across all authenticated pages,
which was invisible on light-mode backgrounds. The dark-* CSS custom
properties already flip correctly between themes, but text-white doesn't.

Changes:
- Revert default theme to 'light' (removed dark mode migration hack)
- Replace text-white → text-dark-50 across 53 app/auth/admin files
  (text-dark-50 is near-black in light mode, near-white in dark mode)
- Restore text-white on colored backgrounds (bg-primary, bg-red, bg-blue,
  bg-gradient, etc.) where white text is correct regardless of theme
- Remove theme toggle from Layout header (theme switch now only in Settings)

Both light and dark modes work correctly. Theme persists via localStorage
and user preference API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New rescan service (internal/rescan/service.go):
- Background scheduler with MongoDB leader lock (single-instance safe)
- Checks every 5 minutes for tracked stores due for rescan
- Queries tenant entitlements: Pro=weekly (168h), Max/Agency=daily (24h)
- Processes 10 stores per cycle, oldest-first (natural staggering)
- Resilient: failed scans retry next cycle (lastScannedAt unchanged)
- Updates TrackedStore score/trend after each successful rescan

Email alerts (internal/email/resend.go):
- SendScoreDropAlert: triggers when score drops 5+ points
- HTML email with score change visualization, common causes, dashboard CTA
- Finds tenant owner via memberships collection

Integration:
- Wired into main.go after metrics service (Start/Stop lifecycle)
- Uses existing scanner.Service.ScanStore for actual scanning
- Uses existing TrackedStoreStore.UpdateTrackedStoreScore for persistence

Entitlement key: rescan_frequency (numeric, hours between rescans)
- Free: 0, Pro: 168 (weekly), Max: 24 (daily), Agency: 24 (daily)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 5 tests (11 sub-tests) for rescan service: job fields, score drop
  threshold boundary cases, constants, service creation, graceful stop
- Fix: releaseLock now guards against nil db (prevents panic when
  Stop is called without a database connection)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hardcoded rgba(0,0,0,...) colors with CSS custom properties
(var(--color-dark-*)) so grid lines and labels adapt to light/dark mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- POST /api/billing/scan-purchase: creates Stripe one-time checkout
  with domain + feature in session metadata
- Webhook handler: detects scanPurchase metadata, triggers scan with
  purchased feature in background goroutine, records transaction
- WebhookHandler.SetScanner() to access scanner service
- CheckoutRequest.ExtraMetadata for custom Stripe session metadata
- Stripe checkout supports one-time payments without bundleId

Frontend:
- BuyFeatureButton component: checks auth state, redirects to signup
  if not logged in (with returnTo URL), opens Stripe checkout if logged in
- Upsell cards now show "$5 / $10" buy buttons alongside "upgrade to Max"
- scanPurchaseApi.checkout() client method
- Login/Signup pages support ?returnTo= query param for post-auth redirect

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dashboard:
- "Recent Scans" section visible to all users (including free tier)
- Shows last 10 scans with score, domain, date, duration
- Links to public scan result page for each scan
- Loads in parallel with tracked stores (Promise.allSettled)
- myScansApi.list() frontend client for GET /api/scans

Tests:
- 6 scan purchase tests: auth gate, missing domain, invalid feature,
  nil Stripe, valid assess request, valid simulate request
- scan-purchase route added to test server helpers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mobile navigation:
- Layout.tsx: hamburger menu (Menu/X icons) visible on md:hidden,
  opens slide-down nav drawer with all nav items + Admin link
- LandingPage.tsx: hamburger menu for public nav (Pricing, GitHub,
  Sign in) visible on sm:hidden, opens dropdown panel

Responsive overflow fixes:
- StoreDetailPage: SVG chart minWidth 320px → 280px (fits 375px viewport)
- StoreDetailPage: long date strings → short format (Mar 24, '26)
- PlanPage: feature column min-w-[200px] → min-w-[140px] sm:min-w-[200px]
- ScanResultPage: tool args JSON gets break-all class to prevent overflow

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pro tier has no AI features per entitlement table. AI assessment and
simulation are Max-only (or available as $5/$10 one-time purchases).
Replaced with "JSON + HTML report downloads" which is an actual Pro feature.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pro card now includes "Unlimited AI quality assessments" — the cost is
~$0.001/scan which is negligible at $50/mo. This makes Pro a real audit
tool (see what's wrong + how to fix it) vs just a monitoring tool.

Max card: replaced duplicate "Unlimited AI assessments" with
"Side-by-side store comparison" (Max-specific feature).

ScanResultPage: AI assessment upsell now says "upgrade to Pro" (was Max).
Simulation upsell still says "upgrade to Max" (simulations remain Max+).

Entitlement: ai_assessment should be set to true on Pro, Max, and Agency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AI cost tracking was using Gemini 2.5 Flash rates ($0.15/$0.60 per M)
but the code uses gemini-3-flash-preview which costs $0.50/$3.00 per M.

Corrected: AI assessment ~$0.005/scan (was showing $0.001)
Corrected: Agent simulation ~$0.05/scan (was showing $0.02)

Actual margins remain 85-92% at $29/$79/$249 pricing across all scale
scenarios (10-200 users). Only risk: Agency worst-case with automated
cross-agent rescans ($307/mo cost on $249/mo revenue).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Researched actual infrastructure and AI costs at scale (10-200 users):
- Fly.io: $7-10/mo base (shared 1CPU/1GB)
- MongoDB Atlas: $25-58/mo (M5-M10)
- Gemini 3 Flash: $0.005/assessment, $0.05/simulation
- Margins: 85% at 10 users, 91% at 50 users, 92% at 200 users

Landing page updated:
- Pro: $29/mo ($290/yr, save $58) — was $50
- Max: $79/mo ($790/yr, save $158) — was $200
- Agency: $249/mo ($2,490/yr, save $498) — was $600

PlanPage prices are dynamic from backend — update in admin panel.

Rescan service confirmed: already Layer 1 only (no AI on auto-rescans).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Autonomous scout monitors the live site, audits source code, and
researches improvements. Findings are stored in scout/findings/ and
coordinated with the builder through scout/HANDOFF.md.

- /scout skill: 4-phase cycle (health, crawl, audit, research)
- /builder-check skill: builder pulls and picks up new findings
- Git-based coordination protocol via HANDOFF.md status table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Health check logs show Resend integration failing with 401 on every
cycle. All transactional emails (verification, password reset, alerts)
are non-functional. Likely an expired or missing RESEND_API_KEY secret.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scout finding 001: RESEND_API_KEY was returning 401 on all health checks.
Generated new sending-access key and updated both local .env and Fly.io secret.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Scout pushes findings to master (only scout/ files)
- Builder works on fix/ branches and opens PRs
- Master reviews, merges PRs, and deploys
- Nobody pushes code directly to master

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- TrackedStore gets optional Label field for user-defined tags
- AddTrackedStore accepts label in request body
- PATCH /api/tracked-stores/{id}/label endpoint for updating labels
- GET /api/tracked-stores/comparison returns score history for all
  tracked stores in one response (powers comparison chart)
- Score alerts now fire on significant changes (up or down, ≥5 pts)
  not just drops — "competitor improved" is as valuable as "you dropped"
- Weekly digest email summarizes all tracked store scores with deltas
  Runs hourly check, sends once per 7 days per tenant via digest_sent collection
- SendScoreChangeAlert replaces SendScoreDropAlert (backward compat kept)
- SendWeeklyDigest with HTML table showing store/score/change per row

Frontend:
- Dashboard comparison timeline chart (SVG multi-line, color-coded per store)
- Store labels shown on dashboard cards above domain name
- Add Store modal includes optional label field
- API client updated with comparison(), updateLabel(), label on TrackedStore
- ComparisonSeries/ComparisonPoint types

Tests:
- TrackedStore label + ComparisonSeries struct tests
- Comparison endpoint auth + empty tests
- Label update 404 test
- AddTrackedStore with label test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
User-controlled --out flag values are interpolated into shell commands
via exec(), allowing arbitrary command execution. Fix: use execFile().

Also marked finding 001 (Resend 401) as regression — still unfixed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…canner CLI

The scanner CLI interpolated user-controlled --out flag values into shell
commands via exec(), allowing arbitrary command execution. Switched all 3
occurrences to execFile() which bypasses the shell entirely.

Resolves scout finding #2.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
reesthomas212 referenced this pull request in reesthomas212/mcplens Mar 24, 2026
When concurrent requests hit an expired rate limit window, both see
ErrNoDocuments and both try to upsert a new doc with the same _id.
The second upsert fails with E11000 duplicate key. Now we catch
the duplicate key error and retry the increment once, since the
other request just created the document.

Resolves scout finding #5.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant