fix: command injection in scanner CLI exec() calls#5
Closed
reesthomas212 wants to merge 58 commits intojonradoff:masterfrom
Closed
fix: command injection in scanner CLI exec() calls#5reesthomas212 wants to merge 58 commits intojonradoff:masterfrom
reesthomas212 wants to merge 58 commits intojonradoff:masterfrom
Conversation
- 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>
…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>
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
exec()withexecFile()at all 3 locations inscanner/src/cli.tswhere user-controlled--outpath values were interpolated into shell commandsexecFile()bypasses the shell entirely, preventing injection via crafted filenames likereport.html"; rm -rf /; #test,scan(single-domain), andscan(multi-domain)--openflag handlersResolves scout finding #2. See
scout/findings/002-command-injection-cli.mdfor details.Test plan
npx tsc --noEmitpasses inscanner/go build ./cmd/server/...passes inbackend/npx mcplens test --url <url> --open --out "test report.html"opens correctly on each platform--outvalues no longer execute shell commands🤖 Generated with Claude Code