codeprobe-scanner 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +19 -0
- package/.dockerignore +17 -0
- package/.env.development +8 -0
- package/.env.example +20 -0
- package/.env.setup +214 -0
- package/.github/workflows/codeprobe-scan.yml +137 -0
- package/.github/workflows/codeprobe.yml +84 -0
- package/.github/workflows/scan-schedule.yml +28 -0
- package/ANALYSIS_SUMMARY.md +365 -0
- package/API_INTEGRATIONS.md +469 -0
- package/BUILD_PLAYBOOK.md +349 -0
- package/CLAUDE.md +106 -0
- package/DEPLOY.md +452 -0
- package/DEPLOYMENT_STATUS.md +240 -0
- package/DEPLOY_CHECKLIST.md +316 -0
- package/Dockerfile +24 -0
- package/EXECUTION_PLAN.html +1086 -0
- package/IMPLEMENTATION_COMPLETE.md +288 -0
- package/IMPLEMENTATION_SUMMARY.md +443 -0
- package/INTERACTIVE_FIX_FLOW.md +308 -0
- package/MIGRATION_COMPLETE.md +327 -0
- package/ORCHESTRATOR_SYNTHESIS.json +80 -0
- package/PENDING_WORK.md +308 -0
- package/PREFLIGHT_PLAN.md +182 -0
- package/QUICKSTART.md +305 -0
- package/README.md +15 -0
- package/STAGE_1_SETUP_ENGINE.md +245 -0
- package/STAGE_2_ARCHITECTURE.md +714 -0
- package/STAGE_2_CLI_VERIFICATION.md +269 -0
- package/STAGE_2_COMPLETE.md +332 -0
- package/STAGE_2_IMPLEMENTATION_PLAN.md +679 -0
- package/STAGE_3_COMPLETE.md +246 -0
- package/STAGE_3_DASHBOARD_POLISH.md +371 -0
- package/STAGE_3_SETUP.md +155 -0
- package/VIDEODB_INTEGRATION.md +237 -0
- package/archived/DASHBOARD_UI_WALKTHROUGH.md +392 -0
- package/archived/FRONTEND_SETUP.md +236 -0
- package/archived/auth.ts +40 -0
- package/archived/dashboard/components/BusinessImpactCard.tsx +48 -0
- package/archived/dashboard/components/CVETable.tsx +104 -0
- package/archived/dashboard/components/ErrorBoundary.tsx +48 -0
- package/archived/dashboard/components/PatchDiffViewer.tsx +43 -0
- package/archived/dashboard/components/RiskGauge.tsx +64 -0
- package/archived/dashboard/frontend.tsx +104 -0
- package/archived/dashboard/hooks/useAuth.ts +32 -0
- package/archived/dashboard/hooks/useScan.ts +65 -0
- package/archived/dashboard/index.html +15 -0
- package/archived/dashboard/pages/LoginPage.tsx +28 -0
- package/archived/dashboard/pages/ScanDetailPage.tsx +143 -0
- package/archived/dashboard/pages/ScansListPage.tsx +160 -0
- package/bin/install-and-run.sh +91 -0
- package/bun.lock +603 -0
- package/codeprobe-prd.md +674 -0
- package/cve-cache.json +25 -0
- package/demo-vulnerable-app/.github/workflows/codeprobe.yml +32 -0
- package/demo-vulnerable-app/README.md +70 -0
- package/demo-vulnerable-app/package-lock.json +27 -0
- package/demo-vulnerable-app/package.json +15 -0
- package/demo-vulnerable-app/server.js +34 -0
- package/demo.sh +45 -0
- package/index.ts +19 -0
- package/package.json +28 -0
- package/patches.json +12 -0
- package/serve-dashboard.ts +23 -0
- package/src/api/server-cli.ts +270 -0
- package/src/api/server.ts +293 -0
- package/src/bot/server.ts +113 -0
- package/src/cli/commands/report.ts +92 -0
- package/src/cli/commands/scan-with-fix.ts +123 -0
- package/src/cli/commands/scan.ts +137 -0
- package/src/cli/config.ts +188 -0
- package/src/cli/errors.ts +120 -0
- package/src/cli/index.ts +137 -0
- package/src/cli/progress.ts +119 -0
- package/src/cli-server.ts +523 -0
- package/src/engine/index.ts +90 -0
- package/src/engine/matcher.ts +115 -0
- package/src/engine/parser.ts +91 -0
- package/src/engine/patcher.ts +280 -0
- package/src/engine/report.ts +137 -0
- package/src/engine/sandbox.ts +222 -0
- package/src/engine/scraper.ts +122 -0
- package/src/integrations/videodb.ts +153 -0
- package/src/mcp/server.ts +149 -0
- package/src/scraper-cron.ts +103 -0
- package/src/shared/constants.ts +88 -0
- package/src/shared/types.ts +123 -0
- package/src/shared/utils.ts +80 -0
- package/src/test/cli.test.ts +211 -0
- package/src/test/dashboard.test.ts +38 -0
- package/src/test/demo-scan.json +32 -0
- package/src/test/engine.test.ts +157 -0
- package/tailwind.config.js +11 -0
- package/tsconfig.json +30 -0
- package/verify-dashboard.ts +87 -0
- package/verify-env.sh +98 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: CodeProbe Demo - Security Scan
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
scan:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
steps:
|
|
9
|
+
- uses: actions/checkout@v4
|
|
10
|
+
with:
|
|
11
|
+
fetch-depth: 0
|
|
12
|
+
|
|
13
|
+
- name: Setup Bun
|
|
14
|
+
uses: oven-sh/setup-bun@v1
|
|
15
|
+
|
|
16
|
+
- name: Install CodeProbe
|
|
17
|
+
run: |
|
|
18
|
+
cd ..
|
|
19
|
+
bun install
|
|
20
|
+
|
|
21
|
+
- name: Run CodeProbe scan
|
|
22
|
+
run: |
|
|
23
|
+
cd ..
|
|
24
|
+
bun run src/cli/index.ts scan ./demo-vulnerable-app --json
|
|
25
|
+
env:
|
|
26
|
+
BRIGHT_DATA_API_KEY: ${{ secrets.BRIGHT_DATA_API_KEY }}
|
|
27
|
+
DAYTONA_API_KEY: ${{ secrets.DAYTONA_API_KEY }}
|
|
28
|
+
|
|
29
|
+
- name: Check for exploitable vulnerabilities
|
|
30
|
+
run: |
|
|
31
|
+
cd ..
|
|
32
|
+
bun run src/cli/index.ts scan ./demo-vulnerable-app --json | grep -q '"exploitable": true' && echo "✅ Vulnerability verified" || echo "⚠️ No exploitable vulnerabilities found"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# CodeProbe Demo: Vulnerable Node.js App
|
|
2
|
+
|
|
3
|
+
This is a deliberately vulnerable Node.js application used to demonstrate CodeProbe's exploit verification capabilities.
|
|
4
|
+
|
|
5
|
+
## Vulnerabilities
|
|
6
|
+
|
|
7
|
+
### CVE-2022-29078: EJS Template Injection RCE
|
|
8
|
+
|
|
9
|
+
**Affected Package:** `ejs@3.1.6`
|
|
10
|
+
**Severity:** CRITICAL (CVSS 9.8)
|
|
11
|
+
**Issue:** The EJS template engine allows template injection attacks that lead to arbitrary code execution.
|
|
12
|
+
|
|
13
|
+
**How it's vulnerable:**
|
|
14
|
+
- Line in `server.js`: `ejs.render(req.query.template, ...)`
|
|
15
|
+
- User can inject malicious EJS template expressions
|
|
16
|
+
- Server executes arbitrary JavaScript code
|
|
17
|
+
|
|
18
|
+
**Exploit:**
|
|
19
|
+
```bash
|
|
20
|
+
curl "http://localhost:3000/?template=<%= require('child_process').execSync('whoami') %>"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Fix:** Upgrade `ejs` to version 3.1.7 or higher.
|
|
24
|
+
|
|
25
|
+
## Running the Demo App
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd demo-vulnerable-app
|
|
29
|
+
npm install
|
|
30
|
+
npm start
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The app listens on `http://localhost:3000`.
|
|
34
|
+
|
|
35
|
+
## Using CodeProbe to Scan
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# From the codeprobe root directory:
|
|
39
|
+
bun run src/cli/index.ts scan ./demo-vulnerable-app
|
|
40
|
+
|
|
41
|
+
# Or with JSON output:
|
|
42
|
+
bun run src/cli/index.ts scan ./demo-vulnerable-app --json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Expected Output
|
|
46
|
+
|
|
47
|
+
CodeProbe will:
|
|
48
|
+
1. **Parse dependencies** — Extract EJS 3.1.6 from package.json
|
|
49
|
+
2. **Scrape CVE data** — Find CVE-2022-29078 in NVD (via Bright Data)
|
|
50
|
+
3. **Match versions** — Detect that EJS 3.1.6 is vulnerable
|
|
51
|
+
4. **Verify in sandbox** — Spawn a Daytona container and execute the PoC exploit
|
|
52
|
+
5. **Generate patch** — Create a patch to upgrade EJS to 3.1.7
|
|
53
|
+
6. **Report findings** — Display "CONFIRMED EXPLOITABLE" with business impact ($4.9M breach cost)
|
|
54
|
+
|
|
55
|
+
## Why This Demo?
|
|
56
|
+
|
|
57
|
+
- **Realistic vulnerability**: EJS template injection is a real, widely-exploited vulnerability
|
|
58
|
+
- **Node.js native**: Works in isolated containers without external dependencies
|
|
59
|
+
- **Clear proof**: Exploit evidence is captured in sandbox logs
|
|
60
|
+
- **Fast verification**: Exploit runs in < 2 seconds
|
|
61
|
+
|
|
62
|
+
## Files
|
|
63
|
+
|
|
64
|
+
- `package.json` — Intentionally pinned to vulnerable EJS 3.1.6
|
|
65
|
+
- `server.js` — Express server with template injection vulnerability
|
|
66
|
+
- `.github/workflows/codeprobe.yml` — CI/CD integration example
|
|
67
|
+
|
|
68
|
+
## Disclaimer
|
|
69
|
+
|
|
70
|
+
This app is for **security research and education only**. Do not use in production or with untrusted code.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "demo-vulnerable-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"lockfileVersion": 3,
|
|
5
|
+
"requires": true,
|
|
6
|
+
"packages": {
|
|
7
|
+
"": {
|
|
8
|
+
"name": "demo-vulnerable-app",
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"express": "^4.18.2",
|
|
12
|
+
"ejs": "3.1.6"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"node_modules/express": {
|
|
16
|
+
"version": "4.18.2",
|
|
17
|
+
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz"
|
|
18
|
+
},
|
|
19
|
+
"node_modules/ejs": {
|
|
20
|
+
"version": "3.1.6",
|
|
21
|
+
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=0.10.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "demo-vulnerable-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Demo application with intentional vulnerability for CodeProbe testing",
|
|
5
|
+
"main": "server.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node server.js",
|
|
8
|
+
"test": "bun test"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"express": "^4.18.2",
|
|
12
|
+
"ejs": "3.1.6"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {}
|
|
15
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const ejs = require("ejs");
|
|
3
|
+
const app = express();
|
|
4
|
+
|
|
5
|
+
app.use(express.urlencoded({ extended: false }));
|
|
6
|
+
|
|
7
|
+
// Vulnerable endpoint: directly renders user input as template
|
|
8
|
+
app.get("/render", (req, res) => {
|
|
9
|
+
const template = req.query.template || "Hello <%= name %>";
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
// VULNERABLE: ejs.render() executes arbitrary code in templates
|
|
13
|
+
// An attacker can inject: <%= require('child_process').execSync('id') %>
|
|
14
|
+
const result = ejs.render(template, { name: "World" });
|
|
15
|
+
res.send(`<pre>${result}</pre>`);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
res.status(500).send(`<pre>Error: ${error.message}</pre>`);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
app.get("/", (req, res) => {
|
|
22
|
+
res.send(`
|
|
23
|
+
<h1>CodeProbe Demo - EJS Template Injection</h1>
|
|
24
|
+
<p>This app uses EJS ${require("ejs/package.json").version}</p>
|
|
25
|
+
<p>Visit: <a href="/render?template=<%= 'VULNERABLE' %>">/render</a> to test</p>
|
|
26
|
+
<p>Or send a template parameter to exploit</p>
|
|
27
|
+
`);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const port = 3000;
|
|
31
|
+
app.listen(port, () => {
|
|
32
|
+
console.log(`Demo app listening on http://localhost:${port}`);
|
|
33
|
+
console.log(`Vulnerable ejs version: ${require("ejs/package.json").version}`);
|
|
34
|
+
});
|
package/demo.sh
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
echo "=== CodeProbe Demo Script ==="
|
|
5
|
+
echo ""
|
|
6
|
+
|
|
7
|
+
# Colors for output
|
|
8
|
+
RED='\033[0;31m'
|
|
9
|
+
GREEN='\033[0;32m'
|
|
10
|
+
YELLOW='\033[1;33m'
|
|
11
|
+
BLUE='\033[0;34m'
|
|
12
|
+
NC='\033[0m' # No Color
|
|
13
|
+
|
|
14
|
+
# Check if bun is installed
|
|
15
|
+
if ! command -v bun &> /dev/null; then
|
|
16
|
+
echo -e "${RED}Error: bun is not installed${NC}"
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
echo -e "${BLUE}1. Clearing previous scans...${NC}"
|
|
21
|
+
rm -rf ~/.codeprobe/scans/*
|
|
22
|
+
echo -e "${GREEN}✓ Scans cleared${NC}"
|
|
23
|
+
echo ""
|
|
24
|
+
|
|
25
|
+
echo -e "${BLUE}2. Running scan...${NC}"
|
|
26
|
+
time bun run src/cli/index.ts scan .
|
|
27
|
+
echo -e "${GREEN}✓ Scan complete${NC}"
|
|
28
|
+
echo ""
|
|
29
|
+
|
|
30
|
+
echo -e "${BLUE}3. Displaying report...${NC}"
|
|
31
|
+
bun run src/cli/index.ts report
|
|
32
|
+
echo ""
|
|
33
|
+
|
|
34
|
+
echo -e "${BLUE}4. Testing with --json flag...${NC}"
|
|
35
|
+
bun run src/cli/index.ts scan . --json | head -20
|
|
36
|
+
echo "..."
|
|
37
|
+
echo ""
|
|
38
|
+
|
|
39
|
+
echo -e "${GREEN}✅ Demo successful!${NC}"
|
|
40
|
+
echo ""
|
|
41
|
+
echo -e "${YELLOW}Next steps:${NC}"
|
|
42
|
+
echo " • Run scan with fix: bun run src/cli/index.ts scan . --fix"
|
|
43
|
+
echo " • View config: bun run src/cli/index.ts config get"
|
|
44
|
+
echo " • Set API key: bun run src/cli/index.ts config set bright_data_api_key <key>"
|
|
45
|
+
echo ""
|
package/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createEngine } from "./src/engine/index";
|
|
2
|
+
|
|
3
|
+
const engine = createEngine();
|
|
4
|
+
const repoPath = process.argv[2] || "./demo-vulnerable-app";
|
|
5
|
+
|
|
6
|
+
console.log(`\n⚡ CodeProbe v1.0.0`);
|
|
7
|
+
console.log(`Scanning repository: ${repoPath}\n`);
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const report = await engine.scan(repoPath);
|
|
11
|
+
const formatted = (await import("./src/engine/report")).createReportBuilder().formatForTerminal(report);
|
|
12
|
+
console.log(formatted);
|
|
13
|
+
|
|
14
|
+
// Exit with appropriate code
|
|
15
|
+
process.exit(report.summary.exploitable_count > 0 ? 1 : 0);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error("❌ Scan failed:", error instanceof Error ? error.message : String(error));
|
|
18
|
+
process.exit(2);
|
|
19
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codeprobe-scanner",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Automated vulnerability scanner with exploit verification and video evidence",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codeprobe": "bin/install-and-run.sh"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"setup": "bin/install-and-run.sh"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@daytona/sdk": "^0.187.0",
|
|
17
|
+
"axios": "^1.6.5",
|
|
18
|
+
"chalk": "^5.3.0",
|
|
19
|
+
"cli-table3": "^0.6.3",
|
|
20
|
+
"dayjs": "^1.11.10",
|
|
21
|
+
"ora": "^8.0.1",
|
|
22
|
+
"react": "^18.2.0",
|
|
23
|
+
"react-dom": "^18.2.0",
|
|
24
|
+
"tailwindcss": "^3.3.0",
|
|
25
|
+
"videodb": "^0.2.7",
|
|
26
|
+
"zod": "^3.22.4"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/patches.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"cve_id": "CVE-2022-29078",
|
|
4
|
+
"package": "ejs",
|
|
5
|
+
"from_version": "3.1.0",
|
|
6
|
+
"to_version": "3.1.7",
|
|
7
|
+
"description": "Fixes template injection RCE vulnerability by sanitizing template inputs",
|
|
8
|
+
"diff": "--- a/package.json\n+++ b/package.json\n@@ -5,1 +5,1 @@\n- \"ejs\": \"3.1.6\"\n+ \"ejs\": \"3.1.7\"\n--- a/package-lock.json\n+++ b/package-lock.json\n@@ -10,1 +10,1 @@\n- \"version\": \"3.1.6\",\n+ \"version\": \"3.1.7\",",
|
|
9
|
+
"severity": "CRITICAL",
|
|
10
|
+
"cvss": 9.8
|
|
11
|
+
}
|
|
12
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { serve } from "bun";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
const distDir = "dist";
|
|
5
|
+
|
|
6
|
+
export default serve({
|
|
7
|
+
port: 5173,
|
|
8
|
+
fetch(req) {
|
|
9
|
+
const url = new URL(req.url);
|
|
10
|
+
let filePath = url.pathname;
|
|
11
|
+
|
|
12
|
+
if (filePath === "/" || filePath === "") {
|
|
13
|
+
filePath = "index.html";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const file = Bun.file(path.join(distDir, filePath));
|
|
17
|
+
return file.exists().then(() => new Response(file));
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
console.log("🎨 Dashboard serving on http://localhost:5173");
|
|
22
|
+
console.log("📡 API on http://localhost:3000");
|
|
23
|
+
console.log("📂 Serving: dist/");
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { createEngine } from "../engine/index";
|
|
2
|
+
import { Report } from "../shared/types";
|
|
3
|
+
|
|
4
|
+
// Environment validation
|
|
5
|
+
const GOOGLE_CLOUD_URL = process.env.GOOGLE_CLOUD_URL;
|
|
6
|
+
const API_SECRET_TOKEN = process.env.API_SECRET_TOKEN;
|
|
7
|
+
const PORT = parseInt(process.env.PORT || "8080");
|
|
8
|
+
const NODE_ENV = process.env.NODE_ENV || "development";
|
|
9
|
+
|
|
10
|
+
// Validate required environment variables
|
|
11
|
+
if (NODE_ENV === "production") {
|
|
12
|
+
if (!GOOGLE_CLOUD_URL) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"GOOGLE_CLOUD_URL environment variable is required in production"
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
if (!API_SECRET_TOKEN) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
"API_SECRET_TOKEN environment variable is required in production"
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Rate limiting: max 5 requests per minute per IP
|
|
25
|
+
interface RateLimitEntry {
|
|
26
|
+
count: number;
|
|
27
|
+
resetTime: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const rateLimitMap = new Map<string, RateLimitEntry>();
|
|
31
|
+
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
|
|
32
|
+
const RATE_LIMIT_MAX = 5;
|
|
33
|
+
|
|
34
|
+
function getClientIP(req: Request): string {
|
|
35
|
+
const forwarded = req.headers.get("x-forwarded-for");
|
|
36
|
+
if (forwarded) {
|
|
37
|
+
return forwarded.split(",")[0].trim();
|
|
38
|
+
}
|
|
39
|
+
// In production on Google Cloud, you should rely on x-forwarded-for
|
|
40
|
+
// For local testing, use localhost
|
|
41
|
+
return req.headers.get("cf-connecting-ip") || "127.0.0.1";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function checkRateLimit(ip: string): { allowed: boolean; remaining: number } {
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
const entry = rateLimitMap.get(ip);
|
|
47
|
+
|
|
48
|
+
if (!entry || now > entry.resetTime) {
|
|
49
|
+
// Window expired, create new entry
|
|
50
|
+
rateLimitMap.set(ip, { count: 1, resetTime: now + RATE_LIMIT_WINDOW });
|
|
51
|
+
return { allowed: true, remaining: RATE_LIMIT_MAX - 1 };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (entry.count >= RATE_LIMIT_MAX) {
|
|
55
|
+
return { allowed: false, remaining: 0 };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
entry.count++;
|
|
59
|
+
return { allowed: true, remaining: RATE_LIMIT_MAX - entry.count };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Type for scan request
|
|
63
|
+
interface ScanRequest {
|
|
64
|
+
dependencies?: Array<{ name: string; version: string }>;
|
|
65
|
+
repoPath?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface ScanResponse {
|
|
69
|
+
success: boolean;
|
|
70
|
+
data?: Report;
|
|
71
|
+
error?: string;
|
|
72
|
+
riskScore?: number;
|
|
73
|
+
executiveSummary?: {
|
|
74
|
+
totalCVEs: number;
|
|
75
|
+
exploitableCVEs: number;
|
|
76
|
+
theoreticalCVEs: number;
|
|
77
|
+
scanDurationMs: number;
|
|
78
|
+
timestamp: string;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function handleScan(
|
|
83
|
+
req: Request,
|
|
84
|
+
ip: string
|
|
85
|
+
): Promise<Response> {
|
|
86
|
+
try {
|
|
87
|
+
// Parse request body
|
|
88
|
+
const body = await req.json() as ScanRequest;
|
|
89
|
+
|
|
90
|
+
// Validate request
|
|
91
|
+
if (!body.repoPath && (!body.dependencies || body.dependencies.length === 0)) {
|
|
92
|
+
return new Response(
|
|
93
|
+
JSON.stringify({
|
|
94
|
+
success: false,
|
|
95
|
+
error:
|
|
96
|
+
"Request must include either repoPath or dependencies array",
|
|
97
|
+
}),
|
|
98
|
+
{
|
|
99
|
+
status: 400,
|
|
100
|
+
headers: { "Content-Type": "application/json" },
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const repoPath = body.repoPath || process.cwd();
|
|
106
|
+
|
|
107
|
+
// Log sponsor branding
|
|
108
|
+
console.log("[Bright Data] Scraping CVE data from NVD, Exploit-DB, Snyk...");
|
|
109
|
+
console.log("[Daytona] Sandboxing exploits for verification...");
|
|
110
|
+
console.log("[Nosana] Patching vulnerable dependencies...");
|
|
111
|
+
|
|
112
|
+
// Run scan
|
|
113
|
+
const engine = createEngine();
|
|
114
|
+
console.log(`\n🔍 Starting security scan for: ${repoPath}`);
|
|
115
|
+
const report = await engine.scan(repoPath);
|
|
116
|
+
|
|
117
|
+
const response: ScanResponse = {
|
|
118
|
+
success: true,
|
|
119
|
+
data: report,
|
|
120
|
+
riskScore: report.scan.risk_score,
|
|
121
|
+
executiveSummary: {
|
|
122
|
+
totalCVEs: report.summary.total_cves,
|
|
123
|
+
exploitableCVEs: report.summary.exploitable_count,
|
|
124
|
+
theoreticalCVEs: report.summary.theoretical_count,
|
|
125
|
+
scanDurationMs: report.summary.scan_duration_ms,
|
|
126
|
+
timestamp: report.scan.timestamp,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return new Response(JSON.stringify(response), {
|
|
131
|
+
status: 200,
|
|
132
|
+
headers: {
|
|
133
|
+
"Content-Type": "application/json",
|
|
134
|
+
"X-RateLimit-Remaining": String(rateLimitMap.get(ip)?.count || 0),
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error("Scan error:", error);
|
|
139
|
+
|
|
140
|
+
const errorMessage =
|
|
141
|
+
error instanceof Error ? error.message : String(error);
|
|
142
|
+
|
|
143
|
+
const errorResponse: ScanResponse = {
|
|
144
|
+
success: false,
|
|
145
|
+
error: errorMessage,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return new Response(JSON.stringify(errorResponse), {
|
|
149
|
+
status: 500,
|
|
150
|
+
headers: { "Content-Type": "application/json" },
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function handleHealth(): Promise<Response> {
|
|
156
|
+
return new Response(JSON.stringify({ status: "ok" }), {
|
|
157
|
+
status: 200,
|
|
158
|
+
headers: { "Content-Type": "application/json" },
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Main server
|
|
163
|
+
const server = Bun.serve({
|
|
164
|
+
port: PORT,
|
|
165
|
+
development: NODE_ENV !== "production",
|
|
166
|
+
async fetch(req) {
|
|
167
|
+
const url = new URL(req.url);
|
|
168
|
+
const pathname = url.pathname;
|
|
169
|
+
const method = req.method;
|
|
170
|
+
const ip = getClientIP(req);
|
|
171
|
+
|
|
172
|
+
// Health check
|
|
173
|
+
if (pathname === "/health" && method === "GET") {
|
|
174
|
+
return handleHealth();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Scan endpoint
|
|
178
|
+
if (pathname === "/api/scan" && method === "POST") {
|
|
179
|
+
// Rate limiting check
|
|
180
|
+
const rateLimit = checkRateLimit(ip);
|
|
181
|
+
if (!rateLimit.allowed) {
|
|
182
|
+
console.warn(`Rate limit exceeded for IP: ${ip}`);
|
|
183
|
+
return new Response(
|
|
184
|
+
JSON.stringify({
|
|
185
|
+
success: false,
|
|
186
|
+
error: "Rate limit exceeded. Maximum 5 requests per minute.",
|
|
187
|
+
}),
|
|
188
|
+
{
|
|
189
|
+
status: 429,
|
|
190
|
+
headers: {
|
|
191
|
+
"Content-Type": "application/json",
|
|
192
|
+
"Retry-After": "60",
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Token validation in production
|
|
199
|
+
if (NODE_ENV === "production") {
|
|
200
|
+
const authHeader = req.headers.get("Authorization") || "";
|
|
201
|
+
const token = authHeader.replace("Bearer ", "");
|
|
202
|
+
|
|
203
|
+
if (token !== API_SECRET_TOKEN) {
|
|
204
|
+
console.warn(`Invalid API token from IP: ${ip}`);
|
|
205
|
+
return new Response(
|
|
206
|
+
JSON.stringify({
|
|
207
|
+
success: false,
|
|
208
|
+
error: "Invalid or missing API token",
|
|
209
|
+
}),
|
|
210
|
+
{
|
|
211
|
+
status: 401,
|
|
212
|
+
headers: { "Content-Type": "application/json" },
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Handle scan
|
|
219
|
+
return handleScan(req, ip);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 404 for unknown routes
|
|
223
|
+
return new Response(
|
|
224
|
+
JSON.stringify({
|
|
225
|
+
success: false,
|
|
226
|
+
error: `Unknown route: ${pathname}`,
|
|
227
|
+
}),
|
|
228
|
+
{
|
|
229
|
+
status: 404,
|
|
230
|
+
headers: { "Content-Type": "application/json" },
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Log startup information
|
|
237
|
+
console.log("\n╔════════════════════════════════════════════════════════╗");
|
|
238
|
+
console.log("║ CodeProbe Security Scan API Server (CLI-only) ║");
|
|
239
|
+
console.log("╚════════════════════════════════════════════════════════╝\n");
|
|
240
|
+
console.log(`📍 Environment: ${NODE_ENV}`);
|
|
241
|
+
console.log(`🚀 Server running on http://localhost:${PORT}`);
|
|
242
|
+
console.log(`\n📊 Available endpoints:`);
|
|
243
|
+
console.log(` GET /health - Health check`);
|
|
244
|
+
console.log(` POST /api/scan - Run vulnerability scan\n`);
|
|
245
|
+
|
|
246
|
+
if (NODE_ENV === "production") {
|
|
247
|
+
console.log(`✅ Production mode enabled`);
|
|
248
|
+
console.log(` - Google Cloud URL configured: ${GOOGLE_CLOUD_URL}`);
|
|
249
|
+
console.log(` - API authentication required (Bearer token)\n`);
|
|
250
|
+
} else {
|
|
251
|
+
console.log(`⚠️ Development mode - authentication disabled\n`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(`📈 Rate limiting: 5 requests per minute per IP`);
|
|
255
|
+
console.log(
|
|
256
|
+
`\n💡 Example request:\n`
|
|
257
|
+
);
|
|
258
|
+
console.log(` curl -X POST http://localhost:${PORT}/api/scan \\`);
|
|
259
|
+
console.log(` -H "Content-Type: application/json" \\`);
|
|
260
|
+
if (NODE_ENV === "production") {
|
|
261
|
+
console.log(` -H "Authorization: Bearer $API_SECRET_TOKEN" \\`);
|
|
262
|
+
}
|
|
263
|
+
console.log(` -d '{"repoPath": "/path/to/repo"}'\n`);
|
|
264
|
+
|
|
265
|
+
console.log(`🔗 Sponsor credits:`);
|
|
266
|
+
console.log(` [Bright Data] - CVE data scraping from NVD, Exploit-DB, Snyk`);
|
|
267
|
+
console.log(` [Daytona] - Sandboxed exploit verification`);
|
|
268
|
+
console.log(` [Nosana] - LLM-powered patch generation\n`);
|
|
269
|
+
|
|
270
|
+
export default server;
|