From 85372c2b1f98962c2b0341726cb7247254556c9a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 9 Jul 2025 13:28:01 +0000
Subject: [PATCH 1/3] Initial plan
From 7ff19add35057ccec62e3bc8a0995613ca250b06 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 9 Jul 2025 13:43:58 +0000
Subject: [PATCH 2/3] Phase 1 Complete: Removed network restrictions and added
bug bounty infrastructure
Co-authored-by: drzarak <36883503+drzarak@users.noreply.github.com>
---
README.md | 4 +-
codex-cli/Dockerfile | 56 +-
codex-cli/build.mjs | 7 +
codex-cli/package-lock.json | 1063 ++++++++++++++++++++-
codex-cli/package.json | 6 +-
codex-cli/scripts/auto-restart.sh | 46 +
codex-cli/scripts/init_firewall.sh | 106 +-
codex-cli/src/cli.tsx | 168 +++-
codex-cli/src/utils/browser-automation.ts | 442 +++++++++
codex-cli/src/utils/bug-bounty-scanner.ts | 478 +++++++++
codex-cli/src/utils/database.ts | 345 +++++++
codex-cli/src/utils/web-server.ts | 704 ++++++++++++++
12 files changed, 3280 insertions(+), 145 deletions(-)
create mode 100644 codex-cli/scripts/auto-restart.sh
create mode 100644 codex-cli/src/utils/browser-automation.ts
create mode 100644 codex-cli/src/utils/bug-bounty-scanner.ts
create mode 100644 codex-cli/src/utils/database.ts
create mode 100644 codex-cli/src/utils/web-server.ts
diff --git a/README.md b/README.md
index 7f9d0245ef5..0e4b71a3a53 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
OpenAI Codex CLI
-Lightweight coding agent that runs in your terminal
+AI Bug Bounty Hunter
+Comprehensive AI-powered bug bounty tool with web interface and automated scanning
npm i -g @openai/codex
diff --git a/codex-cli/Dockerfile b/codex-cli/Dockerfile
index 95fe3800ec2..786a29d95ea 100644
--- a/codex-cli/Dockerfile
+++ b/codex-cli/Dockerfile
@@ -3,29 +3,58 @@ FROM node:20
ARG TZ
ENV TZ="$TZ"
-# Install basic development tools and iptables/ipset
+# Install bug bounty tools and dependencies
RUN apt update && apt install -y \
aggregate \
+ curl \
dnsutils \
fzf \
gh \
git \
gnupg2 \
+ golang-go \
iproute2 \
ipset \
iptables \
jq \
less \
man-db \
+ nmap \
procps \
+ python3 \
+ python3-pip \
+ sqlmap \
sudo \
unzip \
ripgrep \
- zsh
+ wget \
+ zsh
-# Ensure default node user has access to /usr/local/share
+# Install Go-based bug bounty tools
+ENV GOPATH=/opt/go
+ENV PATH=$PATH:/opt/go/bin:/usr/local/go/bin
+RUN mkdir -p /opt/go && \
+ go install -v github.com/owasp-amass/amass/v4/...@master && \
+ go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest && \
+ go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest && \
+ go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest && \
+ go install -v github.com/projectdiscovery/naabu/v2/cmd/naabu@latest && \
+ go install -v github.com/projectdiscovery/katana/cmd/katana@latest
+
+# Install Python-based security tools
+RUN pip3 install \
+ dirsearch \
+ whatweb \
+ xsstrike \
+ commix
+
+# Install Playwright dependencies for browser automation
+RUN npx playwright install --with-deps chromium
+
+# Ensure default node user has access to /usr/local/share and Go tools
RUN mkdir -p /usr/local/share/npm-global && \
- chown -R node:node /usr/local/share
+ chown -R node:node /usr/local/share && \
+ chown -R node:node /opt/go
ARG USERNAME=node
@@ -34,16 +63,31 @@ USER node
# Install global packages
ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global
-ENV PATH=$PATH:/usr/local/share/npm-global/bin
+ENV PATH=$PATH:/usr/local/share/npm-global/bin:/opt/go/bin
# Install codex
COPY dist/codex.tgz codex.tgz
RUN npm install -g codex.tgz
-# Copy and set up firewall script
+# Copy and set up network configuration script (now allows full access)
COPY scripts/init_firewall.sh /usr/local/bin/
USER root
RUN chmod +x /usr/local/bin/init_firewall.sh && \
echo "node ALL=(root) NOPASSWD: /usr/local/bin/init_firewall.sh" > /etc/sudoers.d/node-firewall && \
chmod 0440 /etc/sudoers.d/node-firewall
+
+# Set up auto-restart capability
+COPY scripts/auto-restart.sh /usr/local/bin/
+RUN chmod +x /usr/local/bin/auto-restart.sh && \
+ echo "node ALL=(root) NOPASSWD: /usr/local/bin/auto-restart.sh" >> /etc/sudoers.d/node-firewall
+
USER node
+
+# Create necessary directories for bug bounty operations
+RUN mkdir -p /home/node/.codex/tools /home/node/.codex/results /home/node/.codex/browser_data
+
+# Expose port 222 for web interface
+EXPOSE 222
+
+# Default command starts the bug bounty web server
+CMD ["codex", "bugbounty", "server"]
diff --git a/codex-cli/build.mjs b/codex-cli/build.mjs
index 0fcd5c6eced..1e449ca584a 100644
--- a/codex-cli/build.mjs
+++ b/codex-cli/build.mjs
@@ -74,5 +74,12 @@ esbuild
sourcemap: isDevBuild ? "inline" : true,
plugins,
inject: ["./require-shim.js"],
+ external: [
+ "playwright",
+ "better-sqlite3",
+ "chromium-bidi",
+ "express",
+ "ws"
+ ],
})
.catch(() => process.exit(1));
diff --git a/codex-cli/package-lock.json b/codex-cli/package-lock.json
index 241a96a5da3..67e7cce048f 100644
--- a/codex-cli/package-lock.json
+++ b/codex-cli/package-lock.json
@@ -10,9 +10,11 @@
"license": "Apache-2.0",
"dependencies": {
"@inkjs/ui": "^2.0.0",
+ "better-sqlite3": "^11.6.0",
"chalk": "^5.2.0",
"diff": "^7.0.0",
"dotenv": "^16.1.4",
+ "express": "^4.18.2",
"file-type": "^20.1.0",
"ink": "^5.2.0",
"ink-select-input": "^6.0.0",
@@ -21,9 +23,11 @@
"meow": "^13.2.0",
"open": "^10.1.0",
"openai": "^4.89.0",
+ "playwright": "^1.50.0",
"react": "^18.2.0",
"shell-quote": "^1.8.2",
- "use-interval": "1.4.0"
+ "use-interval": "1.4.0",
+ "ws": "^8.18.0"
},
"bin": {
"codex": "dist/cli.js"
@@ -1459,6 +1463,19 @@
"node": ">=6.5"
}
},
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/acorn": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
@@ -1591,6 +1608,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
"node_modules/array-includes": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz",
@@ -1789,6 +1812,96 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/better-sqlite3": {
+ "version": "11.10.0",
+ "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
+ "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "bindings": "^1.5.0",
+ "prebuild-install": "^7.1.1"
+ }
+ },
+ "node_modules/bindings": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "file-uri-to-path": "1.0.0"
+ }
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.3",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.13.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/body-parser/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/body-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@@ -1810,6 +1923,30 @@
"node": ">=8"
}
},
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
"node_modules/bundle-name": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
@@ -1824,6 +1961,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/cac": {
"version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@@ -1867,7 +2013,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "dev": true,
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
@@ -1933,6 +2078,12 @@
"node": ">= 16"
}
},
+ "node_modules/chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "license": "ISC"
+ },
"node_modules/cli-boxes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
@@ -2198,6 +2349,27 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/convert-to-spaces": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz",
@@ -2206,6 +2378,21 @@
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
+ "node_modules/cookie": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "license": "MIT"
+ },
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
@@ -2300,6 +2487,21 @@
}
}
},
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/deep-eql": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
@@ -2309,6 +2511,15 @@
"node": ">=6"
}
},
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -2403,6 +2614,34 @@
"node": ">=0.4.0"
}
},
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
+ "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/diff": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
@@ -2460,6 +2699,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
"node_modules/emoji-regex": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
@@ -2470,6 +2715,24 @@
"resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz",
"integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw=="
},
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
"node_modules/environment": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
@@ -2703,6 +2966,12 @@
"node": ">=6"
}
},
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -3184,6 +3453,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
@@ -3192,6 +3470,15 @@
"node": ">=6"
}
},
+ "node_modules/expand-template": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
+ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+ "license": "(MIT OR WTFPL)",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/expect-type": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz",
@@ -3201,6 +3488,67 @@
"node": ">=12.0.0"
}
},
+ "node_modules/express": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.3",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.7.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.3.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.13.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.19.0",
+ "serve-static": "1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3308,6 +3656,12 @@
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
+ "node_modules/file-uri-to-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+ "license": "MIT"
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -3320,6 +3674,39 @@
"node": ">=8"
}
},
+ "node_modules/finalhandler": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -3393,25 +3780,6 @@
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
},
- "node_modules/form-data/node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/form-data/node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/formdata-node": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
@@ -3424,6 +3792,30 @@
"node": ">= 12.20"
}
},
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "license": "MIT"
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -3553,6 +3945,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/github-from-package": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
+ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+ "license": "MIT"
+ },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -3772,6 +4170,22 @@
"node": "*"
}
},
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
@@ -3780,6 +4194,18 @@
"ms": "^2.0.0"
}
},
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -3861,9 +4287,13 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true,
- "peer": true
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "license": "ISC"
},
"node_modules/ink": {
"version": "5.2.0",
@@ -3971,6 +4401,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -4636,6 +5075,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/meow": {
"version": "13.2.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
@@ -4647,6 +5095,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -4656,6 +5113,15 @@
"node": ">= 8"
}
},
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -4669,14 +5135,59 @@
"node": ">=8.6"
}
},
- "node_modules/mimic-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"engines": {
"node": ">=6"
}
},
+ "node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -4696,11 +5207,16 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/mkdirp-classic": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -4734,12 +5250,39 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/napi-build-utils": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
+ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
+ "license": "MIT"
+ },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-abi": {
+ "version": "3.75.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz",
+ "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==",
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
@@ -4822,7 +5365,6 @@
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
- "dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -4924,12 +5466,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
- "peer": true,
"dependencies": {
"wrappy": "1"
}
@@ -5105,6 +5657,15 @@
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
},
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/patch-console": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz",
@@ -5149,6 +5710,12 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "license": "MIT"
+ },
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -5203,6 +5770,50 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/playwright": {
+ "version": "1.53.2",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz",
+ "integrity": "sha512-6K/qQxVFuVQhRQhFsVZ9fGeatxirtrpPgxzBYWyZLEXJzqYwuL4fuNmfOfD5et1tJE4GScKyPNeLhZeRwuTU3A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.53.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.53.2",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.2.tgz",
+ "integrity": "sha512-ox/OytMy+2w1jcYEYlOo1Hhp8hZkLCximMTUTMBXjGUA1KoFfiSZ+DU+3a739jsPY0yoKH2TFy9S2fsJas8yAw==",
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -5240,6 +5851,32 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/prebuild-install": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
+ "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.0",
+ "expand-template": "^2.0.3",
+ "github-from-package": "0.0.0",
+ "minimist": "^1.2.3",
+ "mkdirp-classic": "^0.5.3",
+ "napi-build-utils": "^2.0.0",
+ "node-abi": "^3.3.0",
+ "pump": "^3.0.0",
+ "rc": "^1.2.7",
+ "simple-get": "^4.0.0",
+ "tar-fs": "^2.0.0",
+ "tunnel-agent": "^0.6.0"
+ },
+ "bin": {
+ "prebuild-install": "bin.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -5276,6 +5913,29 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/pump": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
+ "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -5285,6 +5945,21 @@
"node": ">=6"
}
},
+ "node_modules/qs": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -5305,6 +5980,54 @@
}
]
},
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
+ "node_modules/rc/node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
@@ -5337,6 +6060,20 @@
"react": "^18.3.1"
}
},
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -5551,6 +6288,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/safe-push-apply": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
@@ -5584,6 +6341,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@@ -5596,7 +6359,6 @@
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
- "dev": true,
"bin": {
"semver": "bin/semver.js"
},
@@ -5604,6 +6366,69 @@
"node": ">=10"
}
},
+ "node_modules/send": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/send/node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/send/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.19.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -5650,6 +6475,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -5688,7 +6519,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "dev": true,
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
@@ -5707,7 +6537,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
- "dev": true,
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
@@ -5723,7 +6552,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "dev": true,
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
@@ -5741,7 +6569,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "dev": true,
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
@@ -5767,6 +6594,51 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
+ "node_modules/simple-concat": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/simple-get": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
+ "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decompress-response": "^6.0.0",
+ "once": "^1.3.1",
+ "simple-concat": "^1.0.0"
+ }
+ },
"node_modules/skin-tone": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz",
@@ -5850,12 +6722,30 @@
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
"dev": true
},
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/std-env": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
"integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
"dev": true
},
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
"node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
@@ -6074,6 +6964,34 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tar-fs": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz",
+ "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==",
+ "license": "MIT",
+ "dependencies": {
+ "chownr": "^1.1.1",
+ "mkdirp-classic": "^0.5.2",
+ "pump": "^3.0.0",
+ "tar-stream": "^2.1.4"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -6162,6 +7080,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
"node_modules/token-types": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.0.tgz",
@@ -6266,6 +7193,18 @@
"strip-bom": "^3.0.0"
}
},
+ "node_modules/tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -6292,6 +7231,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/typed-array-buffer": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
@@ -6421,6 +7373,15 @@
"node": ">=4"
}
},
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -6443,12 +7404,36 @@
"react": ">=16.8.0 || ^17"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true
},
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/vite": {
"version": "6.2.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz",
@@ -6815,9 +7800,7 @@
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true,
- "peer": true
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.18.1",
diff --git a/codex-cli/package.json b/codex-cli/package.json
index c5e0b9dfbe1..d0ec64e6a94 100644
--- a/codex-cli/package.json
+++ b/codex-cli/package.json
@@ -31,9 +31,11 @@
],
"dependencies": {
"@inkjs/ui": "^2.0.0",
+ "better-sqlite3": "^11.6.0",
"chalk": "^5.2.0",
"diff": "^7.0.0",
"dotenv": "^16.1.4",
+ "express": "^4.18.2",
"file-type": "^20.1.0",
"ink": "^5.2.0",
"ink-select-input": "^6.0.0",
@@ -42,9 +44,11 @@
"meow": "^13.2.0",
"open": "^10.1.0",
"openai": "^4.89.0",
+ "playwright": "^1.50.0",
"react": "^18.2.0",
"shell-quote": "^1.8.2",
- "use-interval": "1.4.0"
+ "use-interval": "1.4.0",
+ "ws": "^8.18.0"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
diff --git a/codex-cli/scripts/auto-restart.sh b/codex-cli/scripts/auto-restart.sh
new file mode 100644
index 00000000000..ab58cb85555
--- /dev/null
+++ b/codex-cli/scripts/auto-restart.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+set -euo pipefail
+
+echo "🔄 Setting up auto-restart for AI Bug Bounty Hunter..."
+
+# Create systemd service for Docker auto-restart
+cat > /etc/systemd/system/bugbounty-hunter.service << 'EOF'
+[Unit]
+Description=AI Bug Bounty Hunter Docker Container
+Wants=docker.service
+After=docker.service
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/usr/bin/docker run --name bugbounty-hunter -d \
+ --restart=unless-stopped \
+ -p 222:222 \
+ -e OPENAI_API_KEY=${OPENAI_API_KEY} \
+ --cap-add=NET_ADMIN \
+ --cap-add=NET_RAW \
+ -v /opt/bugbounty-data:/home/node/.codex \
+ codex:latest
+ExecStop=/usr/bin/docker stop bugbounty-hunter
+ExecStopPost=/usr/bin/docker rm -f bugbounty-hunter
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+# Create data directory for persistence
+mkdir -p /opt/bugbounty-data
+chown -R 1000:1000 /opt/bugbounty-data
+
+# Enable the service
+systemctl enable bugbounty-hunter.service
+
+echo "✅ Auto-restart service configured"
+echo "📋 To manage the service:"
+echo " Start: sudo systemctl start bugbounty-hunter"
+echo " Stop: sudo systemctl stop bugbounty-hunter"
+echo " Status: sudo systemctl status bugbounty-hunter"
+echo " Logs: sudo journalctl -u bugbounty-hunter -f"
+echo ""
+echo "🌐 Web interface will be available at http://localhost:222"
+echo "💾 Data will persist in /opt/bugbounty-data"
\ No newline at end of file
diff --git a/codex-cli/scripts/init_firewall.sh b/codex-cli/scripts/init_firewall.sh
index 6e0fa438b4f..98a26e25ec8 100644
--- a/codex-cli/scripts/init_firewall.sh
+++ b/codex-cli/scripts/init_firewall.sh
@@ -2,7 +2,9 @@
set -euo pipefail # Exit on error, undefined vars, and pipeline failures
IFS=$'\n\t' # Stricter word splitting
-# Flush existing rules and delete existing ipsets
+echo "Configuring network access for bug bounty operations..."
+
+# Flush existing restrictive rules and delete existing ipsets
iptables -F
iptables -X
iptables -t nat -F
@@ -11,86 +13,40 @@ iptables -t mangle -F
iptables -t mangle -X
ipset destroy allowed-domains 2>/dev/null || true
-# First allow DNS and localhost before any restrictions
-# Allow outbound DNS
-iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
-# Allow inbound DNS responses
-iptables -A INPUT -p udp --sport 53 -j ACCEPT
-# Allow localhost
-iptables -A INPUT -i lo -j ACCEPT
-iptables -A OUTPUT -o lo -j ACCEPT
-
-# Create ipset with CIDR support
-ipset create allowed-domains hash:net
-
-# Resolve and add other allowed domains
-for domain in \
- "api.openai.com"; do
- echo "Resolving $domain..."
- ips=$(dig +short A "$domain")
- if [ -z "$ips" ]; then
- echo "ERROR: Failed to resolve $domain"
- exit 1
- fi
-
- while read -r ip; do
- if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
- echo "ERROR: Invalid IP from DNS for $domain: $ip"
- exit 1
- fi
- echo "Adding $ip for $domain"
- ipset add allowed-domains "$ip"
- done < <(echo "$ips")
-done
-
-# Get host IP from default route
-HOST_IP=$(ip route | grep default | cut -d" " -f3)
-if [ -z "$HOST_IP" ]; then
- echo "ERROR: Failed to detect host IP"
- exit 1
-fi
-
-HOST_NETWORK=$(echo "$HOST_IP" | sed "s/\.[0-9]*$/.0\/24/")
-echo "Host network detected as: $HOST_NETWORK"
-
-# Set up remaining iptables rules
-iptables -A INPUT -s "$HOST_NETWORK" -j ACCEPT
-iptables -A OUTPUT -d "$HOST_NETWORK" -j ACCEPT
-
-# Set default policies to DROP first
-iptables -P INPUT DROP
-iptables -P FORWARD DROP
-iptables -P OUTPUT DROP
-
-# First allow established connections for already approved traffic
-iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
+# Set permissive default policies to allow all network traffic
+iptables -P INPUT ACCEPT
+iptables -P FORWARD ACCEPT
+iptables -P OUTPUT ACCEPT
-# Then allow only specific outbound traffic to allowed domains
-iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT
+# Allow all inbound and outbound traffic (removing network jail)
+iptables -A INPUT -j ACCEPT
+iptables -A OUTPUT -j ACCEPT
+iptables -A FORWARD -j ACCEPT
-# Append final REJECT rules for immediate error responses
-# For TCP traffic, send a TCP reset; for UDP, send ICMP port unreachable.
-iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset
-iptables -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
-iptables -A OUTPUT -p tcp -j REJECT --reject-with tcp-reset
-iptables -A OUTPUT -p udp -j REJECT --reject-with icmp-port-unreachable
-iptables -A FORWARD -p tcp -j REJECT --reject-with tcp-reset
-iptables -A FORWARD -p udp -j REJECT --reject-with icmp-port-unreachable
+echo "Network configuration complete - full internet access enabled"
+echo "Verifying network access..."
-echo "Firewall configuration complete"
-echo "Verifying firewall rules..."
+# Verify general internet access
if curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then
- echo "ERROR: Firewall verification failed - was able to reach https://example.com"
- exit 1
+ echo "SUCCESS: Full internet access confirmed - can reach https://example.com"
else
- echo "Firewall verification passed - unable to reach https://example.com as expected"
+ echo "WARNING: Unable to reach https://example.com - check network connectivity"
fi
-# Verify OpenAI API access
-if ! curl --connect-timeout 5 https://api.openai.com >/dev/null 2>&1; then
- echo "ERROR: Firewall verification failed - unable to reach https://api.openai.com"
- exit 1
+# Verify OpenAI API access is still working
+if curl --connect-timeout 5 https://api.openai.com >/dev/null 2>&1; then
+ echo "SUCCESS: OpenAI API access confirmed"
else
- echo "Firewall verification passed - able to reach https://api.openai.com as expected"
+ echo "WARNING: Unable to reach OpenAI API - check connectivity"
fi
+
+# Verify access to common bug bounty tool services
+for service in "github.com" "shodan.io" "crt.sh"; do
+ if curl --connect-timeout 5 "https://$service" >/dev/null 2>&1; then
+ echo "SUCCESS: $service is accessible"
+ else
+ echo "WARNING: Unable to reach $service"
+ fi
+done
+
+echo "Bug bounty network configuration complete"
diff --git a/codex-cli/src/cli.tsx b/codex-cli/src/cli.tsx
index da6a465a828..e70fc136c8a 100644
--- a/codex-cli/src/cli.tsx
+++ b/codex-cli/src/cli.tsx
@@ -29,6 +29,10 @@ import {
} from "./utils/model-utils.js";
import { parseToolCall } from "./utils/parsers";
import { onExit, setInkRenderer } from "./utils/terminal";
+import { BugBountyWebServer } from "./utils/web-server.js";
+import { BugBountyScanner } from "./utils/bug-bounty-scanner.js";
+import { BrowserAutomationService } from "./utils/browser-automation.js";
+import { addTarget, getTargets } from "./utils/database.js";
import chalk from "chalk";
import { spawnSync } from "child_process";
import fs from "fs";
@@ -41,16 +45,12 @@ import React from "react";
// immediately. This must be run with DEBUG=1 for logging to work.
initLogger();
-// TODO: migrate to new versions of quiet mode
-//
-// -q, --quiet Non-interactive quiet mode that only prints final message
-// -j, --json Non-interactive JSON output mode that prints JSON messages
-
const cli = meow(
`
Usage
$ codex [options]
$ codex completion
+ $ codex bugbounty [subcommand]
Options
-h, --help Show usage and exit
@@ -62,12 +62,22 @@ const cli = meow(
-a, --approval-mode Override the approval policy: 'suggest', 'auto-edit', or 'full-auto'
--auto-edit Automatically approve file edits; still prompt for commands
- --full-auto Automatically approve edits and commands when executed in the sandbox
+ --full-auto Automatically approve edits and commands when executed in sandbox
--no-project-doc Do not automatically include the repository's 'codex.md'
--project-doc Include an additional markdown file at as context
--full-stdout Do not truncate stdout/stderr from command outputs
+ Bug Bounty Mode
+ $ codex bugbounty server Start web interface on port 222
+ $ codex bugbounty add [description]
+ Add a new target for scanning
+ $ codex bugbounty scan
+ Start comprehensive scan on target
+ $ codex bugbounty auth
+ Set up authentication for target
+ $ codex bugbounty list List all targets and their status
+
Dangerous options
--dangerously-auto-approve-everything
Skip all confirmation prompts and execute commands without
@@ -81,6 +91,9 @@ const cli = meow(
Examples
$ codex "Write and run a python program that prints ASCII art"
$ codex -q "fix build issues"
+ $ codex bugbounty server
+ $ codex bugbounty add example.com "Test target for bug bounty"
+ $ codex bugbounty scan example.com
$ codex completion bash
`,
{
@@ -114,7 +127,7 @@ const cli = meow(
fullAuto: {
type: "boolean",
description:
- "Automatically run commands in a sandbox; only prompt for failures.",
+ "Automatically run commands without sandbox restrictions for bug bounty operations.",
},
approvalMode: {
type: "string",
@@ -136,9 +149,6 @@ const cli = meow(
"Disable truncation of command stdout/stderr messages (show everything)",
aliases: ["no-truncate"],
},
-
- // Experimental mode where whole directory is loaded in context and model is requested
- // to make code edits in a single pass.
fullContext: {
type: "boolean",
aliases: ["f"],
@@ -180,6 +190,132 @@ complete -c codex -a '(_fish_complete_path)' -d 'file path'`,
console.log(script);
process.exit(0);
}
+
+// Handle bug bounty subcommands
+if (cli.input[0] === "bugbounty") {
+ const subcommand = cli.input[1];
+
+ switch (subcommand) {
+ case "server":
+ console.log(chalk.cyan("🚀 Starting AI Bug Bounty Hunter web interface..."));
+ const webServer = new BugBountyWebServer(222);
+ await webServer.start();
+ console.log(chalk.green(`✅ Web interface available at http://localhost:222`));
+ console.log(chalk.yellow("Press Ctrl+C to stop the server"));
+
+ // Keep the process alive
+ process.on("SIGINT", async () => {
+ console.log(chalk.yellow("\n🛑 Shutting down server..."));
+ await webServer.stop();
+ process.exit(0);
+ });
+
+ // Keep alive
+ await new Promise(() => {});
+ break;
+
+ case "add":
+ const domain = cli.input[2];
+ const description = cli.input[3] || "";
+
+ if (!domain) {
+ console.error(chalk.red("❌ Domain is required"));
+ console.log("Usage: codex bugbounty add [description]");
+ process.exit(1);
+ }
+
+ try {
+ const targetId = addTarget({ domain, description, status: "active" });
+ console.log(chalk.green(`✅ Added target: ${domain} (ID: ${targetId})`));
+ } catch (error) {
+ console.error(chalk.red(`❌ Failed to add target: ${error}`));
+ process.exit(1);
+ }
+ break;
+
+ case "scan":
+ const scanDomain = cli.input[2];
+
+ if (!scanDomain) {
+ console.error(chalk.red("❌ Domain is required"));
+ console.log("Usage: codex bugbounty scan ");
+ process.exit(1);
+ }
+
+ const targets = getTargets();
+ const target = targets.find(t => t.domain === scanDomain);
+
+ if (!target) {
+ console.error(chalk.red(`❌ Target ${scanDomain} not found. Add it first with: codex bugbounty add ${scanDomain}`));
+ process.exit(1);
+ }
+
+ console.log(chalk.cyan(`🔍 Starting comprehensive scan for ${scanDomain}...`));
+ const scanner = new BugBountyScanner();
+ await scanner.startComprehensiveScan(target.id!);
+ console.log(chalk.green(`✅ Scan completed for ${scanDomain}`));
+ break;
+
+ case "auth":
+ const authDomain = cli.input[2];
+ const loginUrl = cli.input[3];
+
+ if (!authDomain || !loginUrl) {
+ console.error(chalk.red("❌ Domain and login URL are required"));
+ console.log("Usage: codex bugbounty auth ");
+ process.exit(1);
+ }
+
+ const authTargets = getTargets();
+ const authTarget = authTargets.find(t => t.domain === authDomain);
+
+ if (!authTarget) {
+ console.error(chalk.red(`❌ Target ${authDomain} not found. Add it first.`));
+ process.exit(1);
+ }
+
+ console.log(chalk.cyan(`🔐 Setting up authentication for ${authDomain}...`));
+ const browser = new BrowserAutomationService();
+ const result = await browser.navigateAndLogin(authTarget.id!, loginUrl);
+
+ if (result.success) {
+ console.log(chalk.green(`✅ Authentication setup completed for ${authDomain}`));
+ } else {
+ console.error(chalk.red(`❌ Authentication failed: ${result.error}`));
+ }
+
+ await browser.closeBrowser();
+ break;
+
+ case "list":
+ const allTargets = getTargets();
+
+ if (allTargets.length === 0) {
+ console.log(chalk.yellow("📋 No targets found. Add targets with: codex bugbounty add "));
+ } else {
+ console.log(chalk.cyan("📋 Bug Bounty Targets:"));
+ console.log();
+
+ for (const target of allTargets) {
+ console.log(chalk.bold(`${target.domain} (ID: ${target.id})`));
+ console.log(` Status: ${target.status}`);
+ console.log(` Description: ${target.description || "None"}`);
+ console.log(` Created: ${new Date(target.created_at!).toLocaleString()}`);
+ console.log(` Auth Cookies: ${Object.keys(target.auth_cookies || {}).length > 0 ? "✅" : "❌"}`);
+ console.log();
+ }
+ }
+ break;
+
+ default:
+ console.error(chalk.red(`❌ Unknown bug bounty subcommand: ${subcommand}`));
+ console.log("Available subcommands: server, add, scan, auth, list");
+ process.exit(1);
+ }
+
+ process.exit(0);
+}
+
// Show help if requested
if (cli.flags.help) {
cli.showHelp();
@@ -305,17 +441,7 @@ if (quietMode) {
}
// Default to the "suggest" policy.
-// Determine the approval policy to use in interactive mode.
-//
-// Priority (highest → lowest):
-// 1. --fullAuto – run everything automatically in a sandbox.
-// 2. --dangerouslyAutoApproveEverything – run everything **without** a sandbox
-// or prompts. This is intended for completely trusted environments. Since
-// it is more dangerous than --fullAuto we deliberately give it lower
-// priority so a user specifying both flags still gets the safer behaviour.
-// 3. --autoEdit – automatically approve edits, but prompt for commands.
-// 4. Default – suggest mode (prompt for everything).
-
+// Modified approval policy for bug bounty operations to allow more automation
const approvalPolicy: ApprovalPolicy =
cli.flags.fullAuto || cli.flags.approvalMode === "full-auto"
? AutoApprovalMode.FULL_AUTO
diff --git a/codex-cli/src/utils/browser-automation.ts b/codex-cli/src/utils/browser-automation.ts
new file mode 100644
index 00000000000..8b3c6b0f7c2
--- /dev/null
+++ b/codex-cli/src/utils/browser-automation.ts
@@ -0,0 +1,442 @@
+import { chromium, Browser, Page, BrowserContext } from "playwright";
+import { writeFileSync, readFileSync, existsSync } from "fs";
+import { join } from "path";
+import { CONFIG_DIR } from "./config.js";
+import { updateTarget, getTarget } from "./database.js";
+
+const BROWSER_DATA_DIR = join(CONFIG_DIR, "browser_data");
+const PROXY_LOG_FILE = join(CONFIG_DIR, "proxy_traffic.json");
+
+export interface ProxyLogEntry {
+ timestamp: string;
+ method: string;
+ url: string;
+ headers: Record;
+ body?: string;
+ response_status?: number;
+ response_headers?: Record;
+ response_body?: string;
+}
+
+export class BrowserAutomationService {
+ private browser: Browser | null = null;
+ private context: BrowserContext | null = null;
+ private proxyLogs: ProxyLogEntry[] = [];
+
+ constructor() {
+ this.ensureBrowserDataDir();
+ this.loadExistingProxyLogs();
+ }
+
+ private ensureBrowserDataDir(): void {
+ if (!existsSync(BROWSER_DATA_DIR)) {
+ require("fs").mkdirSync(BROWSER_DATA_DIR, { recursive: true });
+ }
+ }
+
+ private loadExistingProxyLogs(): void {
+ if (existsSync(PROXY_LOG_FILE)) {
+ try {
+ const data = readFileSync(PROXY_LOG_FILE, "utf8");
+ this.proxyLogs = JSON.parse(data);
+ } catch (error) {
+ console.error("Failed to load existing proxy logs:", error);
+ this.proxyLogs = [];
+ }
+ }
+ }
+
+ private saveProxyLogs(): void {
+ try {
+ writeFileSync(PROXY_LOG_FILE, JSON.stringify(this.proxyLogs, null, 2));
+ } catch (error) {
+ console.error("Failed to save proxy logs:", error);
+ }
+ }
+
+ public async initializeBrowser(): Promise {
+ if (this.browser) {
+ await this.closeBrowser();
+ }
+
+ console.log("🌐 Initializing browser for authentication and proxy logging...");
+
+ this.browser = await chromium.launch({
+ headless: false, // Keep visible for user interaction
+ args: [
+ "--disable-web-security",
+ "--disable-features=VizDisplayCompositor",
+ "--ignore-certificate-errors",
+ "--proxy-server=http://localhost:8080"
+ ]
+ });
+
+ this.context = await this.browser.newContext({
+ userAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 BugBountyHunter/1.0",
+ viewport: { width: 1920, height: 1080 },
+ acceptDownloads: true,
+ recordVideo: {
+ dir: join(BROWSER_DATA_DIR, "recordings"),
+ size: { width: 1920, height: 1080 }
+ }
+ });
+
+ // Set up request/response interception for proxy logging
+ await this.setupProxyLogging();
+ }
+
+ private async setupProxyLogging(): Promise {
+ if (!this.context) return;
+
+ // Intercept requests
+ await this.context.route("**/*", async (route) => {
+ const request = route.request();
+
+ const logEntry: ProxyLogEntry = {
+ timestamp: new Date().toISOString(),
+ method: request.method(),
+ url: request.url(),
+ headers: request.headers(),
+ body: request.postData() || undefined
+ };
+
+ // Continue with the request and capture response
+ const response = await route.continue();
+
+ // Note: Due to Playwright limitations, we can't easily capture response body
+ // For full proxy functionality, consider integrating with mitmproxy or similar
+
+ this.proxyLogs.push(logEntry);
+ this.saveProxyLogs();
+
+ console.log(`📡 Logged request: ${request.method()} ${request.url()}`);
+ });
+ }
+
+ public async navigateAndLogin(targetId: number, loginUrl: string): Promise<{ success: boolean; cookies: any[]; error?: string }> {
+ if (!this.context) {
+ await this.initializeBrowser();
+ }
+
+ try {
+ const page = await this.context!.newPage();
+
+ console.log(`🔐 Navigating to login page: ${loginUrl}`);
+ await page.goto(loginUrl, { waitUntil: "networkidle" });
+
+ // Take screenshot of login page
+ await page.screenshot({
+ path: join(BROWSER_DATA_DIR, `login_${targetId}_${Date.now()}.png`),
+ fullPage: true
+ });
+
+ console.log("🖱️ Browser is ready for manual login. Please log in manually...");
+ console.log("Press Enter in this terminal once you have completed login.");
+
+ // Wait for user to complete login manually
+ await this.waitForUserInput();
+
+ // Extract cookies after login
+ const cookies = await this.context!.cookies();
+
+ // Take screenshot after login
+ await page.screenshot({
+ path: join(BROWSER_DATA_DIR, `post_login_${targetId}_${Date.now()}.png`),
+ fullPage: true
+ });
+
+ // Save cookies to target
+ const target = getTarget(targetId);
+ if (target) {
+ const authCookies = cookies.reduce((acc, cookie) => {
+ acc[cookie.name] = cookie.value;
+ return acc;
+ }, {} as Record);
+
+ updateTarget(targetId, { auth_cookies: authCookies });
+ console.log(`✅ Saved ${cookies.length} authentication cookies for target`);
+ }
+
+ await page.close();
+
+ return { success: true, cookies };
+
+ } catch (error) {
+ console.error("❌ Login automation failed:", error);
+ return { success: false, cookies: [], error: String(error) };
+ }
+ }
+
+ private waitForUserInput(): Promise {
+ return new Promise((resolve) => {
+ process.stdin.once("data", () => {
+ resolve();
+ });
+ });
+ }
+
+ public async crawlWebApp(targetId: number, startUrl: string, maxDepth = 3): Promise<{ urls: string[]; apis: any[]; forms: any[] }> {
+ if (!this.context) {
+ await this.initializeBrowser();
+ }
+
+ const crawledUrls = new Set();
+ const discoveredApis: any[] = [];
+ const discoveredForms: any[] = [];
+ const urlsToVisit = [{ url: startUrl, depth: 0 }];
+
+ console.log(`🕷️ Starting web app crawl from ${startUrl}`);
+
+ try {
+ while (urlsToVisit.length > 0) {
+ const { url, depth } = urlsToVisit.shift()!;
+
+ if (crawledUrls.has(url) || depth > maxDepth) {
+ continue;
+ }
+
+ crawledUrls.add(url);
+ console.log(`📖 Crawling: ${url} (depth: ${depth})`);
+
+ const page = await this.context!.newPage();
+
+ try {
+ await page.goto(url, { waitUntil: "networkidle", timeout: 30000 });
+
+ // Extract all links for further crawling
+ const links = await page.$$eval("a[href]", (elements) =>
+ elements.map((el) => (el as HTMLAnchorElement).href)
+ );
+
+ // Add new links to crawl queue
+ for (const link of links) {
+ if (this.isSameDomain(link, startUrl) && !crawledUrls.has(link)) {
+ urlsToVisit.push({ url: link, depth: depth + 1 });
+ }
+ }
+
+ // Extract forms for potential testing
+ const forms = await page.$$eval("form", (elements) =>
+ elements.map((form) => {
+ const inputs = Array.from(form.querySelectorAll("input, textarea, select")).map((input) => ({
+ type: input.getAttribute("type") || "text",
+ name: input.getAttribute("name") || "",
+ id: input.getAttribute("id") || "",
+ required: input.hasAttribute("required")
+ }));
+
+ return {
+ action: form.getAttribute("action") || "",
+ method: form.getAttribute("method") || "GET",
+ inputs: inputs
+ };
+ })
+ );
+
+ discoveredForms.push(...forms.map(form => ({ ...form, url })));
+
+ // Look for API endpoints in JavaScript
+ const apiEndpoints = await page.evaluate(() => {
+ const endpoints: string[] = [];
+ const scripts = Array.from(document.querySelectorAll("script"));
+
+ for (const script of scripts) {
+ const content = script.innerHTML;
+ // Look for common API patterns
+ const apiMatches = content.match(/['"]\/api\/[^'"]*['"]/g) || [];
+ const fetchMatches = content.match(/fetch\s*\(\s*['"][^'"]*['"]/g) || [];
+
+ endpoints.push(...apiMatches.map(m => m.slice(1, -1)));
+ endpoints.push(...fetchMatches.map(m => m.match(/['"]([^'"]*)['"]/)?.[1] || ""));
+ }
+
+ return endpoints.filter(Boolean);
+ });
+
+ discoveredApis.push(...apiEndpoints.map(endpoint => ({ endpoint, discoveredAt: url })));
+
+ await page.close();
+
+ } catch (error) {
+ console.error(`❌ Failed to crawl ${url}:`, error);
+ await page.close();
+ }
+ }
+
+ console.log(`✅ Crawl completed: ${crawledUrls.size} URLs, ${discoveredApis.length} APIs, ${discoveredForms.length} forms`);
+
+ return {
+ urls: Array.from(crawledUrls),
+ apis: discoveredApis,
+ forms: discoveredForms
+ };
+
+ } catch (error) {
+ console.error("❌ Web app crawl failed:", error);
+ return { urls: [], apis: [], forms: [] };
+ }
+ }
+
+ private isSameDomain(url: string, baseUrl: string): boolean {
+ try {
+ const urlObj = new URL(url);
+ const baseUrlObj = new URL(baseUrl);
+ return urlObj.hostname === baseUrlObj.hostname;
+ } catch {
+ return false;
+ }
+ }
+
+ public async testForIDOR(targetId: number, urls: string[]): Promise {
+ console.log(`🔍 Testing for IDOR vulnerabilities on ${urls.length} URLs`);
+
+ if (!this.context) {
+ await this.initializeBrowser();
+ }
+
+ for (const url of urls) {
+ if (this.hasNumericId(url)) {
+ await this.testIDORVariations(targetId, url);
+ }
+ }
+ }
+
+ private hasNumericId(url: string): boolean {
+ return /\/\d+(?:\/|$|\?)/.test(url);
+ }
+
+ private async testIDORVariations(targetId: number, originalUrl: string): Promise {
+ const page = await this.context!.newPage();
+
+ try {
+ // Get original response
+ const originalResponse = await page.goto(originalUrl);
+ const originalContent = await page.content();
+ const originalStatus = originalResponse?.status();
+
+ // Test with different ID values
+ const testIds = ["1", "2", "999", "0", "-1", "admin", "test"];
+
+ for (const testId of testIds) {
+ const modifiedUrl = originalUrl.replace(/\/\d+/, `/${testId}`);
+
+ if (modifiedUrl !== originalUrl) {
+ try {
+ const response = await page.goto(modifiedUrl);
+ const content = await page.content();
+ const status = response?.status();
+
+ // Potential IDOR if we get successful response with different content
+ if (status === 200 && content !== originalContent && content.length > 100) {
+ const { addVulnerability } = await import("./database.js");
+ addVulnerability({
+ target_id: targetId,
+ vuln_type: "idor",
+ severity: "high",
+ title: "Potential IDOR Vulnerability",
+ description: `Access to different resource by changing ID parameter`,
+ url: modifiedUrl,
+ proof_of_concept: `Original URL: ${originalUrl}\nModified URL: ${modifiedUrl}\nBoth returned different valid content`,
+ cwe_id: "CWE-639"
+ });
+
+ console.log(`🚨 Potential IDOR found: ${modifiedUrl}`);
+ }
+ } catch (error) {
+ // Ignore navigation errors
+ }
+ }
+ }
+
+ } catch (error) {
+ console.error(`❌ IDOR testing failed for ${originalUrl}:`, error);
+ } finally {
+ await page.close();
+ }
+ }
+
+ public getProxyLogs(): ProxyLogEntry[] {
+ return this.proxyLogs;
+ }
+
+ public clearProxyLogs(): void {
+ this.proxyLogs = [];
+ this.saveProxyLogs();
+ }
+
+ public async closeBrowser(): Promise {
+ if (this.context) {
+ await this.context.close();
+ this.context = null;
+ }
+
+ if (this.browser) {
+ await this.browser.close();
+ this.browser = null;
+ }
+
+ console.log("🔒 Browser closed");
+ }
+
+ public async takeScreenshot(targetId: number, url: string): Promise {
+ if (!this.context) {
+ await this.initializeBrowser();
+ }
+
+ const page = await this.context!.newPage();
+ const screenshotPath = join(BROWSER_DATA_DIR, `screenshot_${targetId}_${Date.now()}.png`);
+
+ try {
+ await page.goto(url, { waitUntil: "networkidle" });
+ await page.screenshot({ path: screenshotPath, fullPage: true });
+ await page.close();
+
+ console.log(`📸 Screenshot saved: ${screenshotPath}`);
+ return screenshotPath;
+ } catch (error) {
+ await page.close();
+ throw error;
+ }
+ }
+
+ public async extractAPIsFromTraffic(): Promise> {
+ const apis: Array<{ method: string; endpoint: string; parameters: any }> = [];
+
+ for (const log of this.proxyLogs) {
+ if (this.looksLikeAPI(log.url)) {
+ const urlObj = new URL(log.url);
+
+ apis.push({
+ method: log.method,
+ endpoint: urlObj.pathname,
+ parameters: {
+ query: Object.fromEntries(urlObj.searchParams),
+ body: log.body ? this.tryParseJSON(log.body) : null,
+ headers: log.headers
+ }
+ });
+ }
+ }
+
+ // Remove duplicates
+ const uniqueApis = apis.filter((api, index, self) =>
+ index === self.findIndex(a => a.method === api.method && a.endpoint === api.endpoint)
+ );
+
+ return uniqueApis;
+ }
+
+ private looksLikeAPI(url: string): boolean {
+ const apiIndicators = ["/api/", "/v1/", "/v2/", ".json", "/rest/", "/graphql"];
+ return apiIndicators.some(indicator => url.includes(indicator)) ||
+ url.match(/\/[a-zA-Z]+\/\d+/) !== null; // REST-like patterns
+ }
+
+ private tryParseJSON(str: string): any {
+ try {
+ return JSON.parse(str);
+ } catch {
+ return str;
+ }
+ }
+}
\ No newline at end of file
diff --git a/codex-cli/src/utils/bug-bounty-scanner.ts b/codex-cli/src/utils/bug-bounty-scanner.ts
new file mode 100644
index 00000000000..88a2c17b5bd
--- /dev/null
+++ b/codex-cli/src/utils/bug-bounty-scanner.ts
@@ -0,0 +1,478 @@
+import { spawn } from "child_process";
+import { writeFileSync, readFileSync, existsSync, mkdirSync } from "fs";
+import { join } from "path";
+import { CONFIG_DIR } from "./config.js";
+import {
+ addScan,
+ updateScanStatus,
+ addVulnerability,
+ getTarget,
+ type Target,
+ type Scan,
+ type Vulnerability
+} from "./database.js";
+
+const TOOLS_DIR = join(CONFIG_DIR, "tools");
+const RESULTS_DIR = join(CONFIG_DIR, "results");
+
+// Ensure directories exist
+[TOOLS_DIR, RESULTS_DIR].forEach(dir => {
+ if (!existsSync(dir)) {
+ mkdirSync(dir, { recursive: true });
+ }
+});
+
+export class BugBountyScanner {
+ private runningScans = new Map();
+
+ constructor() {
+ this.ensureToolsInstalled();
+ }
+
+ private async ensureToolsInstalled(): Promise {
+ console.log("🔧 Checking and installing bug bounty tools...");
+
+ const tools = [
+ {
+ name: "amass",
+ check: "amass --help",
+ install: "go install -v github.com/owasp-amass/amass/v4/...@master"
+ },
+ {
+ name: "nuclei",
+ check: "nuclei --help",
+ install: "go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest"
+ },
+ {
+ name: "subfinder",
+ check: "subfinder --help",
+ install: "go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest"
+ },
+ {
+ name: "httpx",
+ check: "httpx --help",
+ install: "go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest"
+ },
+ {
+ name: "nmap",
+ check: "nmap --help",
+ install: "apt-get install -y nmap"
+ },
+ {
+ name: "sqlmap",
+ check: "sqlmap --help",
+ install: "apt-get install -y sqlmap"
+ }
+ ];
+
+ for (const tool of tools) {
+ try {
+ await this.runCommand(tool.check.split(" "), { timeout: 5000 });
+ console.log(`✅ ${tool.name} is available`);
+ } catch {
+ console.log(`📦 Installing ${tool.name}...`);
+ try {
+ await this.runCommand(tool.install.split(" "), { timeout: 300000 });
+ console.log(`✅ ${tool.name} installed successfully`);
+ } catch (error) {
+ console.log(`❌ Failed to install ${tool.name}:`, error);
+ }
+ }
+ }
+ }
+
+ private runCommand(cmd: string[], options: { timeout?: number; cwd?: string } = {}): Promise<{ stdout: string; stderr: string; exitCode: number }> {
+ return new Promise((resolve, reject) => {
+ const child = spawn(cmd[0], cmd.slice(1), {
+ cwd: options.cwd || process.cwd(),
+ stdio: ["ignore", "pipe", "pipe"]
+ });
+
+ let stdout = "";
+ let stderr = "";
+
+ child.stdout?.on("data", (data) => {
+ stdout += data.toString();
+ });
+
+ child.stderr?.on("data", (data) => {
+ stderr += data.toString();
+ });
+
+ const timeout = options.timeout ? setTimeout(() => {
+ child.kill("SIGTERM");
+ reject(new Error("Command timeout"));
+ }, options.timeout) : null;
+
+ child.on("exit", (code) => {
+ if (timeout) clearTimeout(timeout);
+ resolve({
+ stdout,
+ stderr,
+ exitCode: code || 0
+ });
+ });
+
+ child.on("error", (error) => {
+ if (timeout) clearTimeout(timeout);
+ reject(error);
+ });
+ });
+ }
+
+ public async startComprehensiveScan(targetId: number): Promise {
+ const target = getTarget(targetId);
+ if (!target) {
+ throw new Error(`Target ${targetId} not found`);
+ }
+
+ console.log(`🚀 Starting comprehensive scan for ${target.domain}`);
+
+ // Chain of scans: subdomain -> port -> web crawl -> vulnerability scan
+ await this.subdomainEnumeration(targetId);
+ await this.portScan(targetId);
+ await this.webCrawl(targetId);
+ await this.vulnerabilityScan(targetId);
+ }
+
+ public async subdomainEnumeration(targetId: number): Promise {
+ const target = getTarget(targetId);
+ if (!target) return;
+
+ const scanId = addScan({
+ target_id: targetId,
+ scan_type: "subdomain_enum",
+ command: `subfinder -d ${target.domain} && amass enum -d ${target.domain}`
+ });
+
+ updateScanStatus(scanId, "running");
+
+ try {
+ const resultFile = join(RESULTS_DIR, `subdomains_${targetId}_${Date.now()}.txt`);
+
+ // Run Subfinder
+ console.log(`🔍 Running Subfinder on ${target.domain}`);
+ const subfinderResult = await this.runCommand([
+ "subfinder", "-d", target.domain, "-o", resultFile
+ ], { timeout: 300000 });
+
+ // Run Amass (if available)
+ try {
+ console.log(`🔍 Running Amass on ${target.domain}`);
+ const amassResult = await this.runCommand([
+ "amass", "enum", "-d", target.domain, "-o", resultFile + ".amass"
+ ], { timeout: 600000 });
+
+ // Combine results
+ if (existsSync(resultFile + ".amass")) {
+ const amassSubdomains = readFileSync(resultFile + ".amass", "utf8");
+ writeFileSync(resultFile, `${readFileSync(resultFile, "utf8")}\n${amassSubdomains}`);
+ }
+ } catch (error) {
+ console.log("Amass not available or failed, continuing with Subfinder results");
+ }
+
+ // Parse and store results
+ const subdomains = existsSync(resultFile)
+ ? readFileSync(resultFile, "utf8").split("\n").filter(Boolean)
+ : [];
+
+ updateScanStatus(scanId, "completed", {
+ subdomains_found: subdomains.length,
+ subdomains: subdomains,
+ result_file: resultFile
+ });
+
+ console.log(`✅ Found ${subdomains.length} subdomains for ${target.domain}`);
+
+ } catch (error) {
+ updateScanStatus(scanId, "failed", { error: String(error) });
+ console.error(`❌ Subdomain enumeration failed for ${target.domain}:`, error);
+ }
+ }
+
+ public async portScan(targetId: number): Promise {
+ const target = getTarget(targetId);
+ if (!target) return;
+
+ const scanId = addScan({
+ target_id: targetId,
+ scan_type: "port_scan",
+ command: `nmap -sS -sV -O -T4 ${target.domain}`
+ });
+
+ updateScanStatus(scanId, "running");
+
+ try {
+ console.log(`🔌 Running Nmap port scan on ${target.domain}`);
+
+ const nmapResult = await this.runCommand([
+ "nmap", "-sS", "-sV", "-O", "-T4",
+ "--open", "-oN", join(RESULTS_DIR, `portscan_${targetId}_${Date.now()}.txt`),
+ target.domain
+ ], { timeout: 1800000 }); // 30 minutes
+
+ // Parse Nmap output for open ports
+ const openPorts = this.parseNmapOutput(nmapResult.stdout);
+
+ updateScanStatus(scanId, "completed", {
+ open_ports: openPorts,
+ nmap_output: nmapResult.stdout
+ });
+
+ // Check for potential vulnerabilities based on open ports
+ this.analyzeOpenPorts(targetId, openPorts);
+
+ console.log(`✅ Port scan completed for ${target.domain}, found ${openPorts.length} open ports`);
+
+ } catch (error) {
+ updateScanStatus(scanId, "failed", { error: String(error) });
+ console.error(`❌ Port scan failed for ${target.domain}:`, error);
+ }
+ }
+
+ public async webCrawl(targetId: number): Promise {
+ const target = getTarget(targetId);
+ if (!target) return;
+
+ const scanId = addScan({
+ target_id: targetId,
+ scan_type: "web_crawl",
+ command: `httpx -l subdomains.txt -status-code -title -tech-detect`
+ });
+
+ updateScanStatus(scanId, "running");
+
+ try {
+ console.log(`🕷️ Running web crawl on ${target.domain}`);
+
+ // First, run httpx to find live hosts
+ const httpxResult = await this.runCommand([
+ "httpx", "-u", target.domain, "-status-code", "-title",
+ "-tech-detect", "-json", "-o", join(RESULTS_DIR, `httpx_${targetId}_${Date.now()}.json`)
+ ], { timeout: 600000 });
+
+ const resultData = {
+ httpx_output: httpxResult.stdout,
+ discovered_technologies: this.parseHttpxOutput(httpxResult.stdout)
+ };
+
+ updateScanStatus(scanId, "completed", resultData);
+
+ console.log(`✅ Web crawl completed for ${target.domain}`);
+
+ } catch (error) {
+ updateScanStatus(scanId, "failed", { error: String(error) });
+ console.error(`❌ Web crawl failed for ${target.domain}:`, error);
+ }
+ }
+
+ public async vulnerabilityScan(targetId: number): Promise {
+ const target = getTarget(targetId);
+ if (!target) return;
+
+ const scanId = addScan({
+ target_id: targetId,
+ scan_type: "vulnerability_scan",
+ command: `nuclei -u ${target.domain} -es info`
+ });
+
+ updateScanStatus(scanId, "running");
+
+ try {
+ console.log(`🛡️ Running Nuclei vulnerability scan on ${target.domain}`);
+
+ const nucleiResult = await this.runCommand([
+ "nuclei", "-u", target.domain, "-es", "info", "-json",
+ "-o", join(RESULTS_DIR, `nuclei_${targetId}_${Date.now()}.json`)
+ ], { timeout: 1800000 }); // 30 minutes
+
+ // Parse Nuclei output and store vulnerabilities
+ const vulnerabilities = this.parseNucleiOutput(nucleiResult.stdout, targetId);
+
+ updateScanStatus(scanId, "completed", {
+ vulnerabilities_found: vulnerabilities.length,
+ nuclei_output: nucleiResult.stdout
+ });
+
+ console.log(`✅ Vulnerability scan completed for ${target.domain}, found ${vulnerabilities.length} issues`);
+
+ } catch (error) {
+ updateScanStatus(scanId, "failed", { error: String(error) });
+ console.error(`❌ Vulnerability scan failed for ${target.domain}:`, error);
+ }
+ }
+
+ public async sqlInjectionTest(targetId: number, url: string): Promise {
+ const scanId = addScan({
+ target_id: targetId,
+ scan_type: "sql_injection",
+ command: `sqlmap -u "${url}" --batch --risk=3 --level=5`
+ });
+
+ updateScanStatus(scanId, "running");
+
+ try {
+ console.log(`💉 Running SQLMap on ${url}`);
+
+ const sqlmapResult = await this.runCommand([
+ "sqlmap", "-u", url, "--batch", "--risk=3", "--level=5",
+ "--output-dir", join(RESULTS_DIR, `sqlmap_${targetId}_${Date.now()}`)
+ ], { timeout: 1800000 });
+
+ // Parse SQLMap output for vulnerabilities
+ if (sqlmapResult.stdout.includes("identified the following injection point")) {
+ addVulnerability({
+ target_id: targetId,
+ scan_id: scanId,
+ vuln_type: "sql_injection",
+ severity: "high",
+ title: "SQL Injection Vulnerability",
+ description: "SQLMap identified potential SQL injection vulnerability",
+ url: url,
+ proof_of_concept: sqlmapResult.stdout,
+ cwe_id: "CWE-89"
+ });
+ }
+
+ updateScanStatus(scanId, "completed", {
+ sqlmap_output: sqlmapResult.stdout
+ });
+
+ } catch (error) {
+ updateScanStatus(scanId, "failed", { error: String(error) });
+ console.error(`❌ SQL injection test failed for ${url}:`, error);
+ }
+ }
+
+ private parseNmapOutput(output: string): Array<{ port: number; service: string; version?: string }> {
+ const ports: Array<{ port: number; service: string; version?: string }> = [];
+ const lines = output.split("\n");
+
+ for (const line of lines) {
+ const portMatch = line.match(/^(\d+)\/tcp\s+open\s+(\w+)(?:\s+(.+))?/);
+ if (portMatch) {
+ ports.push({
+ port: parseInt(portMatch[1]),
+ service: portMatch[2],
+ version: portMatch[3]?.trim()
+ });
+ }
+ }
+
+ return ports;
+ }
+
+ private parseHttpxOutput(output: string): string[] {
+ const technologies: string[] = [];
+ const lines = output.split("\n");
+
+ for (const line of lines) {
+ try {
+ const json = JSON.parse(line);
+ if (json.tech && Array.isArray(json.tech)) {
+ technologies.push(...json.tech);
+ }
+ } catch {
+ // Skip invalid JSON lines
+ }
+ }
+
+ return [...new Set(technologies)]; // Remove duplicates
+ }
+
+ private parseNucleiOutput(output: string, targetId: number): Vulnerability[] {
+ const vulnerabilities: Vulnerability[] = [];
+ const lines = output.split("\n");
+
+ for (const line of lines) {
+ try {
+ const json = JSON.parse(line);
+ if (json.info) {
+ const vuln: Vulnerability = {
+ target_id: targetId,
+ vuln_type: json.info.classification?.cwe_id || json.type || "unknown",
+ severity: this.mapNucleiSeverity(json.info.severity),
+ title: json.info.name,
+ description: json.info.description,
+ url: json.matched_at,
+ cwe_id: json.info.classification?.cwe_id,
+ proof_of_concept: JSON.stringify(json, null, 2)
+ };
+
+ vulnerabilities.push(vuln);
+ addVulnerability(vuln);
+ }
+ } catch {
+ // Skip invalid JSON lines
+ }
+ }
+
+ return vulnerabilities;
+ }
+
+ private mapNucleiSeverity(severity: string): "critical" | "high" | "medium" | "low" | "info" {
+ const severityMap: Record = {
+ "critical": "critical",
+ "high": "high",
+ "medium": "medium",
+ "low": "low",
+ "info": "info",
+ "unknown": "info"
+ };
+
+ return severityMap[severity?.toLowerCase()] || "info";
+ }
+
+ private analyzeOpenPorts(targetId: number, openPorts: Array<{ port: number; service: string; version?: string }>): void {
+ for (const portInfo of openPorts) {
+ // Check for potentially dangerous services
+ if (this.isDangerousService(portInfo)) {
+ addVulnerability({
+ target_id: targetId,
+ vuln_type: "exposed_service",
+ severity: this.getServiceSeverity(portInfo.service),
+ title: `Exposed ${portInfo.service} Service`,
+ description: `Potentially dangerous service ${portInfo.service} detected on port ${portInfo.port}`,
+ url: `port:${portInfo.port}`,
+ proof_of_concept: `Service: ${portInfo.service}, Version: ${portInfo.version || "Unknown"}`,
+ cwe_id: "CWE-200"
+ });
+ }
+ }
+ }
+
+ private isDangerousService(portInfo: { port: number; service: string; version?: string }): boolean {
+ const dangerousServices = [
+ "telnet", "ftp", "ssh", "rlogin", "mysql", "postgresql",
+ "redis", "mongodb", "elasticsearch", "rdp", "vnc", "smb"
+ ];
+
+ return dangerousServices.includes(portInfo.service.toLowerCase()) ||
+ portInfo.port === 22 || // SSH
+ portInfo.port === 3389 || // RDP
+ portInfo.port === 5900; // VNC
+ }
+
+ private getServiceSeverity(service: string): "critical" | "high" | "medium" | "low" | "info" {
+ const criticalServices = ["telnet", "ftp", "rlogin"];
+ const highServices = ["ssh", "rdp", "vnc", "mysql", "postgresql"];
+
+ if (criticalServices.includes(service.toLowerCase())) return "critical";
+ if (highServices.includes(service.toLowerCase())) return "high";
+ return "medium";
+ }
+
+ public async stopScan(scanId: number): Promise {
+ const process = this.runningScans.get(scanId);
+ if (process) {
+ process.kill("SIGTERM");
+ this.runningScans.delete(scanId);
+ updateScanStatus(scanId, "failed", { error: "Scan stopped by user" });
+ }
+ }
+
+ public getRunningScans(): number[] {
+ return Array.from(this.runningScans.keys());
+ }
+}
\ No newline at end of file
diff --git a/codex-cli/src/utils/database.ts b/codex-cli/src/utils/database.ts
new file mode 100644
index 00000000000..b64af5c8e53
--- /dev/null
+++ b/codex-cli/src/utils/database.ts
@@ -0,0 +1,345 @@
+import Database from "better-sqlite3";
+import { join } from "path";
+import { CONFIG_DIR } from "./config.js";
+import { mkdirSync } from "fs";
+
+// Ensure config directory exists
+try {
+ mkdirSync(CONFIG_DIR, { recursive: true });
+} catch {
+ // Directory already exists
+}
+
+const DB_PATH = join(CONFIG_DIR, "bugbounty.db");
+export const db = new Database(DB_PATH);
+
+// Enable foreign keys
+db.pragma("foreign_keys = ON");
+
+// Create tables for bug bounty operations
+export function initDatabase(): void {
+ // Targets table - stores domains/URLs to scan
+ db.exec(`
+ CREATE TABLE IF NOT EXISTS targets (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ domain TEXT NOT NULL UNIQUE,
+ description TEXT,
+ api_keys TEXT, -- JSON string of API keys
+ auth_cookies TEXT, -- JSON string of authentication cookies
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ status TEXT DEFAULT 'active' -- active, paused, completed
+ )
+ `);
+
+ // Scans table - tracks scanning operations
+ db.exec(`
+ CREATE TABLE IF NOT EXISTS scans (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ target_id INTEGER NOT NULL,
+ scan_type TEXT NOT NULL, -- subdomain, port, vuln, web_crawl, etc.
+ status TEXT DEFAULT 'pending', -- pending, running, completed, failed
+ command TEXT, -- Command that was executed
+ started_at DATETIME,
+ completed_at DATETIME,
+ result_data TEXT, -- JSON string of results
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (target_id) REFERENCES targets (id) ON DELETE CASCADE
+ )
+ `);
+
+ // Vulnerabilities table - stores discovered vulnerabilities
+ db.exec(`
+ CREATE TABLE IF NOT EXISTS vulnerabilities (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ target_id INTEGER NOT NULL,
+ scan_id INTEGER,
+ vuln_type TEXT NOT NULL, -- xss, sqli, idor, xxe, etc.
+ severity TEXT NOT NULL, -- critical, high, medium, low, info
+ title TEXT NOT NULL,
+ description TEXT,
+ url TEXT,
+ payload TEXT,
+ proof_of_concept TEXT,
+ cwe_id TEXT, -- CWE identifier
+ cvss_score REAL,
+ discovered_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ verified BOOLEAN DEFAULT FALSE,
+ false_positive BOOLEAN DEFAULT FALSE,
+ FOREIGN KEY (target_id) REFERENCES targets (id) ON DELETE CASCADE,
+ FOREIGN KEY (scan_id) REFERENCES scans (id) ON DELETE SET NULL
+ )
+ `);
+
+ // Web APIs table - stores discovered API endpoints
+ db.exec(`
+ CREATE TABLE IF NOT EXISTS web_apis (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ target_id INTEGER NOT NULL,
+ method TEXT NOT NULL, -- GET, POST, PUT, DELETE, etc.
+ endpoint TEXT NOT NULL,
+ parameters TEXT, -- JSON string of parameters
+ headers TEXT, -- JSON string of headers
+ response_type TEXT, -- JSON, XML, HTML, etc.
+ auth_required BOOLEAN DEFAULT FALSE,
+ discovered_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (target_id) REFERENCES targets (id) ON DELETE CASCADE
+ )
+ `);
+
+ // Scan configurations for self-improvement
+ db.exec(`
+ CREATE TABLE IF NOT EXISTS scan_configs (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ tool_name TEXT NOT NULL,
+ config_data TEXT NOT NULL, -- JSON string of configuration
+ success_rate REAL DEFAULT 0.0, -- Learning metric
+ avg_execution_time INTEGER DEFAULT 0, -- Average time in seconds
+ last_used DATETIME,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ )
+ `);
+
+ // Create indexes for better performance
+ db.exec(`
+ CREATE INDEX IF NOT EXISTS idx_targets_domain ON targets(domain);
+ CREATE INDEX IF NOT EXISTS idx_scans_target_id ON scans(target_id);
+ CREATE INDEX IF NOT EXISTS idx_scans_status ON scans(status);
+ CREATE INDEX IF NOT EXISTS idx_vulns_target_id ON vulnerabilities(target_id);
+ CREATE INDEX IF NOT EXISTS idx_vulns_type ON vulnerabilities(vuln_type);
+ CREATE INDEX IF NOT EXISTS idx_vulns_severity ON vulnerabilities(severity);
+ CREATE INDEX IF NOT EXISTS idx_apis_target_id ON web_apis(target_id);
+ `);
+}
+
+// Target management functions
+export interface Target {
+ id?: number;
+ domain: string;
+ description?: string;
+ api_keys?: Record;
+ auth_cookies?: Record;
+ created_at?: string;
+ updated_at?: string;
+ status?: "active" | "paused" | "completed";
+}
+
+export function addTarget(target: Target): number {
+ const stmt = db.prepare(`
+ INSERT INTO targets (domain, description, api_keys, auth_cookies, status)
+ VALUES (?, ?, ?, ?, ?)
+ `);
+
+ const result = stmt.run(
+ target.domain,
+ target.description || null,
+ target.api_keys ? JSON.stringify(target.api_keys) : null,
+ target.auth_cookies ? JSON.stringify(target.auth_cookies) : null,
+ target.status || "active"
+ );
+
+ return result.lastInsertRowid as number;
+}
+
+export function getTargets(): Target[] {
+ const stmt = db.prepare("SELECT * FROM targets ORDER BY created_at DESC");
+ const rows = stmt.all() as any[];
+
+ return rows.map(row => ({
+ ...row,
+ api_keys: row.api_keys ? JSON.parse(row.api_keys) : {},
+ auth_cookies: row.auth_cookies ? JSON.parse(row.auth_cookies) : {},
+ }));
+}
+
+export function getTarget(id: number): Target | null {
+ const stmt = db.prepare("SELECT * FROM targets WHERE id = ?");
+ const row = stmt.get(id) as any;
+
+ if (!row) return null;
+
+ return {
+ ...row,
+ api_keys: row.api_keys ? JSON.parse(row.api_keys) : {},
+ auth_cookies: row.auth_cookies ? JSON.parse(row.auth_cookies) : {},
+ };
+}
+
+export function updateTarget(id: number, updates: Partial): void {
+ const fields: string[] = [];
+ const values: any[] = [];
+
+ if (updates.description !== undefined) {
+ fields.push("description = ?");
+ values.push(updates.description);
+ }
+
+ if (updates.api_keys !== undefined) {
+ fields.push("api_keys = ?");
+ values.push(JSON.stringify(updates.api_keys));
+ }
+
+ if (updates.auth_cookies !== undefined) {
+ fields.push("auth_cookies = ?");
+ values.push(JSON.stringify(updates.auth_cookies));
+ }
+
+ if (updates.status !== undefined) {
+ fields.push("status = ?");
+ values.push(updates.status);
+ }
+
+ if (fields.length === 0) return;
+
+ fields.push("updated_at = CURRENT_TIMESTAMP");
+ values.push(id);
+
+ const sql = `UPDATE targets SET ${fields.join(", ")} WHERE id = ?`;
+ const stmt = db.prepare(sql);
+ stmt.run(...values);
+}
+
+// Scan management functions
+export interface Scan {
+ id?: number;
+ target_id: number;
+ scan_type: string;
+ status?: "pending" | "running" | "completed" | "failed";
+ command?: string;
+ started_at?: string;
+ completed_at?: string;
+ result_data?: any;
+ created_at?: string;
+}
+
+export function addScan(scan: Scan): number {
+ const stmt = db.prepare(`
+ INSERT INTO scans (target_id, scan_type, status, command, result_data)
+ VALUES (?, ?, ?, ?, ?)
+ `);
+
+ const result = stmt.run(
+ scan.target_id,
+ scan.scan_type,
+ scan.status || "pending",
+ scan.command || null,
+ scan.result_data ? JSON.stringify(scan.result_data) : null
+ );
+
+ return result.lastInsertRowid as number;
+}
+
+export function updateScanStatus(
+ id: number,
+ status: "pending" | "running" | "completed" | "failed",
+ result_data?: any
+): void {
+ const updates: string[] = ["status = ?"];
+ const values: any[] = [status];
+
+ if (status === "running" && !getScan(id)?.started_at) {
+ updates.push("started_at = CURRENT_TIMESTAMP");
+ }
+
+ if (status === "completed" || status === "failed") {
+ updates.push("completed_at = CURRENT_TIMESTAMP");
+ }
+
+ if (result_data !== undefined) {
+ updates.push("result_data = ?");
+ values.push(JSON.stringify(result_data));
+ }
+
+ values.push(id);
+
+ const sql = `UPDATE scans SET ${updates.join(", ")} WHERE id = ?`;
+ const stmt = db.prepare(sql);
+ stmt.run(...values);
+}
+
+export function getScan(id: number): Scan | null {
+ const stmt = db.prepare("SELECT * FROM scans WHERE id = ?");
+ const row = stmt.get(id) as any;
+
+ if (!row) return null;
+
+ return {
+ ...row,
+ result_data: row.result_data ? JSON.parse(row.result_data) : null,
+ };
+}
+
+export function getScansForTarget(targetId: number): Scan[] {
+ const stmt = db.prepare(`
+ SELECT * FROM scans
+ WHERE target_id = ?
+ ORDER BY created_at DESC
+ `);
+ const rows = stmt.all(targetId) as any[];
+
+ return rows.map(row => ({
+ ...row,
+ result_data: row.result_data ? JSON.parse(row.result_data) : null,
+ }));
+}
+
+// Vulnerability management functions
+export interface Vulnerability {
+ id?: number;
+ target_id: number;
+ scan_id?: number;
+ vuln_type: string;
+ severity: "critical" | "high" | "medium" | "low" | "info";
+ title: string;
+ description?: string;
+ url?: string;
+ payload?: string;
+ proof_of_concept?: string;
+ cwe_id?: string;
+ cvss_score?: number;
+ discovered_at?: string;
+ verified?: boolean;
+ false_positive?: boolean;
+}
+
+export function addVulnerability(vuln: Vulnerability): number {
+ const stmt = db.prepare(`
+ INSERT INTO vulnerabilities (
+ target_id, scan_id, vuln_type, severity, title, description,
+ url, payload, proof_of_concept, cwe_id, cvss_score, verified, false_positive
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ `);
+
+ const result = stmt.run(
+ vuln.target_id,
+ vuln.scan_id || null,
+ vuln.vuln_type,
+ vuln.severity,
+ vuln.title,
+ vuln.description || null,
+ vuln.url || null,
+ vuln.payload || null,
+ vuln.proof_of_concept || null,
+ vuln.cwe_id || null,
+ vuln.cvss_score || null,
+ vuln.verified || false,
+ vuln.false_positive || false
+ );
+
+ return result.lastInsertRowid as number;
+}
+
+export function getVulnerabilities(targetId?: number): Vulnerability[] {
+ const sql = targetId
+ ? "SELECT * FROM vulnerabilities WHERE target_id = ? ORDER BY discovered_at DESC"
+ : "SELECT * FROM vulnerabilities ORDER BY discovered_at DESC";
+
+ const stmt = db.prepare(sql);
+ const rows = targetId ? stmt.all(targetId) : stmt.all();
+
+ return rows as Vulnerability[];
+}
+
+// Initialize database on import
+initDatabase();
\ No newline at end of file
diff --git a/codex-cli/src/utils/web-server.ts b/codex-cli/src/utils/web-server.ts
new file mode 100644
index 00000000000..4b6144200a6
--- /dev/null
+++ b/codex-cli/src/utils/web-server.ts
@@ -0,0 +1,704 @@
+import express from "express";
+import { createServer } from "http";
+import { WebSocketServer } from "ws";
+import { join, dirname } from "path";
+import { fileURLToPath } from "url";
+import {
+ getTargets,
+ addTarget,
+ updateTarget,
+ getTarget,
+ getScansForTarget,
+ addScan,
+ updateScanStatus,
+ getVulnerabilities,
+ addVulnerability,
+ type Target,
+ type Scan,
+ type Vulnerability
+} from "./database.js";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+export class BugBountyWebServer {
+ private app = express();
+ private server = createServer(this.app);
+ private wss = new WebSocketServer({ server: this.server });
+ private clients = new Set();
+
+ constructor(private port = 222) {
+ this.setupMiddleware();
+ this.setupRoutes();
+ this.setupWebSocket();
+ }
+
+ private setupMiddleware(): void {
+ this.app.use(express.json());
+ this.app.use(express.static(join(__dirname, "../../../web")));
+ }
+
+ private setupRoutes(): void {
+ // Serve main dashboard
+ this.app.get("/", (req, res) => {
+ res.send(this.getIndexHTML());
+ });
+
+ // API Routes
+
+ // Targets
+ this.app.get("/api/targets", (req, res) => {
+ try {
+ const targets = getTargets();
+ res.json(targets);
+ } catch (error) {
+ res.status(500).json({ error: "Failed to fetch targets" });
+ }
+ });
+
+ this.app.post("/api/targets", (req, res) => {
+ try {
+ const target: Target = req.body;
+ const id = addTarget(target);
+ this.broadcast({ type: "target_added", target: { ...target, id } });
+ res.json({ id, message: "Target added successfully" });
+ } catch (error) {
+ res.status(500).json({ error: "Failed to add target" });
+ }
+ });
+
+ this.app.put("/api/targets/:id", (req, res) => {
+ try {
+ const id = parseInt(req.params.id);
+ const updates = req.body;
+ updateTarget(id, updates);
+ const target = getTarget(id);
+ this.broadcast({ type: "target_updated", target });
+ res.json({ message: "Target updated successfully" });
+ } catch (error) {
+ res.status(500).json({ error: "Failed to update target" });
+ }
+ });
+
+ // Scans
+ this.app.get("/api/targets/:id/scans", (req, res) => {
+ try {
+ const targetId = parseInt(req.params.id);
+ const scans = getScansForTarget(targetId);
+ res.json(scans);
+ } catch (error) {
+ res.status(500).json({ error: "Failed to fetch scans" });
+ }
+ });
+
+ this.app.post("/api/targets/:id/scans", (req, res) => {
+ try {
+ const targetId = parseInt(req.params.id);
+ const scan: Scan = { ...req.body, target_id: targetId };
+ const scanId = addScan(scan);
+ this.broadcast({
+ type: "scan_added",
+ scan: { ...scan, id: scanId }
+ });
+ res.json({ id: scanId, message: "Scan added successfully" });
+ } catch (error) {
+ res.status(500).json({ error: "Failed to add scan" });
+ }
+ });
+
+ this.app.put("/api/scans/:id/status", (req, res) => {
+ try {
+ const id = parseInt(req.params.id);
+ const { status, result_data } = req.body;
+ updateScanStatus(id, status, result_data);
+ this.broadcast({
+ type: "scan_updated",
+ scan_id: id,
+ status,
+ result_data
+ });
+ res.json({ message: "Scan status updated successfully" });
+ } catch (error) {
+ res.status(500).json({ error: "Failed to update scan status" });
+ }
+ });
+
+ // Vulnerabilities
+ this.app.get("/api/vulnerabilities", (req, res) => {
+ try {
+ const targetId = req.query.target_id ? parseInt(req.query.target_id as string) : undefined;
+ const vulnerabilities = getVulnerabilities(targetId);
+ res.json(vulnerabilities);
+ } catch (error) {
+ res.status(500).json({ error: "Failed to fetch vulnerabilities" });
+ }
+ });
+
+ this.app.post("/api/vulnerabilities", (req, res) => {
+ try {
+ const vuln: Vulnerability = req.body;
+ const id = addVulnerability(vuln);
+ this.broadcast({
+ type: "vulnerability_found",
+ vulnerability: { ...vuln, id }
+ });
+ res.json({ id, message: "Vulnerability added successfully" });
+ } catch (error) {
+ res.status(500).json({ error: "Failed to add vulnerability" });
+ }
+ });
+
+ // System status
+ this.app.get("/api/status", (req, res) => {
+ res.json({
+ status: "running",
+ timestamp: new Date().toISOString(),
+ targets_count: getTargets().length,
+ vulnerabilities_count: getVulnerabilities().length,
+ });
+ });
+ }
+
+ private setupWebSocket(): void {
+ this.wss.on("connection", (ws) => {
+ this.clients.add(ws);
+ console.log("New WebSocket client connected");
+
+ ws.on("close", () => {
+ this.clients.delete(ws);
+ console.log("WebSocket client disconnected");
+ });
+
+ ws.on("message", (data) => {
+ try {
+ const message = JSON.parse(data.toString());
+ this.handleWebSocketMessage(ws, message);
+ } catch (error) {
+ console.error("Invalid WebSocket message:", error);
+ }
+ });
+
+ // Send initial data
+ ws.send(JSON.stringify({
+ type: "init",
+ targets: getTargets(),
+ vulnerabilities: getVulnerabilities(),
+ }));
+ });
+ }
+
+ private handleWebSocketMessage(ws: any, message: any): void {
+ switch (message.type) {
+ case "ping":
+ ws.send(JSON.stringify({ type: "pong" }));
+ break;
+ case "get_targets":
+ ws.send(JSON.stringify({
+ type: "targets",
+ data: getTargets(),
+ }));
+ break;
+ // Add more message handlers as needed
+ }
+ }
+
+ private broadcast(message: any): void {
+ const data = JSON.stringify(message);
+ this.clients.forEach((client) => {
+ if (client.readyState === 1) { // WebSocket.OPEN
+ client.send(data);
+ }
+ });
+ }
+
+ private getIndexHTML(): string {
+ return `
+
+
+
+
+
+ AI Bug Bounty Hunter
+
+
+
+
+
+
+
+
+
+
+
0
+
Vulnerabilities Found
+
+
+
+
+
+
+
+
+
Quick Actions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Discovered Vulnerabilities
+
+
+
+
+
+
+
+
+
+
+ `;
+ }
+
+ public start(): Promise {
+ return new Promise((resolve) => {
+ this.server.listen(this.port, () => {
+ console.log(`🚀 AI Bug Bounty Hunter web interface running on http://localhost:${this.port}`);
+ resolve();
+ });
+ });
+ }
+
+ public stop(): Promise {
+ return new Promise((resolve) => {
+ this.server.close(() => {
+ console.log("Web server stopped");
+ resolve();
+ });
+ });
+ }
+
+ public getPort(): number {
+ return this.port;
+ }
+}
\ No newline at end of file
From 9d34226f59fb9108cb90b36fb27cd7bf02b3fcad Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 9 Jul 2025 13:47:17 +0000
Subject: [PATCH 3/3] Final UI updates and port change for web interface
Co-authored-by: drzarak <36883503+drzarak@users.noreply.github.com>
---
codex-cli/Dockerfile | 4 ++--
codex-cli/src/cli.tsx | 6 +++---
codex-cli/src/utils/web-server.ts | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/codex-cli/Dockerfile b/codex-cli/Dockerfile
index 786a29d95ea..9974ab51992 100644
--- a/codex-cli/Dockerfile
+++ b/codex-cli/Dockerfile
@@ -86,8 +86,8 @@ USER node
# Create necessary directories for bug bounty operations
RUN mkdir -p /home/node/.codex/tools /home/node/.codex/results /home/node/.codex/browser_data
-# Expose port 222 for web interface
-EXPOSE 222
+# Expose port 3222 for web interface
+EXPOSE 3222
# Default command starts the bug bounty web server
CMD ["codex", "bugbounty", "server"]
diff --git a/codex-cli/src/cli.tsx b/codex-cli/src/cli.tsx
index e70fc136c8a..26b3ef0fa6d 100644
--- a/codex-cli/src/cli.tsx
+++ b/codex-cli/src/cli.tsx
@@ -69,7 +69,7 @@ const cli = meow(
--full-stdout Do not truncate stdout/stderr from command outputs
Bug Bounty Mode
- $ codex bugbounty server Start web interface on port 222
+ $ codex bugbounty server Start web interface on port 3222
$ codex bugbounty add [description]
Add a new target for scanning
$ codex bugbounty scan
@@ -198,9 +198,9 @@ if (cli.input[0] === "bugbounty") {
switch (subcommand) {
case "server":
console.log(chalk.cyan("🚀 Starting AI Bug Bounty Hunter web interface..."));
- const webServer = new BugBountyWebServer(222);
+ const webServer = new BugBountyWebServer(3222);
await webServer.start();
- console.log(chalk.green(`✅ Web interface available at http://localhost:222`));
+ console.log(chalk.green(`✅ Web interface available at http://localhost:3222`));
console.log(chalk.yellow("Press Ctrl+C to stop the server"));
// Keep the process alive
diff --git a/codex-cli/src/utils/web-server.ts b/codex-cli/src/utils/web-server.ts
index 4b6144200a6..3ca7d781e9f 100644
--- a/codex-cli/src/utils/web-server.ts
+++ b/codex-cli/src/utils/web-server.ts
@@ -27,7 +27,7 @@ export class BugBountyWebServer {
private wss = new WebSocketServer({ server: this.server });
private clients = new Set();
- constructor(private port = 222) {
+ constructor(private port = 3222) {
this.setupMiddleware();
this.setupRoutes();
this.setupWebSocket();