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 + + + +
+
+

AI Bug Bounty Hunter

+

Comprehensive AI-powered security testing platform

+
+ +
+
+
0
+
Active Targets
+
+
+
0
+
Running Scans
+
+
+
0
+
Vulnerabilities Found
+
+
+
0
+
Critical Issues
+
+
+ +
+
+

Add New Target

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ +
+

Quick Actions

+ + + + + +
+
+ +
+
+ + + + +
+ +
+

Targets

+
+
+ + + + + + +
+
+ + + + + `; + } + + 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();