From c8cecd7848f48b19bd28ef59fb9a6706bcb940e1 Mon Sep 17 00:00:00 2001 From: kaitoozawa Date: Fri, 19 Dec 2025 14:43:47 +1000 Subject: [PATCH 01/37] initial version of eslint --- .eslintrc.json | 46 ++ package-lock.json | 1239 ++++++++++++++++++++++++++++++++++++++++- package.json | 3 +- tools/bump-version.sh | 24 - 4 files changed, 1284 insertions(+), 28 deletions(-) create mode 100644 .eslintrc.json delete mode 100755 tools/bump-version.sh diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..9d1e4d0aa --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,46 @@ +{ + "env": { + "browser": true, + "es6": false + }, + "extends": ["eslint:recommended"], + + "parserOptions": { + "ecmaVersion": 5, + "sourceType": "script" + }, + + "ignorePatterns": [ + "core/main/client/lib/**" + ], + + "rules": { + /* Keep this minimal at first. Add project-specific rules after seeing CI logs. */ + }, + + "globals": { + // beef + "beef": "readonly", + + // jQuery / alias + "$": "readonly", + "jQuery": "readonly", + "$j": "readonly", + + // BeEF bootstrapping + "beef_init": "readonly", + + // Existing libraries + "MobileEsp": "readonly", + "evercookie": "readonly", + "swfobject": "readonly", + + // Browser-specific globals (old IE / old Firefox) + "XDomainRequest": "readonly", + "MozWebSocket": "readonly", + "clipboardData": "readonly", + + // Debug + "isDebug": "readonly" + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 69f3aa8db..88387bf6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,21 @@ "version": "0.6.0.0", "license": "GNU General Public License v2.0", "devDependencies": { + "eslint": "^7.32.0", "jsdoc": "^4.0.5", "jsdoc-to-markdown": "^9.1.3" } }, + "node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", @@ -23,14 +34,109 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", - "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/highlight": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", + "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/parser": { "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz", @@ -60,6 +166,51 @@ "node": ">=6.9.0" } }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/@jsdoc/salty": { "version": "0.2.8", "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.8.tgz", @@ -129,6 +280,66 @@ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "dev": true }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -159,12 +370,40 @@ "node": ">=12.17" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -198,6 +437,16 @@ } } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/catharsis": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", @@ -306,6 +555,13 @@ "node": ">=12.17" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/config-master": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/config-master/-/config-master-3.1.0.tgz", @@ -324,6 +580,21 @@ "node": ">=0.10.0" } }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/current-module-paths": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/current-module-paths/-/current-module-paths-1.1.2.tgz", @@ -334,6 +605,31 @@ "node": ">=12.17" } }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/dmd": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/dmd/-/dmd-7.1.1.tgz", @@ -360,6 +656,40 @@ } } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -381,6 +711,240 @@ "node": ">=8" } }, + "node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -397,6 +961,37 @@ "node": ">=8.6.0" } }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -406,6 +1001,19 @@ "reusify": "^1.0.4" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/file-set": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/file-set/-/file-set-5.3.0.tgz", @@ -457,6 +1065,64 @@ } } }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -469,6 +1135,22 @@ "node": ">= 6" } }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -505,6 +1187,62 @@ "node": ">=8" } }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "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, + "license": "ISC" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -514,6 +1252,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -535,6 +1283,44 @@ "node": ">=0.12.0" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/js2xmlparser": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", @@ -647,6 +1433,37 @@ } } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/klaw": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", @@ -656,6 +1473,20 @@ "graceful-fs": "^4.1.9" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -677,6 +1508,20 @@ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, "node_modules/markdown-it": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", @@ -744,6 +1589,19 @@ "node": ">=8.6" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -765,6 +1623,20 @@ "node": ">=10" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "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, + "license": "MIT" + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -781,6 +1653,74 @@ "node": ">=8.0.0" } }, + "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, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -793,6 +1733,36 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/punycode.js": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", @@ -822,6 +1792,29 @@ } ] }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/requizzle": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", @@ -831,6 +1824,16 @@ "lodash": "^4.17.21" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -841,6 +1844,23 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -864,6 +1884,60 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/sort-array": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-5.1.1.tgz", @@ -895,6 +1969,41 @@ "node": ">=0.10.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -919,6 +2028,23 @@ "node": ">=8" } }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/table-layout": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz", @@ -932,6 +2058,37 @@ "node": ">=12.17" } }, + "node_modules/table/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -953,6 +2110,32 @@ "node": ">=8.0" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typical": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/typical/-/typical-7.2.0.tgz", @@ -987,6 +2170,23 @@ "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", "dev": true }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", + "dev": true, + "license": "MIT" + }, "node_modules/walk-back": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.1.tgz", @@ -996,6 +2196,32 @@ "node": ">=12.17" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -1011,6 +2237,13 @@ "node": ">=12.17" } }, + "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, + "license": "ISC" + }, "node_modules/xmlcreate": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", diff --git a/package.json b/package.json index b6edfce2b..7bb6b79b3 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,9 @@ "author": "Wade Alcorn", "license": "GNU General Public License v2.0", "devDependencies": { + "eslint": "^7.32.0", "jsdoc": "^4.0.5", "jsdoc-to-markdown": "^9.1.3" }, "dependencies": {} -} \ No newline at end of file +} diff --git a/tools/bump-version.sh b/tools/bump-version.sh deleted file mode 100755 index 2077a5726..000000000 --- a/tools/bump-version.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -if [[ -z "${1}" || -z "${2}" ]]; then - echo "Error: missing arguments" - exit 1 -fi - -if [[ ! -f beef || ! -f VERSION ]]; then - echo "Error: must be run from within the BeEF root directory" - exit 1 -fi - -echo "Updating version ${1} to ${2}" - -git checkout -b "release/${2}" -sed -i '' -e "s/$1/$2/g" VERSION -sed -i '' -e "s/\"version\": \"$1\"/\"version\": \"$2\"/g" package.json -sed -i '' -e "s/\"version\": \"$1\"/\"version\": \"$2\"/g" package-lock.json -sed -i '' -e "s/\"version\": \"$1\"/\"version\": \"$2\"/g" config.yaml - -git add VERSION package.json package-lock.json config.yaml -git commit -m "Version bump ${2}" -git push --set-upstream origin "release/${2}" -git push From ce10c091800abde3a711188d5becdb7dae65cc36 Mon Sep 17 00:00:00 2001 From: kaitoozawa Date: Wed, 31 Dec 2025 14:10:26 +1000 Subject: [PATCH 02/37] restore bump-version --- tools/bump-version.sh | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100755 tools/bump-version.sh diff --git a/tools/bump-version.sh b/tools/bump-version.sh new file mode 100755 index 000000000..2077a5726 --- /dev/null +++ b/tools/bump-version.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +if [[ -z "${1}" || -z "${2}" ]]; then + echo "Error: missing arguments" + exit 1 +fi + +if [[ ! -f beef || ! -f VERSION ]]; then + echo "Error: must be run from within the BeEF root directory" + exit 1 +fi + +echo "Updating version ${1} to ${2}" + +git checkout -b "release/${2}" +sed -i '' -e "s/$1/$2/g" VERSION +sed -i '' -e "s/\"version\": \"$1\"/\"version\": \"$2\"/g" package.json +sed -i '' -e "s/\"version\": \"$1\"/\"version\": \"$2\"/g" package-lock.json +sed -i '' -e "s/\"version\": \"$1\"/\"version\": \"$2\"/g" config.yaml + +git add VERSION package.json package-lock.json config.yaml +git commit -m "Version bump ${2}" +git push --set-upstream origin "release/${2}" +git push From 7663914fa46f0d8adab6f10ba92d1b2c0f26156b Mon Sep 17 00:00:00 2001 From: kaitoozawa Date: Wed, 31 Dec 2025 14:44:40 +1000 Subject: [PATCH 03/37] JSlint workflow --- .github/workflows/eslint.yml | 82 ++++++++++++++++++++++++++++++++++++ package.json | 4 +- 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/eslint.yml diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml new file mode 100644 index 000000000..cfd83503e --- /dev/null +++ b/.github/workflows/eslint.yml @@ -0,0 +1,82 @@ +name: JavaScript Linting + +on: + pull_request: + branches: [ master ] + +permissions: + contents: read + pull-requests: read + +jobs: + lint-changed-files: + name: Lint Changed JavaScript Files (Blocking) + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Get changed JavaScript files + id: changed-js-files + uses: tj-actions/changed-files@v45 + with: + files: '**/*.js' + files_ignore: | + node_modules/** + core/main/client/lib/** + **/*.min.js + extensions/admin_ui/media/javascript-min/** + separator: ' ' + + - name: Lint changed JavaScript files + if: steps.changed-js-files.outputs.any_changed == 'true' + run: | + echo "Linting ${{ steps.changed-js-files.outputs.all_changed_files_count }} changed JavaScript file(s)" + npx eslint ${{ steps.changed-js-files.outputs.all_changed_files }} + + - name: No JavaScript files changed + if: steps.changed-js-files.outputs.any_changed == 'false' + run: | + echo "No JavaScript files were changed in this PR. Skipping ESLint." + + lint-all-files: + name: Lint All JavaScript Files (Non-blocking Report) + runs-on: ubuntu-latest + continue-on-error: true + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Lint all JavaScript files (report-only) + run: | + echo "Running ESLint on entire codebase" + echo "This is a report-only job and will not block the PR." + npx eslint "**/*.js" || true + + - name: Summary + if: always() + run: | + echo "Full codebase linting completed. Check the logs above for details." + echo "This job does not affect PR merge status." diff --git a/package.json b/package.json index 7bb6b79b3..e8c1ef461 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,9 @@ "version": "0.6.0.0", "description": "The Browser Exploitation Framework Project", "scripts": { - "docs": "./node_modules/.bin/jsdoc -c conf.json" + "docs": "./node_modules/.bin/jsdoc -c conf.json", + "lint": "eslint", + "lint:fix": "eslint --fix" }, "author": "Wade Alcorn", "license": "GNU General Public License v2.0", From 905157edfb5a27c00ffc5943906bc53bb234fb8f Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 16:24:41 +1000 Subject: [PATCH 04/37] TEST: gbmanager spec --- spec/beef/core/hbmanager_spec.rb | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 spec/beef/core/hbmanager_spec.rb diff --git a/spec/beef/core/hbmanager_spec.rb b/spec/beef/core/hbmanager_spec.rb new file mode 100644 index 000000000..c1a00589c --- /dev/null +++ b/spec/beef/core/hbmanager_spec.rb @@ -0,0 +1,41 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require 'spec_helper' + +RSpec.describe BeEF::HBManager do + describe '.get_by_session' do + it 'returns the hooked browser when session exists' do + hb = BeEF::Core::Models::HookedBrowser.create!(session: 'hb_session_123', ip: '127.0.0.1') + + result = described_class.get_by_session('hb_session_123') + + expect(result).to eq(hb) + expect(result.session).to eq('hb_session_123') + end + + it 'returns nil when no hooked browser has the session' do + result = described_class.get_by_session('nonexistent_session') + + expect(result).to be_nil + end + end + + describe '.get_by_id' do + it 'returns the hooked browser when id exists' do + hb = BeEF::Core::Models::HookedBrowser.create!(session: 'hb_by_id', ip: '127.0.0.1') + + result = described_class.get_by_id(hb.id) + + expect(result).to eq(hb) + expect(result.id).to eq(hb.id) + end + + it 'raises when id does not exist' do + expect { described_class.get_by_id(999_999) }.to raise_error(ActiveRecord::RecordNotFound) + end + end +end From 3287d3f49dea4ed32433a0c8d4be3c69befb8458 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 16:24:52 +1000 Subject: [PATCH 05/37] TEST: core main logger spec --- spec/beef/core/main/logger_spec.rb | 62 ++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 spec/beef/core/main/logger_spec.rb diff --git a/spec/beef/core/main/logger_spec.rb b/spec/beef/core/main/logger_spec.rb new file mode 100644 index 000000000..c3481331b --- /dev/null +++ b/spec/beef/core/main/logger_spec.rb @@ -0,0 +1,62 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require 'spec_helper' + +RSpec.describe BeEF::Core::Logger do + let(:logger) { described_class.instance } + let(:log_double) { instance_double(BeEF::Core::Models::Log, save!: true) } + + before do + allow(BeEF::Core::Models::Log).to receive(:create).and_return(log_double) + allow(logger).to receive(:print_debug) + logger.instance_variable_set(:@notifications, nil) + end + + describe '#register' do + it 'creates a log entry with from, event, and hooked_browser_id' do + result = logger.register('Authentication', 'User logged in', 0) + + expect(result).to be true + expect(BeEF::Core::Models::Log).to have_received(:create).with( + hash_including( + logtype: 'Authentication', + event: 'User logged in', + hooked_browser_id: 0 + ) + ) + expect(log_double).to have_received(:save!) + end + + it 'converts hb to integer' do + logger.register('From', 'Event', '42') + + expect(BeEF::Core::Models::Log).to have_received(:create).with( + hash_including(hooked_browser_id: 42) + ) + end + + it 'defaults hb to 0 when not provided' do + logger.register('From', 'Event') + + expect(BeEF::Core::Models::Log).to have_received(:create).with( + hash_including(hooked_browser_id: 0) + ) + end + + it 'raises TypeError when from is not a String' do + expect { logger.register(123, 'Event', 0) }.to raise_error( + TypeError, "'from' is Integer; expected String" + ) + end + + it 'raises TypeError when event is not a String' do + expect { logger.register('From', nil, 0) }.to raise_error( + TypeError, "'event' is NilClass; expected String" + ) + end + end +end From 1b2bd10a0a8d78c2f8f3748654968f4930e64c55 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 16:25:10 +1000 Subject: [PATCH 06/37] TEST: core main constatns browsers spec --- .../beef/core/main/constants/browsers_spec.rb | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 spec/beef/core/main/constants/browsers_spec.rb diff --git a/spec/beef/core/main/constants/browsers_spec.rb b/spec/beef/core/main/constants/browsers_spec.rb new file mode 100644 index 000000000..c28ee66a8 --- /dev/null +++ b/spec/beef/core/main/constants/browsers_spec.rb @@ -0,0 +1,60 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require 'spec_helper' + +RSpec.describe BeEF::Core::Constants::Browsers do + describe 'constants' do + it 'defines short browser codes' do + expect(described_class::FF).to eq('FF') + expect(described_class::C).to eq('C') + expect(described_class::IE).to eq('IE') + expect(described_class::S).to eq('S') + expect(described_class::ALL).to eq('ALL') + expect(described_class::UNKNOWN).to eq('UN') + end + + it 'defines friendly names' do + expect(described_class::FRIENDLY_FF_NAME).to eq('Firefox') + expect(described_class::FRIENDLY_C_NAME).to eq('Chrome') + expect(described_class::FRIENDLY_UN_NAME).to eq('UNKNOWN') + end + end + + describe '.friendly_name' do + it 'returns Firefox for FF' do + expect(described_class.friendly_name(described_class::FF)).to eq('Firefox') + end + + it 'returns Chrome for C' do + expect(described_class.friendly_name(described_class::C)).to eq('Chrome') + end + + it 'returns Internet Explorer for IE' do + expect(described_class.friendly_name(described_class::IE)).to eq('Internet Explorer') + end + + it 'returns Safari for S' do + expect(described_class.friendly_name(described_class::S)).to eq('Safari') + end + + it 'returns MSEdge for E' do + expect(described_class.friendly_name(described_class::E)).to eq('MSEdge') + end + + it 'returns UNKNOWN for UN' do + expect(described_class.friendly_name(described_class::UNKNOWN)).to eq('UNKNOWN') + end + + it 'returns nil for unknown browser code' do + expect(described_class.friendly_name('XX')).to be_nil + end + + it 'returns nil for nil' do + expect(described_class.friendly_name(nil)).to be_nil + end + end +end From fc10884d50ebc0815bdcfb665dc55be63d38a9c1 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 16:25:26 +1000 Subject: [PATCH 07/37] TEST: core main models specs --- .../core/main/models/commandmodule_spec.rb | 26 +++++++++++ .../core/main/models/hookedbrowser_spec.rb | 44 +++++++++++++++++++ spec/beef/core/main/models/log_spec.rb | 42 ++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 spec/beef/core/main/models/commandmodule_spec.rb create mode 100644 spec/beef/core/main/models/hookedbrowser_spec.rb create mode 100644 spec/beef/core/main/models/log_spec.rb diff --git a/spec/beef/core/main/models/commandmodule_spec.rb b/spec/beef/core/main/models/commandmodule_spec.rb new file mode 100644 index 000000000..96a1e272a --- /dev/null +++ b/spec/beef/core/main/models/commandmodule_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require 'spec_helper' + +RSpec.describe BeEF::Core::Models::CommandModule do + describe 'associations' do + it 'has_many commands' do + expect(described_class.reflect_on_association(:commands)).not_to be_nil + expect(described_class.reflect_on_association(:commands).macro).to eq(:has_many) + end + end + + describe '.create' do + it 'creates a command module with name and path' do + mod = described_class.create!(name: 'test_module', path: 'modules/test/') + + expect(mod).to be_persisted + expect(mod.name).to eq('test_module') + expect(mod.path).to eq('modules/test/') + end + end +end diff --git a/spec/beef/core/main/models/hookedbrowser_spec.rb b/spec/beef/core/main/models/hookedbrowser_spec.rb new file mode 100644 index 000000000..eeef52924 --- /dev/null +++ b/spec/beef/core/main/models/hookedbrowser_spec.rb @@ -0,0 +1,44 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require 'spec_helper' + +RSpec.describe BeEF::Core::Models::HookedBrowser do + describe 'associations' do + it 'has_many commands' do + expect(described_class.reflect_on_association(:commands)).not_to be_nil + expect(described_class.reflect_on_association(:commands).macro).to eq(:has_many) + end + + it 'has_many results' do + expect(described_class.reflect_on_association(:results)).not_to be_nil + expect(described_class.reflect_on_association(:results).macro).to eq(:has_many) + end + + it 'has_many logs' do + expect(described_class.reflect_on_association(:logs)).not_to be_nil + expect(described_class.reflect_on_association(:logs).macro).to eq(:has_many) + end + end + + describe '#count!' do + it 'sets count to 1 when count is nil' do + hb = described_class.create!(session: 'count_nil', ip: '127.0.0.1', count: nil) + + hb.count! + + expect(hb.count).to eq(1) + end + + it 'increments count when count is already set' do + hb = described_class.create!(session: 'count_set', ip: '127.0.0.1', count: 3) + + hb.count! + + expect(hb.count).to eq(4) + end + end +end diff --git a/spec/beef/core/main/models/log_spec.rb b/spec/beef/core/main/models/log_spec.rb new file mode 100644 index 000000000..9c2750ceb --- /dev/null +++ b/spec/beef/core/main/models/log_spec.rb @@ -0,0 +1,42 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require 'spec_helper' + +RSpec.describe BeEF::Core::Models::Log do + describe 'associations' do + it 'has_one hooked_browser' do + expect(described_class.reflect_on_association(:hooked_browser)).not_to be_nil + expect(described_class.reflect_on_association(:hooked_browser).macro).to eq(:has_one) + end + end + + describe '.create' do + it 'creates a log with logtype, event, and date' do + log = described_class.create!( + logtype: 'TestSource', + event: 'Test event message', + date: Time.now + ) + + expect(log).to be_persisted + expect(log.logtype).to eq('TestSource') + expect(log.event).to eq('Test event message') + end + + it 'can store hooked_browser_id' do + hb = BeEF::Core::Models::HookedBrowser.create!(session: 'log_hb', ip: '127.0.0.1') + log = described_class.create!( + logtype: 'Hook', + event: 'Browser hooked', + date: Time.now, + hooked_browser_id: hb.id + ) + + expect(log.hooked_browser_id).to eq(hb.id) + end + end +end From a69423fb942ef265ecb964db369880bbdc0f89f8 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 16:38:40 +1000 Subject: [PATCH 08/37] TEST: main autorun specs --- .../core/main/autorun_engine/parser_spec.rb | 119 ++++++++++++++++++ .../main/autorun_engine/rule_loader_spec.rb | 83 ++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 spec/beef/core/main/autorun_engine/parser_spec.rb create mode 100644 spec/beef/core/main/autorun_engine/rule_loader_spec.rb diff --git a/spec/beef/core/main/autorun_engine/parser_spec.rb b/spec/beef/core/main/autorun_engine/parser_spec.rb new file mode 100644 index 000000000..9ab37d85a --- /dev/null +++ b/spec/beef/core/main/autorun_engine/parser_spec.rb @@ -0,0 +1,119 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require 'spec_helper' + +RSpec.describe BeEF::Core::AutorunEngine::Parser do + let(:parser) { described_class.instance } + + def valid_minimal_args + { + name: 'Test Rule', + author: 'Test Author', + browser: 'ALL', + browser_version: 'ALL', + os: 'Windows', + os_version: 'ALL', + modules: [], + execution_order: [], + execution_delay: [], + chain_mode: 'sequential' + } + end + + describe '#parse' do + it 'returns true for valid minimal args (empty modules)' do + result = parser.parse( + valid_minimal_args[:name], + valid_minimal_args[:author], + valid_minimal_args[:browser], + valid_minimal_args[:browser_version], + valid_minimal_args[:os], + valid_minimal_args[:os_version], + valid_minimal_args[:modules], + valid_minimal_args[:execution_order], + valid_minimal_args[:execution_delay], + valid_minimal_args[:chain_mode] + ) + + expect(result).to be true + end + + it 'raises ArgumentError for empty name' do + expect do + parser.parse('', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'sequential') + end.to raise_error(ArgumentError, /Invalid rule name/) + end + + it 'raises ArgumentError for nil name' do + expect do + parser.parse(nil, 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'sequential') + end.to raise_error(ArgumentError, /Invalid rule name/) + end + + it 'raises ArgumentError for empty author' do + expect do + parser.parse('Name', '', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'sequential') + end.to raise_error(ArgumentError, /Invalid author name/) + end + + it 'raises ArgumentError for invalid chain_mode' do + expect do + parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'invalid') + end.to raise_error(ArgumentError, /Invalid chain_mode definition/) + end + + it 'raises ArgumentError for invalid os' do + expect do + parser.parse('Name', 'Author', 'ALL', 'ALL', 'InvalidOS', 'ALL', [], [], [], 'sequential') + end.to raise_error(ArgumentError, /Invalid os definition/) + end + + it 'raises ArgumentError when execution_delay size does not match modules size' do + expect do + parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [{ 'name' => 'a' }], [1], [], 'sequential') + end.to raise_error(ArgumentError, /execution_delay.*consistent with number of modules/) + end + + it 'raises ArgumentError when execution_order size does not match modules size' do + expect do + parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [{ 'name' => 'a' }], [], [0], 'sequential') + end.to raise_error(ArgumentError, /execution_order.*consistent with number of modules/) + end + + it 'raises TypeError when execution_delay contains non-Integer' do + # Use one module so sizes match; then type check runs on execution_delay + expect do + parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [{}], [1], ['not_an_int'], 'sequential') + end.to raise_error(TypeError, /execution_delay.*Integers/) + end + + it 'raises TypeError when execution_order contains non-Integer' do + # Use one module so sizes match; then type check runs on execution_order + expect do + parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [{}], ['x'], [0], 'sequential') + end.to raise_error(TypeError, /execution_order.*Integers/) + end + + it 'raises ArgumentError for invalid browser' do + expect do + parser.parse('Name', 'Author', 'XX', 'ALL', 'Windows', 'ALL', [], [], [], 'sequential') + end.to raise_error(ArgumentError, /Invalid browser definition/) + end + + it 'accepts nested-forward as chain_mode' do + result = parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'nested-forward') + expect(result).to be true + end + + it 'accepts valid os values' do + %w[Linux Windows OSX Android iOS BlackBerry ALL].each do |os| + result = parser.parse('Name', 'Author', 'ALL', 'ALL', os, 'ALL', [], [], [], 'sequential') + expect(result).to be true + end + end + end +end diff --git a/spec/beef/core/main/autorun_engine/rule_loader_spec.rb b/spec/beef/core/main/autorun_engine/rule_loader_spec.rb new file mode 100644 index 000000000..a27560b68 --- /dev/null +++ b/spec/beef/core/main/autorun_engine/rule_loader_spec.rb @@ -0,0 +1,83 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require 'spec_helper' + +RSpec.describe BeEF::Core::AutorunEngine::RuleLoader do + let(:loader) { described_class.instance } + + def valid_rule_data + { + 'name' => 'Test Rule', + 'author' => 'Test Author', + 'browser' => 'ALL', + 'browser_version' => 'ALL', + 'os' => 'Windows', + 'os_version' => 'ALL', + 'modules' => [], + 'execution_order' => [], + 'execution_delay' => [], + 'chain_mode' => 'sequential' + } + end + + before do + allow(loader).to receive(:print_error) + allow(loader).to receive(:print_info) + allow(loader).to receive(:print_more) + end + + describe '#load_rule_json' do + it 'returns success and rule_id when parse succeeds and rule is new' do + # Parser will succeed with valid minimal data; no existing rule + result = loader.load_rule_json(valid_rule_data) + + expect(result['success']).to be true + expect(result).to have_key('rule_id') + expect(result['rule_id']).to be_a(Integer) + end + + it 'returns success false and error when parse raises' do + allow(BeEF::Core::AutorunEngine::Parser.instance).to receive(:parse).and_raise(ArgumentError.new('Invalid rule name')) + + result = loader.load_rule_json(valid_rule_data.merge('name' => 'x')) + + expect(result['success']).to be false + expect(result['error']).to include('Invalid rule name') + end + + it 'returns success false and error when rule already exists' do + # Create the rule first so it already exists + BeEF::Core::Models::Rule.create!( + name: 'Duplicate Rule', + author: 'Test Author', + browser: 'ALL', + browser_version: 'ALL', + os: 'Windows', + os_version: 'ALL', + modules: [].to_json, + execution_order: [].to_s, + execution_delay: [].to_s, + chain_mode: 'sequential' + ) + + result = loader.load_rule_json( + valid_rule_data.merge('name' => 'Duplicate Rule') + ) + + expect(result['success']).to be false + expect(result['error']).to include('Duplicate rule already exists') + end + + it 'uses default chain_mode sequential when missing' do + data = valid_rule_data.except('chain_mode') + result = loader.load_rule_json(data) + + expect(result['success']).to be true + expect(result).to have_key('rule_id') + end + end +end From 83d2b2ff761c17b16750e72a17d2e144e124405a Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 16:38:50 +1000 Subject: [PATCH 09/37] TEST: main models command spec --- spec/beef/core/main/models/command_spec.rb | 123 +++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 spec/beef/core/main/models/command_spec.rb diff --git a/spec/beef/core/main/models/command_spec.rb b/spec/beef/core/main/models/command_spec.rb new file mode 100644 index 000000000..2cec84b98 --- /dev/null +++ b/spec/beef/core/main/models/command_spec.rb @@ -0,0 +1,123 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require 'spec_helper' + +RSpec.describe BeEF::Core::Models::Command do + describe 'associations' do + it 'has_many results' do + expect(described_class.reflect_on_association(:results)).not_to be_nil + expect(described_class.reflect_on_association(:results).macro).to eq(:has_many) + end + + it 'has_one command_module' do + expect(described_class.reflect_on_association(:command_module)).not_to be_nil + expect(described_class.reflect_on_association(:command_module).macro).to eq(:has_one) + end + + it 'has_one hooked_browser' do + expect(described_class.reflect_on_association(:hooked_browser)).not_to be_nil + expect(described_class.reflect_on_association(:hooked_browser).macro).to eq(:has_one) + end + end + + describe '.show_status' do + it 'returns ERROR for status -1' do + expect(described_class.show_status(-1)).to eq('ERROR') + end + + it 'returns SUCCESS for status 1' do + expect(described_class.show_status(1)).to eq('SUCCESS') + end + + it 'returns UNKNOWN for status 0' do + expect(described_class.show_status(0)).to eq('UNKNOWN') + end + + it 'returns UNKNOWN for any other status' do + expect(described_class.show_status(2)).to eq('UNKNOWN') + expect(described_class.show_status(99)).to eq('UNKNOWN') + end + end + + describe '.save_result' do + let(:hooked_browser) { BeEF::Core::Models::HookedBrowser.create!(session: 'cmd_save_session', ip: '127.0.0.1') } + let(:command_module) { BeEF::Core::Models::CommandModule.create!(name: 'cmd_save_mod', path: 'modules/test/') } + let(:command) do + described_class.create!( + hooked_browser_id: hooked_browser.id, + command_module_id: command_module.id + ) + end + + before do + allow(BeEF::Core::Logger.instance).to receive(:register) + allow(described_class).to receive(:print_info) + end + + it 'creates a Result and returns true when all args are valid' do + result = described_class.save_result( + 'cmd_save_session', + command.id, + 'Friendly Name', + { 'output' => 'data' }, + 1 + ) + + expect(result).to be true + created = BeEF::Core::Models::Result.last + expect(created).not_to be_nil + expect(created.command_id).to eq(command.id) + expect(created.hooked_browser_id).to eq(hooked_browser.id) + expect(created.status).to eq(1) + expect(JSON.parse(created.data)).to eq({ 'output' => 'data' }) + end + + it 'raises TypeError when hook_session_id is not a String' do + expect do + described_class.save_result(123, command.id, 'Name', {}, 1) + end.to raise_error(TypeError, '"hook_session_id" needs to be a string') + end + + it 'raises TypeError when command_id is not an Integer' do + expect do + described_class.save_result('cmd_save_session', '1', 'Name', {}, 1) + end.to raise_error(TypeError, '"command_id" needs to be an integer') + end + + it 'raises TypeError when command_friendly_name is not a String' do + expect do + described_class.save_result('cmd_save_session', command.id, 123, {}, 1) + end.to raise_error(TypeError, '"command_friendly_name" needs to be a string') + end + + it 'raises TypeError when result is not a Hash' do + expect do + described_class.save_result('cmd_save_session', command.id, 'Name', 'string', 1) + end.to raise_error(TypeError, '"result" needs to be a hash') + end + + it 'raises TypeError when status is not an Integer' do + expect do + described_class.save_result('cmd_save_session', command.id, 'Name', {}, '1') + end.to raise_error(TypeError, '"status" needs to be an integer') + end + + it 'raises TypeError when hooked_browser is not found for session' do + expect do + described_class.save_result('nonexistent_session', command.id, 'Name', {}, 1) + end.to raise_error(TypeError, 'hooked_browser is nil') + end + + it 'raises TypeError when command is not found for id and hooked_browser' do + other_hb = BeEF::Core::Models::HookedBrowser.create!(session: 'other_session', ip: '127.0.0.1') + + expect do + described_class.save_result('other_session', command.id, 'Name', {}, 1) + end.to raise_error(TypeError, 'command is nil') + end + end +end From 51afa04e6b2b53fc5411786fce048c3dc12ad95d Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 16:40:39 +1000 Subject: [PATCH 10/37] TEST: main constants specs --- .../beef/core/main/constants/hardware_spec.rb | 69 ++++++++++++++++++ spec/beef/core/main/constants/os_spec.rb | 73 +++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 spec/beef/core/main/constants/hardware_spec.rb create mode 100644 spec/beef/core/main/constants/os_spec.rb diff --git a/spec/beef/core/main/constants/hardware_spec.rb b/spec/beef/core/main/constants/hardware_spec.rb new file mode 100644 index 000000000..1846d0c4c --- /dev/null +++ b/spec/beef/core/main/constants/hardware_spec.rb @@ -0,0 +1,69 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require 'spec_helper' + +RSpec.describe BeEF::Core::Constants::Hardware do + describe 'constants' do + it 'defines hardware UA strings and image paths' do + expect(described_class::HW_IPHONE_UA_STR).to eq('iPhone') + expect(described_class::HW_IPAD_UA_STR).to eq('iPad') + expect(described_class::HW_BLACKBERRY_UA_STR).to eq('BlackBerry') + expect(described_class::HW_ALL_UA_STR).to eq('All') + expect(described_class::HW_UNKNOWN_IMG).to eq('pc.png') + end + end + + describe '.match_hardware' do + it 'returns iPhone for iphone-like strings' do + expect(described_class.match_hardware('iPhone')).to eq('iPhone') + expect(described_class.match_hardware('iPhone OS')).to eq('iPhone') + end + + it 'returns iPad for ipad-like strings' do + expect(described_class.match_hardware('iPad')).to eq('iPad') + end + + it 'returns iPod for ipod-like strings' do + expect(described_class.match_hardware('iPod')).to eq('iPod') + end + + it 'returns BlackBerry for blackberry-like strings' do + expect(described_class.match_hardware('BlackBerry')).to eq('BlackBerry') + end + + it 'returns Windows Phone for windows phone-like strings' do + expect(described_class.match_hardware('Windows Phone')).to eq('Windows Phone') + end + + it 'returns Kindle for kindle-like strings' do + expect(described_class.match_hardware('Kindle')).to eq('Kindle') + end + + it 'returns Nokia for nokia-like strings' do + expect(described_class.match_hardware('Nokia')).to eq('Nokia') + end + + it 'returns HTC for htc-like strings' do + expect(described_class.match_hardware('HTC')).to eq('HTC') + end + + it 'returns Nexus for google-like strings' do + expect(described_class.match_hardware('Google Nexus')).to eq('Nexus') + end + + it 'is case insensitive' do + expect(described_class.match_hardware('IPHONE')).to eq('iPhone') + expect(described_class.match_hardware('ipad')).to eq('iPad') + expect(described_class.match_hardware('BLACKBERRY')).to eq('BlackBerry') + end + + it 'returns ALL for unknown hardware strings' do + expect(described_class.match_hardware('UnknownDevice')).to eq('ALL') + expect(described_class.match_hardware('')).to eq('ALL') + end + end +end diff --git a/spec/beef/core/main/constants/os_spec.rb b/spec/beef/core/main/constants/os_spec.rb new file mode 100644 index 000000000..5cba824e9 --- /dev/null +++ b/spec/beef/core/main/constants/os_spec.rb @@ -0,0 +1,73 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require 'spec_helper' + +RSpec.describe BeEF::Core::Constants::Os do + describe 'constants' do + it 'defines OS UA strings and image paths' do + expect(described_class::OS_WINDOWS_UA_STR).to eq('Windows') + expect(described_class::OS_LINUX_UA_STR).to eq('Linux') + expect(described_class::OS_MAC_UA_STR).to eq('Mac') + expect(described_class::OS_ANDROID_UA_STR).to eq('Android') + expect(described_class::OS_ALL_UA_STR).to eq('All') + expect(described_class::OS_UNKNOWN_IMG).to eq('unknown.png') + end + end + + describe '.match_os' do + it 'returns Windows for win-like strings' do + expect(described_class.match_os('Windows')).to eq('Windows') + expect(described_class.match_os('Windows NT')).to eq('Windows') + expect(described_class.match_os('Win32')).to eq('Windows') + end + + it 'returns Linux for lin-like strings' do + expect(described_class.match_os('Linux')).to eq('Linux') + expect(described_class.match_os('Lin')).to eq('Linux') + end + + it 'returns Mac for os x, osx, mac-like strings' do + expect(described_class.match_os('Mac OS X')).to eq('Mac') + expect(described_class.match_os('OSX')).to eq('Mac') + expect(described_class.match_os('Macintosh')).to eq('Mac') + end + + it 'returns iOS for iphone, ipad, ipod' do + expect(described_class.match_os('iPhone')).to eq('iOS') + expect(described_class.match_os('iPad')).to eq('iOS') + expect(described_class.match_os('iPod')).to eq('iOS') + expect(described_class.match_os('iOS')).to eq('iOS') + end + + it 'returns Android for android-like strings' do + expect(described_class.match_os('Android')).to eq('Android') + end + + it 'returns BlackBerry for blackberry-like strings' do + expect(described_class.match_os('BlackBerry')).to eq('BlackBerry') + end + + it 'returns QNX for qnx-like strings' do + expect(described_class.match_os('QNX')).to eq('QNX') + end + + it 'returns SunOS for sun-like strings' do + expect(described_class.match_os('SunOS')).to eq('SunOS') + end + + it 'is case insensitive' do + expect(described_class.match_os('WINDOWS')).to eq('Windows') + expect(described_class.match_os('linux')).to eq('Linux') + expect(described_class.match_os('ANDROID')).to eq('Android') + end + + it 'returns ALL for unknown OS strings' do + expect(described_class.match_os('UnknownOS')).to eq('ALL') + expect(described_class.match_os('')).to eq('ALL') + end + end +end From d27fba46b8d684111f323421d3eed179ee436688 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 16:40:54 +1000 Subject: [PATCH 11/37] TEST: main constatns commandmodule spec --- .../core/main/constants/commandmodule_spec.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 spec/beef/core/main/constants/commandmodule_spec.rb diff --git a/spec/beef/core/main/constants/commandmodule_spec.rb b/spec/beef/core/main/constants/commandmodule_spec.rb new file mode 100644 index 000000000..eb94fc00a --- /dev/null +++ b/spec/beef/core/main/constants/commandmodule_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require 'spec_helper' + +RSpec.describe BeEF::Core::Constants::CommandModule do + describe 'constants' do + it 'defines verified working status values' do + expect(described_class::VERIFIED_WORKING).to eq(0) + expect(described_class::VERIFIED_UNKNOWN).to eq(1) + expect(described_class::VERIFIED_USER_NOTIFY).to eq(2) + expect(described_class::VERIFIED_NOT_WORKING).to eq(3) + end + end +end From dfce4728d5b2c4a4a3753ffd1cf6169a260c4deb Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 16:54:15 +1000 Subject: [PATCH 12/37] TEST: updates to base and crpto specs --- spec/beef/core/filter/base_spec.rb | 138 +++++++++++++++++++++++++++++ spec/beef/core/main/crypto_spec.rb | 1 + 2 files changed, 139 insertions(+) diff --git a/spec/beef/core/filter/base_spec.rb b/spec/beef/core/filter/base_spec.rb index 6e54d0dea..04275ecc8 100644 --- a/spec/beef/core/filter/base_spec.rb +++ b/spec/beef/core/filter/base_spec.rb @@ -307,4 +307,142 @@ RSpec.describe BeEF::Filters do end end end + + describe '.is_valid_ip?' do + it 'returns false for nil, empty, or non-string' do + expect(BeEF::Filters.is_valid_ip?(nil)).to be(false) + expect(BeEF::Filters.is_valid_ip?('')).to be(false) + end + + it 'returns true for valid IPv4' do + expect(BeEF::Filters.is_valid_ip?('127.0.0.1')).to be(true) + expect(BeEF::Filters.is_valid_ip?('192.168.1.1')).to be(true) + expect(BeEF::Filters.is_valid_ip?('10.0.0.1')).to be(true) + expect(BeEF::Filters.is_valid_ip?('0.0.0.0')).to be(true) + end + + it 'returns false for invalid IPv4' do + expect(BeEF::Filters.is_valid_ip?('256.1.1.1')).to be(false) + expect(BeEF::Filters.is_valid_ip?('1.2.3')).to be(false) + expect(BeEF::Filters.is_valid_ip?('not.an.ip')).to be(false) + end + + it 'accepts :ipv4 version' do + expect(BeEF::Filters.is_valid_ip?('127.0.0.1', :ipv4)).to be(true) + expect(BeEF::Filters.is_valid_ip?('256.1.1.1', :ipv4)).to be(false) + end + + it 'accepts :both version (default)' do + expect(BeEF::Filters.is_valid_ip?('127.0.0.1')).to be(true) + end + end + + describe '.is_valid_private_ip?' do + it 'returns false when ip is not valid' do + expect(BeEF::Filters.is_valid_private_ip?(nil)).to be(false) + expect(BeEF::Filters.is_valid_private_ip?('8.8.8.8')).to be(false) + end + + it 'returns true for 127.x (localhost)' do + expect(BeEF::Filters.is_valid_private_ip?('127.0.0.1')).to be(true) + end + + it 'returns true for 192.168.x' do + expect(BeEF::Filters.is_valid_private_ip?('192.168.1.1')).to be(true) + end + + it 'returns true for 10.x' do + expect(BeEF::Filters.is_valid_private_ip?('10.0.0.1')).to be(true) + end + + it 'returns false for public IPv4' do + expect(BeEF::Filters.is_valid_private_ip?('8.8.8.8')).to be(false) + end + end + + describe '.is_valid_port?' do + it 'returns true for valid port range' do + expect(BeEF::Filters.is_valid_port?(1)).to be(true) + expect(BeEF::Filters.is_valid_port?('80')).to be(true) + expect(BeEF::Filters.is_valid_port?(65535)).to be(true) + end + + it 'returns false for 0 or negative' do + expect(BeEF::Filters.is_valid_port?(0)).to be(false) + expect(BeEF::Filters.is_valid_port?('0')).to be(false) + end + + it 'returns false for port above 65535' do + expect(BeEF::Filters.is_valid_port?(65536)).to be(false) + end + end + + describe '.is_valid_domain?' do + it 'returns false for nil or empty' do + expect(BeEF::Filters.is_valid_domain?(nil)).to be(false) + expect(BeEF::Filters.is_valid_domain?('')).to be(false) + end + + it 'returns true for valid domain format' do + expect(BeEF::Filters.is_valid_domain?('example.com')).to be(true) + expect(BeEF::Filters.is_valid_domain?('sub.example.co.uk')).to be(true) + end + + it 'returns false for invalid domain format' do + expect(BeEF::Filters.is_valid_domain?('no-tld')).to be(false) + expect(BeEF::Filters.is_valid_domain?('.leading')).to be(false) + end + end + + describe '.has_valid_browser_details_chars?' do + it 'returns false for nil or empty' do + expect(BeEF::Filters.has_valid_browser_details_chars?(nil)).to be(false) + expect(BeEF::Filters.has_valid_browser_details_chars?('')).to be(false) + end + + it 'returns false when string only has allowed chars' do + # Method returns true when regex matches (invalid char found); false when only valid chars + expect(BeEF::Filters.has_valid_browser_details_chars?('abc')).to be(false) + expect(BeEF::Filters.has_valid_browser_details_chars?('a-b (c)')).to be(false) + end + + it 'returns true when string contains disallowed character' do + expect(BeEF::Filters.has_valid_browser_details_chars?('ab@c')).to be(true) + end + end + + describe '.has_valid_base_chars?' do + it 'returns false for nil or empty' do + expect(BeEF::Filters.has_valid_base_chars?(nil)).to be(false) + expect(BeEF::Filters.has_valid_base_chars?('')).to be(false) + end + + it 'returns true when string only has printable (and registered symbol)' do + expect(BeEF::Filters.has_valid_base_chars?('abc')).to be(true) + expect(BeEF::Filters.has_valid_base_chars?('Hello 123')).to be(true) + end + + it 'returns false when string has non-printable character' do + expect(BeEF::Filters.has_valid_base_chars?("ab\x00c")).to be(false) + end + end + + describe '.is_valid_yes_no?' do + it 'returns true for Yes and No (case insensitive)' do + expect(BeEF::Filters.is_valid_yes_no?('Yes')).to be(true) + expect(BeEF::Filters.is_valid_yes_no?('No')).to be(true) + expect(BeEF::Filters.is_valid_yes_no?('yes')).to be(true) + expect(BeEF::Filters.is_valid_yes_no?('no')).to be(true) + end + + it 'returns false for other values' do + expect(BeEF::Filters.is_valid_yes_no?('')).to be(false) + expect(BeEF::Filters.is_valid_yes_no?('maybe')).to be(false) + expect(BeEF::Filters.is_valid_yes_no?('1')).to be(false) + end + + it 'returns false when string has non-printable character' do + expect(BeEF::Filters.is_valid_yes_no?("Yes\x00")).to be(false) + end + end end diff --git a/spec/beef/core/main/crypto_spec.rb b/spec/beef/core/main/crypto_spec.rb index bfe4ccfbf..048d75c3e 100644 --- a/spec/beef/core/main/crypto_spec.rb +++ b/spec/beef/core/main/crypto_spec.rb @@ -59,6 +59,7 @@ RSpec.describe 'BeEF::Core::Crypto' do it 'raises TypeError for invalid inputs' do expect { BeEF::Core::Crypto.random_hex_string('invalid') }.to raise_error(TypeError) expect { BeEF::Core::Crypto.random_hex_string(0) }.to raise_error(TypeError, /Invalid length/) + expect { BeEF::Core::Crypto.random_hex_string(-1) }.to raise_error(TypeError, /Invalid length/) end end From 035f17a5c797f8308c2c2e079663e54e962095e5 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 17:10:28 +1000 Subject: [PATCH 13/37] TEST: core main various specs --- .../core/main/autorun_engine/engine_spec.rb | 117 +++++++++++ spec/beef/core/main/console/banners_spec.rb | 196 ++++++++++++++++++ .../core/main/handlers/hookedbrowsers_spec.rb | 51 +++-- spec/beef/core/ruby/security_spec.rb | 26 +++ 4 files changed, 364 insertions(+), 26 deletions(-) create mode 100644 spec/beef/core/main/autorun_engine/engine_spec.rb create mode 100644 spec/beef/core/main/console/banners_spec.rb diff --git a/spec/beef/core/main/autorun_engine/engine_spec.rb b/spec/beef/core/main/autorun_engine/engine_spec.rb new file mode 100644 index 000000000..ed708ed53 --- /dev/null +++ b/spec/beef/core/main/autorun_engine/engine_spec.rb @@ -0,0 +1,117 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Example: unit specs for AutorunEngine::Engine using mocks instead of a real server/DB. +# + +require 'spec_helper' + +RSpec.describe BeEF::Core::AutorunEngine::Engine do + let(:engine) { described_class.instance } + let(:config) { BeEF::Core::Configuration.instance } + + before do + allow(engine).to receive(:print_debug) + allow(engine).to receive(:print_info) + allow(engine).to receive(:print_more) + allow(engine).to receive(:print_error) + end + + # Fake rule object (could be a double or a persisted Rule with minimal attributes) + def rule_with(browser: 'ALL', browser_version: 'ALL', os: 'ALL', os_version: 'ALL') + double( + 'Rule', + id: 1, + browser: browser, + browser_version: browser_version, + os: os, + os_version: os_version + ) + end + + describe '#zombie_matches_rule?' do + it 'returns false when rule is nil' do + expect(engine.zombie_matches_rule?('FF', '41', 'Windows', '7', nil)).to be false + end + + it 'returns true when rule is ALL for browser and OS' do + rule = rule_with(browser: 'ALL', browser_version: 'ALL', os: 'ALL', os_version: 'ALL') + allow(engine).to receive(:zombie_browser_matches_rule?).with('FF', '41', rule).and_return(true) + allow(engine).to receive(:zombie_os_matches_rule?).with('Windows', '7', rule).and_return(true) + expect(engine.zombie_matches_rule?('FF', '41', 'Windows', '7', rule)).to be true + end + + it 'returns false when browser does not match' do + rule = rule_with(browser: 'FF', browser_version: '>= 41', os: 'ALL', os_version: 'ALL') + allow(engine).to receive(:zombie_browser_matches_rule?).with('FF', '41', rule).and_return(false) + expect(engine.zombie_matches_rule?('FF', '41', 'Windows', '7', rule)).to be false + end + + it 'returns false when OS does not match' do + rule = rule_with(browser: 'ALL', browser_version: 'ALL', os: 'Windows', os_version: '7') + allow(engine).to receive(:zombie_browser_matches_rule?).with('FF', '41', rule).and_return(true) + allow(engine).to receive(:zombie_os_matches_rule?).with('Windows', '7', rule).and_return(false) + expect(engine.zombie_matches_rule?('FF', '41', 'Windows', '7', rule)).to be false + end + end + + describe '#zombie_os_matches_rule?' do + it 'returns false when rule is nil' do + expect(engine.zombie_os_matches_rule?('Windows', '7', nil)).to be false + end + + it 'returns true when rule os is ALL' do + rule = double('Rule', os: 'ALL', os_version: 'ALL') + expect(engine.zombie_os_matches_rule?('Windows', '7', rule)).to be true + end + + it 'returns false when hook os does not match rule os' do + rule = double('Rule', os: 'Linux', os_version: 'ALL') + expect(engine.zombie_os_matches_rule?('Windows', '7', rule)).to be false + end + + it 'returns true when rule os matches and os_version is ALL' do + rule = double('Rule', os: 'Windows', os_version: 'ALL') + expect(engine.zombie_os_matches_rule?('Windows', '7', rule)).to be true + end + end + + describe '#zombie_browser_matches_rule?' do + it 'returns false when rule is nil' do + expect(engine.zombie_browser_matches_rule?('FF', '41', nil)).to be false + end + + it 'returns true when rule browser is ALL and version is ALL' do + rule = double('Rule', browser: 'ALL', browser_version: 'ALL') + expect(engine.zombie_browser_matches_rule?('FF', '41', rule)).to be true + end + + it 'returns true when rule browser matches and version is ALL' do + rule = double('Rule', browser: 'FF', browser_version: 'ALL') + expect(engine.zombie_browser_matches_rule?('FF', '41', rule)).to be true + end + + it 'returns false when rule browser does not match' do + rule = double('Rule', browser: 'IE', browser_version: 'ALL') + expect(engine.zombie_browser_matches_rule?('FF', '41', rule)).to be false + end + end + + describe '#find_matching_rules_for_zombie' do + it 'returns nil when no rules exist' do + allow(BeEF::Core::Models::Rule).to receive(:all).and_return([]) + expect(engine.find_matching_rules_for_zombie('FF', '41', 'Windows', '7')).to be_nil + end + + it 'returns matching rule ids when rules match zombie' do + rule1 = double('Rule', id: 1, name: 'Rule1', browser: 'ALL', browser_version: 'ALL', os: 'ALL', os_version: 'ALL') + rule2 = double('Rule', id: 2, name: 'Rule2', browser: 'IE', browser_version: 'ALL', os: 'ALL', os_version: 'ALL') + allow(BeEF::Core::Models::Rule).to receive(:all).and_return([rule1, rule2]) + allow(engine).to receive(:zombie_matches_rule?).with('FF', '41', 'Windows', '7', rule1).and_return(true) + allow(engine).to receive(:zombie_matches_rule?).with('FF', '41', 'Windows', '7', rule2).and_return(false) + expect(engine.find_matching_rules_for_zombie('FF', '41', 'Windows', '7')).to eq([1]) + end + end +end diff --git a/spec/beef/core/main/console/banners_spec.rb b/spec/beef/core/main/console/banners_spec.rb new file mode 100644 index 000000000..e27b7cc8f --- /dev/null +++ b/spec/beef/core/main/console/banners_spec.rb @@ -0,0 +1,196 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require 'spec_helper' + +RSpec.describe BeEF::Core::Console::Banners do + let(:config) { BeEF::Core::Configuration.instance } + + before do + allow(described_class).to receive(:print_info) + allow(described_class).to receive(:print_more) + end + + describe '.print_welcome_msg' do + it 'calls print_info with version from config' do + allow(config).to receive(:get).with('beef.version').and_return('1.0.0') + described_class.print_welcome_msg + expect(described_class).to have_received(:print_info).with('Browser Exploitation Framework (BeEF) 1.0.0') + end + + it 'calls print_more with project links' do + allow(config).to receive(:get).with('beef.version').and_return('1.0.0') + described_class.print_welcome_msg + expect(described_class).to have_received(:print_more).with(a_string_including('@beefproject')) + expect(described_class).to have_received(:print_more).with(a_string_including('beefproject.com')) + expect(described_class).to have_received(:print_more).with(a_string_including('github.com/beefproject')) + end + + it 'calls print_info with project creator' do + allow(config).to receive(:get).with('beef.version').and_return('1.0.0') + described_class.print_welcome_msg + expect(described_class).to have_received(:print_info).with(a_string_including('Wade Alcorn')) + expect(described_class).to have_received(:print_info).with(a_string_including('@WadeAlcorn')) + end + end + + describe '.print_network_interfaces_count' do + it 'uses config local_host and sets interfaces when host is 0.0.0.0' do + allow(config).to receive(:local_host).and_return('0.0.0.0') + mock_addrs = [double('Addr', ip_address: '127.0.0.1', ipv4?: true), double('Addr', ip_address: '192.168.1.1', ipv4?: true)] + allow(Socket).to receive(:ip_address_list).and_return(mock_addrs) + described_class.print_network_interfaces_count + expect(described_class.interfaces).to eq(['127.0.0.1', '192.168.1.1']) + expect(described_class).to have_received(:print_info).with('2 network interfaces were detected.') + end + + it 'sets single interface when host is not 0.0.0.0' do + allow(config).to receive(:local_host).and_return('192.168.1.1') + described_class.print_network_interfaces_count + expect(described_class.interfaces).to eq(['192.168.1.1']) + expect(described_class).to have_received(:print_info).with('1 network interfaces were detected.') + end + end + + describe '.print_loaded_extensions' do + it 'calls print_info with count from Extensions.get_loaded' do + allow(BeEF::Extensions).to receive(:get_loaded).and_return({ 'AdminUI' => { 'name' => 'Admin UI' }, 'DNS' => { 'name' => 'DNS' } }) + described_class.print_loaded_extensions + expect(described_class).to have_received(:print_info).with('2 extensions enabled:') + expect(described_class).to have_received(:print_more).with(a_string_including('Admin UI')) + expect(described_class).to have_received(:print_more).with(a_string_including('DNS')) + end + + it 'handles empty extensions' do + allow(BeEF::Extensions).to receive(:get_loaded).and_return({}) + described_class.print_loaded_extensions + expect(described_class).to have_received(:print_info).with('0 extensions enabled:') + end + end + + describe '.print_loaded_modules' do + it 'calls print_info with count from Modules.get_enabled' do + enabled = double('Relation', count: 42) + allow(BeEF::Modules).to receive(:get_enabled).and_return(enabled) + described_class.print_loaded_modules + expect(described_class).to have_received(:print_info).with('42 modules enabled.') + end + end + + describe '.print_ascii_art' do + it 'reads and puts file content when beef.ascii exists' do + allow(File).to receive(:exist?).with('core/main/console/beef.ascii').and_return(true) + io = StringIO.new("BEEF\nASCII\n") + allow(File).to receive(:open).with('core/main/console/beef.ascii', 'r').and_yield(io) + allow(described_class).to receive(:puts) + described_class.print_ascii_art + expect(described_class).to have_received(:puts).with("BEEF\n") + expect(described_class).to have_received(:puts).with("ASCII\n") + end + + it 'does nothing when beef.ascii does not exist' do + allow(File).to receive(:exist?).with('core/main/console/beef.ascii').and_return(false) + expect(File).not_to receive(:open) + described_class.print_ascii_art + end + end + + describe '.print_network_interfaces_routes' do + before do + described_class.interfaces = ['127.0.0.1', '192.168.1.1'] + end + + it 'prints hook and UI URL for each interface when admin_ui enabled' do + allow(config).to receive(:local_proto).and_return('http') + allow(config).to receive(:hook_file_path).and_return('/hook.js') + allow(config).to receive(:get).with('beef.extension.admin_ui.enable').and_return(true) + allow(config).to receive(:get).with('beef.extension.admin_ui.base_path').and_return('/ui') + allow(config).to receive(:local_port).and_return(3000) + allow(config).to receive(:public_enabled?).and_return(false) + + described_class.print_network_interfaces_routes + + expect(described_class).to have_received(:print_info).with('running on network interface: 127.0.0.1') + expect(described_class).to have_received(:print_info).with('running on network interface: 192.168.1.1') + expect(described_class).to have_received(:print_more).with(a_string_matching(%r{Hook URL: http://127\.0\.0\.1:3000/hook\.js})) + expect(described_class).to have_received(:print_more).at_least(:twice).with(a_string_matching(%r{UI URL:.*/ui/panel})) + end + + it 'omits UI URL when admin_ui disabled' do + allow(config).to receive(:local_proto).and_return('http') + allow(config).to receive(:hook_file_path).and_return('/hook.js') + allow(config).to receive(:get).with('beef.extension.admin_ui.enable').and_return(false) + allow(config).to receive(:get).with('beef.extension.admin_ui.base_path').and_return('/ui') + allow(config).to receive(:local_port).and_return(3000) + allow(config).to receive(:public_enabled?).and_return(false) + + described_class.print_network_interfaces_routes + + expect(described_class).to have_received(:print_more).with("Hook URL: http://127.0.0.1:3000/hook.js\n") + expect(described_class).to have_received(:print_more).with("Hook URL: http://192.168.1.1:3000/hook.js\n") + end + + it 'prints public hook and UI when public_enabled?' do + allow(config).to receive(:local_proto).and_return('http') + allow(config).to receive(:hook_file_path).and_return('/hook.js') + allow(config).to receive(:get).with('beef.extension.admin_ui.enable').and_return(true) + allow(config).to receive(:get).with('beef.extension.admin_ui.base_path').and_return('/ui') + allow(config).to receive(:local_port).and_return(3000) + allow(config).to receive(:public_enabled?).and_return(true) + allow(config).to receive(:hook_url).and_return('http://public.example.com/hook.js') + allow(config).to receive(:beef_url_str).and_return('http://public.example.com') + + described_class.print_network_interfaces_routes + + expect(described_class).to have_received(:print_info).with('Public:') + expect(described_class).to have_received(:print_more).with(a_string_including('http://public.example.com/hook.js')) + expect(described_class).to have_received(:print_more).at_least(:once).with(a_string_including('/ui/panel')) + end + end + + describe '.print_websocket_servers' do + it 'prints WebSocket server line with host, port and timer' do + allow(config).to receive(:beef_host).and_return('0.0.0.0') + allow(config).to receive(:get).with('beef.http.websocket.ws_poll_timeout').and_return(5) + allow(config).to receive(:get).with('beef.http.websocket.port').and_return(61_985) + allow(config).to receive(:get).with('beef.http.websocket.secure').and_return(false) + + described_class.print_websocket_servers + + expect(described_class).to have_received(:print_info).with('Starting WebSocket server ws://0.0.0.0:61985 [timer: 5]') + end + + it 'prints WebSocketSecure server when secure enabled' do + allow(config).to receive(:beef_host).and_return('0.0.0.0') + allow(config).to receive(:get).with('beef.http.websocket.ws_poll_timeout').and_return(10) + allow(config).to receive(:get).with('beef.http.websocket.port').and_return(61_985) + allow(config).to receive(:get).with('beef.http.websocket.secure').and_return(true) + allow(config).to receive(:get).with('beef.http.websocket.secure_port').and_return(61_986) + + described_class.print_websocket_servers + + expect(described_class).to have_received(:print_info).with('Starting WebSocket server ws://0.0.0.0:61985 [timer: 10]') + expect(described_class).to have_received(:print_info).with(a_string_matching(/WebSocketSecure.*wss:.*61986.*timer: 10/)) + end + end + + describe '.print_http_proxy' do + it 'prints proxy address and port from config' do + allow(config).to receive(:get).with('beef.extension.proxy.address').and_return('127.0.0.1') + allow(config).to receive(:get).with('beef.extension.proxy.port').and_return(8080) + + described_class.print_http_proxy + + expect(described_class).to have_received(:print_info).with('HTTP Proxy: http://127.0.0.1:8080') + end + end + + describe '.print_dns' do + it 'does not raise when DNS config is not set' do + expect { described_class.print_dns }.not_to raise_error + end + end +end diff --git a/spec/beef/core/main/handlers/hookedbrowsers_spec.rb b/spec/beef/core/main/handlers/hookedbrowsers_spec.rb index 595b7f9e8..0bfa9ead0 100644 --- a/spec/beef/core/main/handlers/hookedbrowsers_spec.rb +++ b/spec/beef/core/main/handlers/hookedbrowsers_spec.rb @@ -4,37 +4,36 @@ # See the file 'doc/COPYING' for copying permission # +require 'spec_helper' + RSpec.describe BeEF::Core::Handlers::HookedBrowsers do - # Test the confirm_browser_user_agent logic directly - describe 'confirm_browser_user_agent logic' do - it 'matches legacy browser user agents' do + # .new returns Sinatra::Wrapper; use allocate to get the real class instance for unit testing + let(:handler) { described_class.allocate } + + describe '#confirm_browser_user_agent' do + it 'returns true when user_agent suffix matches a legacy UA string' do allow(BeEF::Core::Models::LegacyBrowserUserAgents).to receive(:user_agents).and_return(['IE 8.0']) - - # Test the logic: browser_type = user_agent.split(' ').last - user_agent = 'Mozilla/5.0 IE 8.0' - browser_type = user_agent.split(' ').last - - # Test the matching logic - matched = false - BeEF::Core::Models::LegacyBrowserUserAgents.user_agents.each do |ua_string| - matched = true if ua_string.include?(browser_type) - end - - expect(matched).to be true + + # browser_type = user_agent.split(' ').last => '8.0'; 'IE 8.0'.include?('8.0') => true + expect(handler.confirm_browser_user_agent('Mozilla/5.0 IE 8.0')).to be true end - it 'does not match non-legacy browser user agents' do + it 'returns true when first legacy UA matches' do + allow(BeEF::Core::Models::LegacyBrowserUserAgents).to receive(:user_agents).and_return(['IE 8.0', 'Firefox/3.6']) + + expect(handler.confirm_browser_user_agent('Mozilla/5.0 IE 8.0')).to be true + end + + it 'returns false when no legacy UA includes the browser type' do allow(BeEF::Core::Models::LegacyBrowserUserAgents).to receive(:user_agents).and_return([]) - - user_agent = 'Chrome/91.0' - browser_type = user_agent.split(' ').last - - matched = false - BeEF::Core::Models::LegacyBrowserUserAgents.user_agents.each do |ua_string| - matched = true if ua_string.include?(browser_type) - end - - expect(matched).to be false + + expect(handler.confirm_browser_user_agent('Mozilla/5.0 Chrome/91.0')).to be false + end + + it 'returns false when legacy list has entries but none match' do + allow(BeEF::Core::Models::LegacyBrowserUserAgents).to receive(:user_agents).and_return(['IE 8.0']) + + expect(handler.confirm_browser_user_agent('Chrome/91.0')).to be false end end end diff --git a/spec/beef/core/ruby/security_spec.rb b/spec/beef/core/ruby/security_spec.rb index ba257bfcd..9b9fa9a33 100644 --- a/spec/beef/core/ruby/security_spec.rb +++ b/spec/beef/core/ruby/security_spec.rb @@ -25,4 +25,30 @@ RSpec.describe 'Security method overrides' do expect(Kernel.method(:system).source_location).not_to be_nil expect(Kernel.method(:system).source_location[0]).to include('core/ruby/security.rb') end + + describe 'override behavior' do + it 'exec prints security message and exits' do + allow(Kernel).to receive(:puts) + allow(Kernel).to receive(:exit) + exec('ls') + expect(Kernel).to have_received(:puts).with(/security reasons.*exec/) + expect(Kernel).to have_received(:exit) + end + + it 'system prints security message and exits' do + allow(Kernel).to receive(:puts) + allow(Kernel).to receive(:exit) + system('ls') + expect(Kernel).to have_received(:puts).with(/security reasons.*system/) + expect(Kernel).to have_received(:exit) + end + + it 'Kernel.system prints security message and exits' do + allow(Kernel).to receive(:puts) + allow(Kernel).to receive(:exit) + Kernel.system('ls') + expect(Kernel).to have_received(:puts).with(/security reasons.*system/) + expect(Kernel).to have_received(:exit) + end + end end From cad6fc9d815a739fc7db8fbb5b543df7f3c27f8c Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 17:54:19 +1000 Subject: [PATCH 14/37] TEST: core main server spec --- spec/beef/core/main/server_spec.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/spec/beef/core/main/server_spec.rb b/spec/beef/core/main/server_spec.rb index b694c9bb1..71419d062 100644 --- a/spec/beef/core/main/server_spec.rb +++ b/spec/beef/core/main/server_spec.rb @@ -110,4 +110,31 @@ RSpec.describe BeEF::Core::Server do server.remap end end + + describe '#prepare' do + before do + allow(BeEF::API::Registrar.instance).to receive(:fire).with(BeEF::API::Server, 'mount_handler', server) + allow(config).to receive(:get).and_return(nil) + allow(config).to receive(:get).with('beef.http.hook_file').and_return('/hook.js') + allow(config).to receive(:get).with('beef.http.host').and_return('0.0.0.0') + allow(config).to receive(:get).with('beef.http.port').and_return('3000') + allow(config).to receive(:get).with('beef.http.debug').and_return(false) + allow(config).to receive(:get).with('beef.http.https.enable').and_return(false) + allow(config).to receive(:get).with('beef.debug').and_return(false) + allow(Thin::Server).to receive(:new).and_return(double('Thin::Server')) + end + + it 'mounts hook file handler and init handler' do + server.prepare + expect(server.mounts).to have_key('/hook.js') + expect(server.mounts).to have_key('/init') + expect(server.mounts['/hook.js']).not_to be_nil + expect(server.mounts['/init']).to eq(BeEF::Core::Handlers::BrowserDetails) + end + + it 'builds Rack URLMap from mounts' do + server.prepare + expect(server.instance_variable_get(:@rack_app)).to be_a(Rack::URLMap) + end + end end From ee8234958eed8c478ee0ce229fa8bd9ecc37af71 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 17:54:49 +1000 Subject: [PATCH 15/37] TEST: more engine spec unit tests --- .../core/main/autorun_engine/engine_spec.rb | 242 ++++++++++++++++++ 1 file changed, 242 insertions(+) diff --git a/spec/beef/core/main/autorun_engine/engine_spec.rb b/spec/beef/core/main/autorun_engine/engine_spec.rb index ed708ed53..357af25f7 100644 --- a/spec/beef/core/main/autorun_engine/engine_spec.rb +++ b/spec/beef/core/main/autorun_engine/engine_spec.rb @@ -114,4 +114,246 @@ RSpec.describe BeEF::Core::AutorunEngine::Engine do expect(engine.find_matching_rules_for_zombie('FF', '41', 'Windows', '7')).to eq([1]) end end + + describe '#compare_versions' do + it 'returns true when cond is ALL' do + expect(engine.send(:compare_versions, '7', 'ALL', '8')).to be true + end + + it 'returns true when cond is == and versions equal' do + expect(engine.send(:compare_versions, '41', '==', '41')).to be true + end + + it 'returns false when cond is == and versions differ' do + expect(engine.send(:compare_versions, '41', '==', '42')).to be false + end + + it 'returns true when cond is <= and ver_a <= ver_b' do + expect(engine.send(:compare_versions, '41', '<=', '42')).to be true + expect(engine.send(:compare_versions, '41', '<=', '41')).to be true + end + + it 'returns false when cond is <= and ver_a > ver_b' do + expect(engine.send(:compare_versions, '42', '<=', '41')).to be false + end + + it 'returns true when cond is < and ver_a < ver_b' do + expect(engine.send(:compare_versions, '41', '<', '42')).to be true + end + + it 'returns false when cond is < and ver_a >= ver_b' do + expect(engine.send(:compare_versions, '42', '<', '41')).to be false + expect(engine.send(:compare_versions, '41', '<', '41')).to be false + end + + it 'returns true when cond is >= and ver_a >= ver_b' do + expect(engine.send(:compare_versions, '42', '>=', '41')).to be true + expect(engine.send(:compare_versions, '41', '>=', '41')).to be true + end + + it 'returns true when cond is > and ver_a > ver_b' do + expect(engine.send(:compare_versions, '42', '>', '41')).to be true + end + + it 'returns false when cond is > and ver_a <= ver_b' do + expect(engine.send(:compare_versions, '41', '>', '42')).to be false + expect(engine.send(:compare_versions, '41', '>', '41')).to be false + end + + it 'returns false for unknown cond' do + expect(engine.send(:compare_versions, '41', '!=', '42')).to be false + end + end + + describe '#clean_command_body' do + it 'extracts body range and replaces single-quoted mod_input when replace_input is true' do + body = "beef.execute(function(){\n alert('<>');\n});\n" + result = engine.send(:clean_command_body, body, true) + expect(result).to include('alert(mod_input)') + expect(result).to include('beef.execute(function(){') + end + + it 'returns cleaned body without mod_input replacement when replace_input is false' do + body = "beef.execute(function(){\n doSomething('<>');\n});\n" + result = engine.send(:clean_command_body, body, false) + expect(result).to include('<>') + end + + it 'replaces double-quoted <> with mod_input when replace_input is true' do + body = "beef.execute(function(){\n x(\"<>\");\n});\n" + result = engine.send(:clean_command_body, body, true) + expect(result).to include('mod_input') + expect(result).not_to include('"<>"') + end + + it 'replaces single-quoted <> with mod_input when replace_input is true' do + body = "beef.execute(function(){\n x('<>');\n});\n" + result = engine.send(:clean_command_body, body, true) + expect(result).to include('mod_input') + end + end + + describe '#prepare_sequential_wrapper' do + it 'builds wrapper with mod bodies and setTimeout calls in order' do + mods = [ + { mod_name: 'mod_a', mod_body: 'var mod_a_mod_output = 1;' }, + { mod_name: 'mod_b', mod_body: 'var mod_b_mod_output = 2;' } + ] + order = [0, 1] + delay = [0, 500] + token = 't1' + result = engine.send(:prepare_sequential_wrapper, mods, order, delay, token) + expect(result).to include('mod_a_t1') + expect(result).to include('mod_b_t1') + expect(result).to include('setTimeout(function(){mod_a_t1();}, 0)') + expect(result).to include('setTimeout(function(){mod_b_t1();}, 500)') + expect(result).to include('mod_a_t1_mod_output') + expect(result).to include('mod_b_t1_mod_output') + end + + it 'handles single module' do + mods = [{ mod_name: 'single', mod_body: 'x();' }] + order = [0] + delay = [0] + result = engine.send(:prepare_sequential_wrapper, mods, order, delay, 'tk') + expect(result).to include('single_tk') + expect(result).to include('setTimeout(function(){single_tk();}, 0)') + end + end + + describe '#prepare_nested_forward_wrapper' do + it 'builds wrapper for single module' do + mods = [{ mod_name: 'only', mod_body: 'only();' }] + code = ['null'] + conditions = [true] + order = [0] + token = 'nf1' + result = engine.send(:prepare_nested_forward_wrapper, mods, code, conditions, order, token) + expect(result).to include('only_nf1') + expect(result).to include('only_nf1_f') + expect(result).to include('only_nf1_mod_output') + end + end + + describe '#find_and_run_all_matching_rules_for_zombie' do + it 'returns without calling run_rules when hb_id is nil' do + expect(engine).not_to receive(:run_rules_on_zombie) + engine.find_and_run_all_matching_rules_for_zombie(nil) + end + + it 'returns without calling run_rules when find_matching_rules returns nil' do + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7') + allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return(nil) + expect(engine).not_to receive(:run_rules_on_zombie) + engine.find_and_run_all_matching_rules_for_zombie(1) + end + + it 'returns without calling run_rules when find_matching_rules returns empty' do + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7') + allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return([]) + expect(engine).not_to receive(:run_rules_on_zombie) + engine.find_and_run_all_matching_rules_for_zombie(1) + end + + it 'calls run_rules_on_zombie with matching rule ids when rules match' do + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7') + allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return([1, 2]) + expect(engine).to receive(:run_rules_on_zombie).with([1, 2], 1) + engine.find_and_run_all_matching_rules_for_zombie(1) + end + end + + describe '#run_matching_rules_on_zombie' do + it 'returns when rule_ids is nil' do + expect(engine).not_to receive(:run_rules_on_zombie) + engine.run_matching_rules_on_zombie(nil, 1) + end + + it 'returns when hb_id is nil' do + expect(engine).not_to receive(:run_rules_on_zombie) + engine.run_matching_rules_on_zombie([1], nil) + end + + it 'returns without calling run_rules when find_matching_rules returns nil' do + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7') + allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return(nil) + expect(engine).not_to receive(:run_rules_on_zombie) + engine.run_matching_rules_on_zombie([1], 1) + end + + it 'calls run_rules_on_zombie with intersection of rule_ids and matching rules' do + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7') + allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return([1, 2]) + expect(engine).to receive(:run_rules_on_zombie).with([1], 1) + engine.run_matching_rules_on_zombie([1], 1) + end + + it 'does not call run_rules_on_zombie when no rule_ids overlap matching rules' do + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows') + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7') + allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return([1, 2]) + expect(engine).not_to receive(:run_rules_on_zombie) + engine.run_matching_rules_on_zombie([99], 1) + end + end + + describe '#run_rules_on_zombie' do + it 'returns when rule_ids is nil' do + expect(BeEF::HBManager).not_to receive(:get_by_id) + engine.run_rules_on_zombie(nil, 1) + end + + it 'returns when hb_id is nil' do + expect(BeEF::HBManager).not_to receive(:get_by_id) + engine.run_rules_on_zombie([1], nil) + end + + it 'normalizes single Integer rule_id to array and processes rule' do + hb = double('HookedBrowser', session: 'sess1') + allow(BeEF::HBManager).to receive(:get_by_id).with(1).and_return(hb) + rule = double( + 'Rule', + modules: '[]', + execution_order: '[]', + execution_delay: '[]', + chain_mode: 'invalid' + ) + allow(BeEF::Core::Models::Rule).to receive(:find).with(1).and_return(rule) + engine.run_rules_on_zombie(1, 1) + expect(BeEF::Core::Models::Rule).to have_received(:find).with(1) + expect(engine).to have_received(:print_error).with(/Invalid chain mode 'invalid'/) + end + + it 'prints error and returns when rule has invalid chain_mode' do + hb = double('HookedBrowser', session: 'sess1') + allow(BeEF::HBManager).to receive(:get_by_id).with(1).and_return(hb) + rule = double( + 'Rule', + modules: '[]', + execution_order: '[]', + execution_delay: '[]', + chain_mode: 'invalid' + ) + allow(BeEF::Core::Models::Rule).to receive(:find).with(1).and_return(rule) + engine.run_rules_on_zombie([1], 1) + expect(engine).to have_received(:print_error).with(/Invalid chain mode 'invalid'/) + end + end end From 65395cdd4d8499b86216429c088c2b5d415a35d5 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 17:55:05 +1000 Subject: [PATCH 16/37] TEST: more logger spec unit tests --- spec/beef/core/main/logger_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/beef/core/main/logger_spec.rb b/spec/beef/core/main/logger_spec.rb index c3481331b..5bb6a3c8c 100644 --- a/spec/beef/core/main/logger_spec.rb +++ b/spec/beef/core/main/logger_spec.rb @@ -58,5 +58,18 @@ RSpec.describe BeEF::Core::Logger do TypeError, "'event' is NilClass; expected String" ) end + + it 'calls notifications when extension is enabled' do + notifications_double = double('Notifications') + logger.instance_variable_set(:@notifications, notifications_double) + allow(notifications_double).to receive(:new) + logger.register('Zombie', 'Browser hooked', 7) + expect(notifications_double).to have_received(:new).with( + 'Zombie', + 'Browser hooked', + kind_of(Time), + 7 + ) + end end end From 30ae29e30d6b11c0ccbd6237f07163bea3121814 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 17:56:22 +1000 Subject: [PATCH 17/37] TEST: additional specs for command and commandline spec --- spec/beef/core/filter/command_spec.rb | 15 ++- .../core/main/console/commandline_spec.rb | 126 ++++++++++++++++++ 2 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 spec/beef/core/main/console/commandline_spec.rb diff --git a/spec/beef/core/filter/command_spec.rb b/spec/beef/core/filter/command_spec.rb index 1cb4dda42..c15d2759f 100644 --- a/spec/beef/core/filter/command_spec.rb +++ b/spec/beef/core/filter/command_spec.rb @@ -11,6 +11,10 @@ RSpec.describe BeEF::Filters do expect(BeEF::Filters.is_valid_path_info?("\x00")).to be(false) expect(BeEF::Filters.is_valid_path_info?(nil)).to be(false) end + + it 'returns false when argument is not a String' do + expect(BeEF::Filters.is_valid_path_info?(123)).to be(false) + end end describe '.is_valid_hook_session_id?' do @@ -43,15 +47,22 @@ RSpec.describe BeEF::Filters do end describe '.has_valid_param_chars?' do - it 'false' do + it 'returns false for nil, empty, or invalid chars' do chars = [nil, '', '+'] chars.each do |c| expect(BeEF::Filters.has_valid_param_chars?(c)).to be(false) end end - it 'true' do + it 'returns true for word, underscore, and colon' do expect(BeEF::Filters.has_valid_param_chars?('A')).to be(true) + expect(BeEF::Filters.has_valid_param_chars?('key_name')).to be(true) + expect(BeEF::Filters.has_valid_param_chars?('a:1')).to be(true) + end + + it 'returns false for string with spaces or special chars' do + expect(BeEF::Filters.has_valid_param_chars?('a b')).to be(false) + expect(BeEF::Filters.has_valid_param_chars?('a-b')).to be(false) end end end diff --git a/spec/beef/core/main/console/commandline_spec.rb b/spec/beef/core/main/console/commandline_spec.rb new file mode 100644 index 000000000..2804b6b30 --- /dev/null +++ b/spec/beef/core/main/console/commandline_spec.rb @@ -0,0 +1,126 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require 'spec_helper' + +RSpec.describe BeEF::Core::Console::CommandLine do + DEFAULT_OPTIONS = { + verbose: false, + resetdb: false, + ascii_art: false, + ext_config: '', + port: '', + ws_port: '', + update_disabled: false, + update_auto: false + }.freeze + + def reset_commandline_state + described_class.instance_variable_set(:@already_parsed, false) + described_class.instance_variable_set(:@options, DEFAULT_OPTIONS.dup) + end + + before do + reset_commandline_state + end + + describe '.parse' do + it 'returns default options when ARGV is empty' do + original_argv = ARGV.dup + ARGV.replace([]) + result = described_class.parse + ARGV.replace(original_argv) + expect(result[:verbose]).to be false + expect(result[:resetdb]).to be false + expect(result[:ext_config]).to eq('') + expect(result[:port]).to eq('') + end + + it 'sets verbose when -v is given' do + original_argv = ARGV.dup + ARGV.replace(%w[-v]) + result = described_class.parse + ARGV.replace(original_argv) + expect(result[:verbose]).to be true + end + + it 'sets resetdb when -x is given' do + original_argv = ARGV.dup + ARGV.replace(%w[-x]) + result = described_class.parse + ARGV.replace(original_argv) + expect(result[:resetdb]).to be true + end + + it 'sets ascii_art when -a is given' do + original_argv = ARGV.dup + ARGV.replace(%w[-a]) + result = described_class.parse + ARGV.replace(original_argv) + expect(result[:ascii_art]).to be true + end + + it 'sets ext_config when -c FILE is given' do + original_argv = ARGV.dup + ARGV.replace(%w[-c custom.yaml]) + result = described_class.parse + ARGV.replace(original_argv) + expect(result[:ext_config]).to eq('custom.yaml') + end + + it 'sets port when -p PORT is given' do + original_argv = ARGV.dup + ARGV.replace(%w[-p 9090]) + result = described_class.parse + ARGV.replace(original_argv) + expect(result[:port]).to eq('9090') + end + + it 'sets ws_port when -w WS_PORT is given' do + original_argv = ARGV.dup + ARGV.replace(%w[-w 61985]) + result = described_class.parse + ARGV.replace(original_argv) + expect(result[:ws_port]).to eq('61985') + end + + it 'sets update_disabled when -d is given' do + original_argv = ARGV.dup + ARGV.replace(%w[-d]) + result = described_class.parse + ARGV.replace(original_argv) + expect(result[:update_disabled]).to be true + end + + it 'sets update_auto when -u is given' do + original_argv = ARGV.dup + ARGV.replace(%w[-u]) + result = described_class.parse + ARGV.replace(original_argv) + expect(result[:update_auto]).to be true + end + + it 'returns cached options on second parse' do + original_argv = ARGV.dup + ARGV.replace([]) + first = described_class.parse + ARGV.replace(%w[-v -x]) + second = described_class.parse + ARGV.replace(original_argv) + expect(second).to eq(first) + expect(second[:verbose]).to be false + end + + it 'prints and exits on invalid option' do + original_argv = ARGV.dup + ARGV.replace(%w[--invalid-option]) + allow(Kernel).to receive(:puts).with(/Invalid command line option/) + allow(Kernel).to receive(:exit).with(1) { raise SystemExit.new(1) } + expect { described_class.parse }.to raise_error(SystemExit) + ARGV.replace(original_argv) + end + end +end From 0ceb550409cc0fbee20822b709b2f1a42683c754 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 30 Jan 2026 17:58:24 +1000 Subject: [PATCH 18/37] TEST: more server spec --- spec/beef/core/main/server_spec.rb | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/spec/beef/core/main/server_spec.rb b/spec/beef/core/main/server_spec.rb index 71419d062..b19ddcf53 100644 --- a/spec/beef/core/main/server_spec.rb +++ b/spec/beef/core/main/server_spec.rb @@ -136,5 +136,42 @@ RSpec.describe BeEF::Core::Server do server.prepare expect(server.instance_variable_get(:@rack_app)).to be_a(Rack::URLMap) end + + it 'returns early when @http_server already set' do + allow(Thin::Server).to receive(:new) + existing = double('Thin::Server') + server.instance_variable_set(:@http_server, existing) + server.prepare + expect(Thin::Server).not_to have_received(:new) + end + + it 'sets Thin::Logging when beef.http.debug is true' do + allow(config).to receive(:get).with('beef.http.debug').and_return(true) + allow(Thin::Logging).to receive(:silent=) + allow(Thin::Logging).to receive(:debug=) + server.prepare + expect(Thin::Logging).to have_received(:silent=).with(false) + expect(Thin::Logging).to have_received(:debug=).with(true) + end + end + + describe '#start' do + it 'rescues port-in-use error and exits' do + mock_thin = double('Thin::Server') + allow(mock_thin).to receive(:start).and_raise(RuntimeError.new('no acceptor')) + server.instance_variable_set(:@http_server, mock_thin) + allow(server).to receive(:print_error) + allow(server).to receive(:exit).with(127) { raise SystemExit.new(127) } + expect { server.start }.to raise_error(SystemExit) + expect(server).to have_received(:print_error).with(/port|invalid IP/i) + expect(server).to have_received(:exit).with(127) + end + + it 're-raises RuntimeError when message does not include no acceptor' do + mock_thin = double('Thin::Server') + allow(mock_thin).to receive(:start).and_raise(RuntimeError.new('other error')) + server.instance_variable_set(:@http_server, mock_thin) + expect { server.start }.to raise_error(RuntimeError, 'other error') + end end end From 088b2441094ff942fa5b9ee76b2d0cdbab5754fc Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Mon, 2 Feb 2026 11:00:50 +1000 Subject: [PATCH 19/37] TEST: hookerbrowser and browserdetails spec --- .../core/main/handlers/browserdetails_spec.rb | 153 ++++++++++++++++++ .../core/main/handlers/hookedbrowsers_spec.rb | 125 ++++++++++++++ 2 files changed, 278 insertions(+) diff --git a/spec/beef/core/main/handlers/browserdetails_spec.rb b/spec/beef/core/main/handlers/browserdetails_spec.rb index 6cf623346..c773633e1 100644 --- a/spec/beef/core/main/handlers/browserdetails_spec.rb +++ b/spec/beef/core/main/handlers/browserdetails_spec.rb @@ -413,5 +413,158 @@ RSpec.describe BeEF::Core::Handlers::BrowserDetails do expect(BeEF::Core::Models::BrowserDetails).to receive(:set).with(session_id, 'network.proxy.server', 'proxy.example.com') described_class.new(proxy_data) end + + context 'filter failures (err_msg branches)' do + # Full data with optional keys so later filters (e.g. battery, capabilities) don't trigger err_msg + let(:full_data) do + data.merge('results' => data['results'].merge( + 'hardware.battery.level' => '50%', + 'browser.name.reported' => 'Mozilla/5.0', + 'browser.engine' => 'Gecko', + 'browser.window.cookies' => 'session=abc', + 'host.os.name' => 'Windows', + 'host.os.family' => 'Windows', + 'host.os.version' => '10', + 'browser.capabilities.vbscript' => 'yes' + )) + end + + def stub_all_filters_valid_except(except_key = nil) + %i[ + is_valid_hook_session_id? is_valid_browsername? is_valid_browserversion? is_valid_ip? + is_valid_browserstring? is_valid_cookies? is_valid_osname? is_valid_hwname? + is_valid_date_stamp? is_valid_pagetitle? is_valid_url? is_valid_pagereferrer? + is_valid_hostname? is_valid_port? is_valid_browser_plugins? is_valid_system_platform? + nums_only? is_valid_yes_no? is_valid_memory? is_valid_gpu? is_valid_cpu? alphanums_only? + ].each do |m| + allow(BeEF::Filters).to receive(m).and_return(except_key == m ? false : true) + end + end + + it 'calls err_msg when browser name is invalid' do + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + stub_all_filters_valid_except(:is_valid_browsername?) + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name) + zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1') + allow(zombie).to receive(:firstseen=) + allow(zombie).to receive(:domain=) + allow(zombie).to receive(:port=) + allow(zombie).to receive(:httpheaders=) + allow(zombie).to receive(:httpheaders).and_return('{}') + allow(zombie).to receive(:save!) + allow(JSON).to receive(:parse).with('{}').and_return({}) + allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie) + err_msg_calls = [] + allow_any_instance_of(described_class).to receive(:err_msg) { |*args| err_msg_calls << args.last } + described_class.new(full_data) + expect(err_msg_calls).to include(a_string_matching(/Invalid browser name/)) + end + + it 'calls err_msg when IP is invalid' do + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + stub_all_filters_valid_except(:is_valid_ip?) + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox') + zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1') + allow(zombie).to receive(:firstseen=) + allow(zombie).to receive(:domain=) + allow(zombie).to receive(:port=) + allow(zombie).to receive(:httpheaders=) + allow(zombie).to receive(:httpheaders).and_return('{}') + allow(zombie).to receive(:save!) + allow(JSON).to receive(:parse).with('{}').and_return({}) + allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie) + err_msg_calls = [] + allow_any_instance_of(described_class).to receive(:err_msg) { |*args| err_msg_calls << args.last } + described_class.new(full_data) + expect(err_msg_calls).to include(a_string_matching(/Invalid IP address/)) + end + + it 'calls err_msg when browser version is invalid' do + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + stub_all_filters_valid_except(:is_valid_browserversion?) + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox') + zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1') + allow(zombie).to receive(:firstseen=) + allow(zombie).to receive(:domain=) + allow(zombie).to receive(:port=) + allow(zombie).to receive(:httpheaders=) + allow(zombie).to receive(:httpheaders).and_return('{}') + allow(zombie).to receive(:save!) + allow(JSON).to receive(:parse).with('{}').and_return({}) + allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie) + err_msg_calls = [] + allow_any_instance_of(described_class).to receive(:err_msg) { |*args| err_msg_calls << args.last } + described_class.new(full_data) + expect(err_msg_calls).to include(a_string_matching(/Invalid browser version/)) + end + + it 'calls err_msg when browser.name.reported is invalid' do + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + allow(BeEF::Filters).to receive(:is_valid_browsername?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browserversion?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browserstring?).and_return(false) + stub_all_filters_valid_except(nil) + allow(BeEF::Filters).to receive(:is_valid_browserstring?).and_return(false) + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox') + zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1') + allow(zombie).to receive(:firstseen=) + allow(zombie).to receive(:domain=) + allow(zombie).to receive(:port=) + allow(zombie).to receive(:httpheaders=) + allow(zombie).to receive(:httpheaders).and_return('{}') + allow(zombie).to receive(:save!) + allow(JSON).to receive(:parse).with('{}').and_return({}) + allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie) + err_msg_calls = [] + allow_any_instance_of(described_class).to receive(:err_msg) { |*args| err_msg_calls << args.last } + described_class.new(full_data) + expect(err_msg_calls).to include(a_string_matching(/browser\.name\.reported/)) + end + + it 'calls err_msg when cookies are invalid' do + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + stub_all_filters_valid_except(:is_valid_cookies?) + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox') + zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1') + allow(zombie).to receive(:firstseen=) + allow(zombie).to receive(:domain=) + allow(zombie).to receive(:port=) + allow(zombie).to receive(:httpheaders=) + allow(zombie).to receive(:httpheaders).and_return('{}') + allow(zombie).to receive(:save!) + allow(JSON).to receive(:parse).with('{}').and_return({}) + allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie) + err_msg_calls = [] + allow_any_instance_of(described_class).to receive(:err_msg) { |*args| err_msg_calls << args.last } + described_class.new(full_data) + expect(err_msg_calls).to include(a_string_matching(/Invalid cookies/)) + end + + it 'calls err_msg when host.os.name is invalid' do + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + stub_all_filters_valid_except(:is_valid_osname?) + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox') + zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1') + allow(zombie).to receive(:firstseen=) + allow(zombie).to receive(:domain=) + allow(zombie).to receive(:port=) + allow(zombie).to receive(:httpheaders=) + allow(zombie).to receive(:httpheaders).and_return('{}') + allow(zombie).to receive(:save!) + allow(JSON).to receive(:parse).with('{}').and_return({}) + allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie) + err_msg_calls = [] + allow_any_instance_of(described_class).to receive(:err_msg) { |*args| err_msg_calls << args.last } + described_class.new(full_data) + expect(err_msg_calls).to include(a_string_matching(/operating system name/)) + end + end end end diff --git a/spec/beef/core/main/handlers/hookedbrowsers_spec.rb b/spec/beef/core/main/handlers/hookedbrowsers_spec.rb index 0bfa9ead0..aae0d90f0 100644 --- a/spec/beef/core/main/handlers/hookedbrowsers_spec.rb +++ b/spec/beef/core/main/handlers/hookedbrowsers_spec.rb @@ -10,6 +10,131 @@ RSpec.describe BeEF::Core::Handlers::HookedBrowsers do # .new returns Sinatra::Wrapper; use allocate to get the real class instance for unit testing let(:handler) { described_class.allocate } + describe "GET '/'" do + let(:config) { BeEF::Core::Configuration.instance } + # Use a host permitted by Router's host_authorization (.localhost, .test, or config public host) + let(:rack_env) { { 'REMOTE_ADDR' => '192.168.1.1', 'HTTP_HOST' => 'localhost' } } + + def app + described_class + end + + before do + allow(BeEF::Core::Logger.instance).to receive(:register) + allow(config).to receive(:get).and_call_original + allow(config).to receive(:get).with('beef.http.restful_api.allow_cors').and_return(false) + end + + it 'returns 404 when permitted_hooking_subnet is nil' do + allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return(nil) + get '/', {}, rack_env + expect(last_response.status).to eq(404) + end + + it 'returns 404 when permitted_hooking_subnet is empty' do + allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return([]) + get '/', {}, rack_env + expect(last_response.status).to eq(404) + end + + it 'returns 404 when client IP is not in permitted subnet' do + allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return(['10.0.0.0/8']) + get '/', {}, rack_env + expect(last_response.status).to eq(404) + end + + it 'returns 404 when client IP is in excluded_hooking_subnet' do + allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return(['0.0.0.0/0']) + allow(config).to receive(:get).with('beef.restrictions.excluded_hooking_subnet').and_return(['192.168.1.0/24']) + get '/', {}, rack_env + expect(last_response.status).to eq(404) + end + + it 'returns 200 and hook body when IP permitted, not excluded, no session (new browser)' do + allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return(['192.168.0.0/16']) + allow(config).to receive(:get).with('beef.restrictions.excluded_hooking_subnet').and_return([]) + allow(config).to receive(:get).with('beef.http.hook_session_name').and_return('beefhook') + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + allow(BeEF::Filters).to receive(:is_valid_hostname?).with('localhost').and_return(true) + allow(config).to receive(:get).with('beef.http.websocket.enable').and_return(false) + allow_any_instance_of(described_class).to receive(:confirm_browser_user_agent).and_return(false) + allow_any_instance_of(described_class).to receive(:legacy_build_beefjs!).with('localhost') + get '/', {}, rack_env + expect(last_response.status).to eq(200) + expect(last_response.headers['Content-Type']).to include('javascript') + end + + it 'uses multi_stage_beefjs when websocket disabled and confirm_browser_user_agent true' do + allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return(['0.0.0.0/0']) + allow(config).to receive(:get).with('beef.restrictions.excluded_hooking_subnet').and_return([]) + allow(config).to receive(:get).with('beef.http.hook_session_name').and_return('beefhook') + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + allow(BeEF::Filters).to receive(:is_valid_hostname?).with('localhost').and_return(true) + allow(config).to receive(:get).with('beef.http.websocket.enable').and_return(false) + allow_any_instance_of(described_class).to receive(:confirm_browser_user_agent).and_return(true) + allow_any_instance_of(described_class).to receive(:multi_stage_beefjs!).with('localhost') + get '/', {}, { 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_HOST' => 'localhost' } + expect(last_response.status).to eq(200) + end + + it 'returns early with empty body when hostname is invalid' do + allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return(['0.0.0.0/0']) + allow(config).to receive(:get).with('beef.restrictions.excluded_hooking_subnet').and_return([]) + allow(config).to receive(:get).with('beef.http.hook_session_name').and_return('beefhook') + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + # Use permitted host so request reaches handler; stub hostname validation to fail + allow(BeEF::Filters).to receive(:is_valid_hostname?).with('localhost').and_return(false) + get '/', {}, { 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_HOST' => 'localhost' } + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('') + end + + context 'when session exists (existing browser path)' do + let(:hooked_browser) do + double('HookedBrowser', + id: 1, + ip: '192.168.1.1', + lastseen: Time.new.to_i - 120, + session: 'existing_session', + count!: nil, + save!: true).tap do |d| + allow(d).to receive(:lastseen=) + allow(d).to receive(:ip=) + end + end + + before do + allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return(['192.168.0.0/16']) + allow(config).to receive(:get).with('beef.restrictions.excluded_hooking_subnet').and_return([]) + allow(config).to receive(:get).with('beef.http.hook_session_name').and_return('beefhook') + allow(config).to receive(:get).with('beef.http.allow_reverse_proxy').and_return(false) + relation = double('Relation', first: hooked_browser) + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).with(session: 'existing_session').and_return(relation) + allow(BeEF::Core::Models::Command).to receive(:where).with(hooked_browser_id: 1, instructions_sent: false).and_return([]) + allow(BeEF::Core::Models::Execution).to receive(:where).with(is_sent: false, session_id: 'existing_session').and_return([]) + allow(BeEF::API::Registrar.instance).to receive(:fire) + end + + it 'returns 200 and updates lastseen' do + get '/', { 'beefhook' => 'existing_session' }, rack_env + expect(last_response.status).to eq(200) + expect(hooked_browser).to have_received(:save!) + end + + it 'logs zombie comeback when lastseen was more than 60 seconds ago' do + get '/', { 'beefhook' => 'existing_session' }, rack_env + expect(BeEF::Core::Logger.instance).to have_received(:register).with('Zombie', /appears to have come back online/, '1') + end + + it 'calls add_command_instructions for each pending command' do + command = double('Command', id: 1, command_module_id: 1) + allow(BeEF::Core::Models::Command).to receive(:where).with(hooked_browser_id: 1, instructions_sent: false).and_return([command]) + expect_any_instance_of(described_class).to receive(:add_command_instructions).with(command, hooked_browser) + get '/', { 'beefhook' => 'existing_session' }, rack_env + end + end + end + describe '#confirm_browser_user_agent' do it 'returns true when user_agent suffix matches a legacy UA string' do allow(BeEF::Core::Models::LegacyBrowserUserAgents).to receive(:user_agents).and_return(['IE 8.0']) From e8427f0a5ed8f8489131c86ddec7ee9d300a006e Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Mon, 2 Feb 2026 16:54:02 +1000 Subject: [PATCH 20/37] TEST: modules/browser specs --- .../browser/avant_steal_history_spec.rb | 26 ++++++++++++ .../browser/browser_fingerprinting_spec.rb | 26 ++++++++++++ .../modules/browser/detect_activex_spec.rb | 38 +++++++++++++++++ .../browser/detect_evernote_clipper_spec.rb | 24 +++++++++++ .../modules/browser/detect_extensions_spec.rb | 18 ++++++++ .../modules/browser/detect_firebug_spec.rb | 24 +++++++++++ .../beef/modules/browser/detect_foxit_spec.rb | 38 +++++++++++++++++ .../modules/browser/detect_lastpass_spec.rb | 24 +++++++++++ .../modules/browser/detect_mime_types_spec.rb | 24 +++++++++++ .../modules/browser/detect_office_spec.rb | 38 +++++++++++++++++ .../browser/detect_popup_blocker_spec.rb | 18 ++++++++ .../modules/browser/detect_quicktime_spec.rb | 18 ++++++++ .../modules/browser/detect_realplayer_spec.rb | 18 ++++++++ .../browser/detect_silverlight_spec.rb | 18 ++++++++ .../browser/detect_simple_adblock_spec.rb | 24 +++++++++++ .../modules/browser/detect_toolbars_spec.rb | 18 ++++++++ .../beef/modules/browser/detect_unity_spec.rb | 18 ++++++++ .../browser/detect_unsafe_activex_spec.rb | 18 ++++++++ spec/beef/modules/browser/detect_vlc_spec.rb | 28 +++++++++++++ spec/beef/modules/browser/detect_wmp_spec.rb | 28 +++++++++++++ .../browser/fingerprint_browser_spec.rb | 26 ++++++++++++ .../browser/get_visited_domains_spec.rb | 28 +++++++++++++ .../modules/browser/get_visited_urls_spec.rb | 26 ++++++++++++ .../hooked_origin/ajax_fingerprint_spec.rb | 24 +++++++++++ .../hooked_origin/alert_dialog_spec.rb | 26 ++++++++++++ ..._tomcat_examples_cookie_disclosure_spec.rb | 26 ++++++++++++ .../cisco_asa_password_disclosure_spec.rb | 18 ++++++++ .../hooked_origin/clear_console_spec.rb | 18 ++++++++ .../deface_web_page_component_spec.rb | 26 ++++++++++++ .../hooked_origin/deface_web_page_spec.rb | 33 +++++++++++++++ .../disable_developer_tools_spec.rb | 18 ++++++++ .../get_autocomplete_creds_spec.rb | 24 +++++++++++ .../browser/hooked_origin/get_cookie_spec.rb | 18 ++++++++ .../hooked_origin/get_form_values_spec.rb | 18 ++++++++ .../hooked_origin/get_local_storage_spec.rb | 18 ++++++++ .../get_page_html_iframe_spec.rb | 21 ++++++++++ .../hooked_origin/get_page_html_spec.rb | 18 ++++++++ .../hooked_origin/get_page_links_spec.rb | 18 ++++++++ .../hooked_origin/get_session_storage_spec.rb | 18 ++++++++ .../get_stored_credentials_spec.rb | 18 ++++++++ .../link_rewrite_click_events_spec.rb | 26 ++++++++++++ .../hooked_origin/link_rewrite_spec.rb | 26 ++++++++++++ .../link_rewrite_sslstrip_spec.rb | 18 ++++++++ .../hooked_origin/link_rewrite_tel_spec.rb | 26 ++++++++++++ .../mobilesafari_address_spoofing_spec.rb | 26 ++++++++++++ .../hooked_origin/overflow_cookiejar_spec.rb | 26 ++++++++++++ .../hooked_origin/prompt_dialog_spec.rb | 26 ++++++++++++ .../remove_stuck_iframes_spec.rb | 21 ++++++++++ .../hooked_origin/replace_video_spec.rb | 26 ++++++++++++ .../browser/hooked_origin/rickroll_spec.rb | 18 ++++++++ .../site_redirect_iframe_spec.rb | 33 +++++++++++++++ .../hooked_origin/site_redirect_spec.rb | 26 ++++++++++++ spec/beef/modules/browser/play_sound_spec.rb | 26 ++++++++++++ .../browser/remove_hook_element_spec.rb | 24 +++++++++++ spec/beef/modules/browser/spyder_eye_spec.rb | 41 +++++++++++++++++++ spec/beef/modules/browser/unhook_spec.rb | 24 +++++++++++ .../beef/modules/browser/webcam_flash_spec.rb | 33 +++++++++++++++ .../beef/modules/browser/webcam_html5_spec.rb | 32 +++++++++++++++ .../browser/webcam_permission_check_spec.rb | 24 +++++++++++ 59 files changed, 1428 insertions(+) create mode 100644 spec/beef/modules/browser/avant_steal_history_spec.rb create mode 100644 spec/beef/modules/browser/browser_fingerprinting_spec.rb create mode 100644 spec/beef/modules/browser/detect_activex_spec.rb create mode 100644 spec/beef/modules/browser/detect_evernote_clipper_spec.rb create mode 100644 spec/beef/modules/browser/detect_extensions_spec.rb create mode 100644 spec/beef/modules/browser/detect_firebug_spec.rb create mode 100644 spec/beef/modules/browser/detect_foxit_spec.rb create mode 100644 spec/beef/modules/browser/detect_lastpass_spec.rb create mode 100644 spec/beef/modules/browser/detect_mime_types_spec.rb create mode 100644 spec/beef/modules/browser/detect_office_spec.rb create mode 100644 spec/beef/modules/browser/detect_popup_blocker_spec.rb create mode 100644 spec/beef/modules/browser/detect_quicktime_spec.rb create mode 100644 spec/beef/modules/browser/detect_realplayer_spec.rb create mode 100644 spec/beef/modules/browser/detect_silverlight_spec.rb create mode 100644 spec/beef/modules/browser/detect_simple_adblock_spec.rb create mode 100644 spec/beef/modules/browser/detect_toolbars_spec.rb create mode 100644 spec/beef/modules/browser/detect_unity_spec.rb create mode 100644 spec/beef/modules/browser/detect_unsafe_activex_spec.rb create mode 100644 spec/beef/modules/browser/detect_vlc_spec.rb create mode 100644 spec/beef/modules/browser/detect_wmp_spec.rb create mode 100644 spec/beef/modules/browser/fingerprint_browser_spec.rb create mode 100644 spec/beef/modules/browser/get_visited_domains_spec.rb create mode 100644 spec/beef/modules/browser/get_visited_urls_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/ajax_fingerprint_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/alert_dialog_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/apache_tomcat_examples_cookie_disclosure_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/cisco_asa_password_disclosure_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/clear_console_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/deface_web_page_component_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/deface_web_page_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/disable_developer_tools_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/get_autocomplete_creds_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/get_cookie_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/get_form_values_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/get_local_storage_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/get_page_html_iframe_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/get_page_html_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/get_page_links_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/get_session_storage_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/get_stored_credentials_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/link_rewrite_click_events_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/link_rewrite_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/link_rewrite_sslstrip_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/link_rewrite_tel_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/mobilesafari_address_spoofing_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/overflow_cookiejar_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/prompt_dialog_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/remove_stuck_iframes_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/replace_video_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/rickroll_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/site_redirect_iframe_spec.rb create mode 100644 spec/beef/modules/browser/hooked_origin/site_redirect_spec.rb create mode 100644 spec/beef/modules/browser/play_sound_spec.rb create mode 100644 spec/beef/modules/browser/remove_hook_element_spec.rb create mode 100644 spec/beef/modules/browser/spyder_eye_spec.rb create mode 100644 spec/beef/modules/browser/unhook_spec.rb create mode 100644 spec/beef/modules/browser/webcam_flash_spec.rb create mode 100644 spec/beef/modules/browser/webcam_html5_spec.rb create mode 100644 spec/beef/modules/browser/webcam_permission_check_spec.rb diff --git a/spec/beef/modules/browser/avant_steal_history_spec.rb b/spec/beef/modules/browser/avant_steal_history_spec.rb new file mode 100644 index 000000000..0d16649cb --- /dev/null +++ b/spec/beef/modules/browser/avant_steal_history_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/avant_steal_history/module' + +RSpec.describe Avant_steal_history do + describe '.options' do + it 'returns cId option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.first).to include('name' => 'cId', 'ui_label' => 'Command ID') + end + end + + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'done') + results = run_post_execute(instance) + expect(results).to eq('result' => 'done') + end + end +end diff --git a/spec/beef/modules/browser/browser_fingerprinting_spec.rb b/spec/beef/modules/browser/browser_fingerprinting_spec.rb new file mode 100644 index 000000000..00b310710 --- /dev/null +++ b/spec/beef/modules/browser/browser_fingerprinting_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/browser_fingerprinting/module' + +RSpec.describe Browser_fingerprinting do + describe '#post_execute' do + it 'saves browser_type and browser_version from datastore' do + instance = build_command_instance(described_class, + 'browser_type' => 'Chrome', + 'browser_version' => '120') + results = run_post_execute(instance) + expect(results).to include('browser_type' => 'Chrome', 'browser_version' => '120') + end + + it 'sets fail message when content is empty' do + instance = build_command_instance(described_class, {}) + results = run_post_execute(instance) + expect(results).to eq('fail' => 'Failed to fingerprint browser.') + end + end +end diff --git a/spec/beef/modules/browser/detect_activex_spec.rb b/spec/beef/modules/browser/detect_activex_spec.rb new file mode 100644 index 000000000..1a6f1d6d9 --- /dev/null +++ b/spec/beef/modules/browser/detect_activex_spec.rb @@ -0,0 +1,38 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_activex/module' + +RSpec.describe Detect_activex do + describe '#post_execute' do + it 'saves activex from datastore' do + instance = build_command_instance(described_class, 'activex' => 'Yes', 'results' => '') + results = run_post_execute(instance) + expect(results).to eq('activex' => 'Yes') + end + + it 'calls BrowserDetails.set when results matches activex=(Yes|No)' do + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + instance = build_command_instance(described_class, + 'activex' => 'Yes', + 'beefhook' => 'hook123', + 'results' => 'activex=Yes') + run_post_execute(instance) + expect(BeEF::Core::Models::BrowserDetails).to have_received(:set).with('hook123', 'browser.capabilities.activex', 'Yes') + end + + it 'does not call BrowserDetails.set when results does not match' do + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + instance = build_command_instance(described_class, + 'activex' => 'Yes', + 'beefhook' => 'hook123', + 'results' => 'unknown') + run_post_execute(instance) + expect(BeEF::Core::Models::BrowserDetails).not_to have_received(:set) + end + end +end diff --git a/spec/beef/modules/browser/detect_evernote_clipper_spec.rb b/spec/beef/modules/browser/detect_evernote_clipper_spec.rb new file mode 100644 index 000000000..a471b7cf0 --- /dev/null +++ b/spec/beef/modules/browser/detect_evernote_clipper_spec.rb @@ -0,0 +1,24 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_evernote_clipper/module' + +RSpec.describe Detect_evernote_clipper do + describe '#post_execute' do + it 'saves evernote_clipper from datastore' do + instance = build_command_instance(described_class, 'evernote_clipper' => 'Yes') + results = run_post_execute(instance) + expect(results).to eq('evernote_clipper' => 'Yes') + end + + it 'does not set key when evernote_clipper is nil' do + instance = build_command_instance(described_class, {}) + results = run_post_execute(instance) + expect(results).to eq({}) + end + end +end diff --git a/spec/beef/modules/browser/detect_extensions_spec.rb b/spec/beef/modules/browser/detect_extensions_spec.rb new file mode 100644 index 000000000..18f3b341b --- /dev/null +++ b/spec/beef/modules/browser/detect_extensions_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_extensions/module' + +RSpec.describe Detect_extensions do + describe '#post_execute' do + it 'saves extension from datastore' do + instance = build_command_instance(described_class, 'extension' => 'Chrome, AdBlock') + results = run_post_execute(instance) + expect(results).to eq('extension' => 'Chrome, AdBlock') + end + end +end diff --git a/spec/beef/modules/browser/detect_firebug_spec.rb b/spec/beef/modules/browser/detect_firebug_spec.rb new file mode 100644 index 000000000..32deafc82 --- /dev/null +++ b/spec/beef/modules/browser/detect_firebug_spec.rb @@ -0,0 +1,24 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_firebug/module' + +RSpec.describe Detect_firebug do + describe '#post_execute' do + it 'saves firebug from datastore when present' do + instance = build_command_instance(described_class, 'firebug' => 'Yes') + results = run_post_execute(instance) + expect(results).to eq('firebug' => 'Yes') + end + + it 'saves empty content when firebug is nil' do + instance = build_command_instance(described_class, 'firebug' => nil) + results = run_post_execute(instance) + expect(results).to eq({}) + end + end +end diff --git a/spec/beef/modules/browser/detect_foxit_spec.rb b/spec/beef/modules/browser/detect_foxit_spec.rb new file mode 100644 index 000000000..154b966d6 --- /dev/null +++ b/spec/beef/modules/browser/detect_foxit_spec.rb @@ -0,0 +1,38 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_foxit/module' + +RSpec.describe Detect_foxit do + describe '#post_execute' do + it 'saves foxit from datastore' do + instance = build_command_instance(described_class, 'foxit' => 'Yes') + results = run_post_execute(instance) + expect(results).to eq('foxit' => 'Yes') + end + + it 'calls BrowserDetails.set when results matches foxit=(Yes|No)' do + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + instance = build_command_instance(described_class, + 'foxit' => 'Yes', + 'beefhook' => 'hook123', + 'results' => 'foxit=Yes') + run_post_execute(instance) + expect(BeEF::Core::Models::BrowserDetails).to have_received(:set).with('hook123', 'HasFoxit', 'Yes') + end + + it 'does not call BrowserDetails.set when results does not match' do + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + instance = build_command_instance(described_class, + 'foxit' => 'Yes', + 'beefhook' => 'hook123', + 'results' => 'unknown') + run_post_execute(instance) + expect(BeEF::Core::Models::BrowserDetails).not_to have_received(:set) + end + end +end diff --git a/spec/beef/modules/browser/detect_lastpass_spec.rb b/spec/beef/modules/browser/detect_lastpass_spec.rb new file mode 100644 index 000000000..66ba9df9b --- /dev/null +++ b/spec/beef/modules/browser/detect_lastpass_spec.rb @@ -0,0 +1,24 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_lastpass/module' + +RSpec.describe Detect_lastpass do + describe '#post_execute' do + it 'saves lastpass from datastore' do + instance = build_command_instance(described_class, 'lastpass' => 'Yes') + results = run_post_execute(instance) + expect(results).to eq('lastpass' => 'Yes') + end + + it 'does not set key when lastpass is nil' do + instance = build_command_instance(described_class, {}) + results = run_post_execute(instance) + expect(results).to eq({}) + end + end +end diff --git a/spec/beef/modules/browser/detect_mime_types_spec.rb b/spec/beef/modules/browser/detect_mime_types_spec.rb new file mode 100644 index 000000000..78480744a --- /dev/null +++ b/spec/beef/modules/browser/detect_mime_types_spec.rb @@ -0,0 +1,24 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_mime_types/module' + +RSpec.describe Detect_mime_types do + describe '#post_execute' do + it 'saves mime_types from datastore' do + instance = build_command_instance(described_class, 'mime_types' => 'application/pdf') + results = run_post_execute(instance) + expect(results).to eq('mime_types' => 'application/pdf') + end + + it 'does not set key when mime_types is nil' do + instance = build_command_instance(described_class, {}) + results = run_post_execute(instance) + expect(results).to eq({}) + end + end +end diff --git a/spec/beef/modules/browser/detect_office_spec.rb b/spec/beef/modules/browser/detect_office_spec.rb new file mode 100644 index 000000000..db3d8a1f8 --- /dev/null +++ b/spec/beef/modules/browser/detect_office_spec.rb @@ -0,0 +1,38 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_office/module' + +RSpec.describe Detect_office do + describe '#post_execute' do + it 'saves office from datastore' do + instance = build_command_instance(described_class, 'office' => 'Office 2016') + results = run_post_execute(instance) + expect(results).to eq('office' => 'Office 2016') + end + + it 'calls BrowserDetails.set when results matches office=Office N' do + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + instance = build_command_instance(described_class, + 'office' => 'Office 2016', + 'beefhook' => 'hook123', + 'results' => 'office=Office 2016') + run_post_execute(instance) + expect(BeEF::Core::Models::BrowserDetails).to have_received(:set).with('hook123', 'HasOffice', '2016') + end + + it 'does not call BrowserDetails.set when results does not match' do + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + instance = build_command_instance(described_class, + 'office' => 'None', + 'beefhook' => 'hook123', + 'results' => 'office=None') + run_post_execute(instance) + expect(BeEF::Core::Models::BrowserDetails).not_to have_received(:set) + end + end +end diff --git a/spec/beef/modules/browser/detect_popup_blocker_spec.rb b/spec/beef/modules/browser/detect_popup_blocker_spec.rb new file mode 100644 index 000000000..cec5d0a44 --- /dev/null +++ b/spec/beef/modules/browser/detect_popup_blocker_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_popup_blocker/module' + +RSpec.describe Detect_popup_blocker do + describe '#post_execute' do + it 'saves popup_blocker_enabled from datastore' do + instance = build_command_instance(described_class, 'popup_blocker_enabled' => 'Yes') + results = run_post_execute(instance) + expect(results).to eq('popup_blocker_enabled' => 'Yes') + end + end +end diff --git a/spec/beef/modules/browser/detect_quicktime_spec.rb b/spec/beef/modules/browser/detect_quicktime_spec.rb new file mode 100644 index 000000000..e5a339f5b --- /dev/null +++ b/spec/beef/modules/browser/detect_quicktime_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_quicktime/module' + +RSpec.describe Detect_quicktime do + describe '#post_execute' do + it 'saves quicktime from datastore' do + instance = build_command_instance(described_class, 'quicktime' => 'Yes') + results = run_post_execute(instance) + expect(results).to eq('quicktime' => 'Yes') + end + end +end diff --git a/spec/beef/modules/browser/detect_realplayer_spec.rb b/spec/beef/modules/browser/detect_realplayer_spec.rb new file mode 100644 index 000000000..f2a3a1cfc --- /dev/null +++ b/spec/beef/modules/browser/detect_realplayer_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_realplayer/module' + +RSpec.describe Detect_realplayer do + describe '#post_execute' do + it 'saves realplayer from datastore' do + instance = build_command_instance(described_class, 'realplayer' => 'Yes') + results = run_post_execute(instance) + expect(results).to eq('realplayer' => 'Yes') + end + end +end diff --git a/spec/beef/modules/browser/detect_silverlight_spec.rb b/spec/beef/modules/browser/detect_silverlight_spec.rb new file mode 100644 index 000000000..507e117ae --- /dev/null +++ b/spec/beef/modules/browser/detect_silverlight_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_silverlight/module' + +RSpec.describe Detect_silverlight do + describe '#post_execute' do + it 'saves silverlight from datastore' do + instance = build_command_instance(described_class, 'silverlight' => 'Yes') + results = run_post_execute(instance) + expect(results).to eq('silverlight' => 'Yes') + end + end +end diff --git a/spec/beef/modules/browser/detect_simple_adblock_spec.rb b/spec/beef/modules/browser/detect_simple_adblock_spec.rb new file mode 100644 index 000000000..4ea6be255 --- /dev/null +++ b/spec/beef/modules/browser/detect_simple_adblock_spec.rb @@ -0,0 +1,24 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_simple_adblock/module' + +RSpec.describe Detect_simple_adblock do + describe '#post_execute' do + it 'saves simple_adblock from datastore' do + instance = build_command_instance(described_class, 'simple_adblock' => 'Yes') + results = run_post_execute(instance) + expect(results).to eq('simple_adblock' => 'Yes') + end + + it 'does not set key when simple_adblock is nil' do + instance = build_command_instance(described_class, {}) + results = run_post_execute(instance) + expect(results).to eq({}) + end + end +end diff --git a/spec/beef/modules/browser/detect_toolbars_spec.rb b/spec/beef/modules/browser/detect_toolbars_spec.rb new file mode 100644 index 000000000..88e942d22 --- /dev/null +++ b/spec/beef/modules/browser/detect_toolbars_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_toolbars/module' + +RSpec.describe Detect_toolbars do + describe '#post_execute' do + it 'saves toolbars from datastore' do + instance = build_command_instance(described_class, 'toolbars' => 'Toolbar1, Toolbar2') + results = run_post_execute(instance) + expect(results).to eq('toolbars' => 'Toolbar1, Toolbar2') + end + end +end diff --git a/spec/beef/modules/browser/detect_unity_spec.rb b/spec/beef/modules/browser/detect_unity_spec.rb new file mode 100644 index 000000000..c19995da8 --- /dev/null +++ b/spec/beef/modules/browser/detect_unity_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_unity/module' + +RSpec.describe Detect_unity do + describe '#post_execute' do + it 'saves unity from datastore' do + instance = build_command_instance(described_class, 'unity' => 'Yes') + results = run_post_execute(instance) + expect(results).to eq('unity' => 'Yes') + end + end +end diff --git a/spec/beef/modules/browser/detect_unsafe_activex_spec.rb b/spec/beef/modules/browser/detect_unsafe_activex_spec.rb new file mode 100644 index 000000000..d605c7d85 --- /dev/null +++ b/spec/beef/modules/browser/detect_unsafe_activex_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_unsafe_activex/module' + +RSpec.describe Detect_unsafe_activex do + describe '#post_execute' do + it 'saves unsafe_activex from datastore' do + instance = build_command_instance(described_class, 'unsafe_activex' => 'Yes') + results = run_post_execute(instance) + expect(results).to eq('unsafe_activex' => 'Yes') + end + end +end diff --git a/spec/beef/modules/browser/detect_vlc_spec.rb b/spec/beef/modules/browser/detect_vlc_spec.rb new file mode 100644 index 000000000..82d6f5238 --- /dev/null +++ b/spec/beef/modules/browser/detect_vlc_spec.rb @@ -0,0 +1,28 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_vlc/module' + +RSpec.describe Detect_vlc do + describe '#post_execute' do + it 'saves vlc from datastore' do + instance = build_command_instance(described_class, 'vlc' => 'Yes') + results = run_post_execute(instance) + expect(results).to eq('vlc' => 'Yes') + end + + it 'calls BrowserDetails.set when results matches vlc=(Yes|No)' do + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + instance = build_command_instance(described_class, + 'vlc' => 'Yes', + 'beefhook' => 'hook123', + 'results' => 'vlc=Yes') + run_post_execute(instance) + expect(BeEF::Core::Models::BrowserDetails).to have_received(:set).with('hook123', 'browser.capabilities.vlc', 'Yes') + end + end +end diff --git a/spec/beef/modules/browser/detect_wmp_spec.rb b/spec/beef/modules/browser/detect_wmp_spec.rb new file mode 100644 index 000000000..a56a1e0c9 --- /dev/null +++ b/spec/beef/modules/browser/detect_wmp_spec.rb @@ -0,0 +1,28 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/detect_wmp/module' + +RSpec.describe Detect_wmp do + describe '#post_execute' do + it 'saves wmp from datastore' do + instance = build_command_instance(described_class, 'wmp' => 'Yes') + results = run_post_execute(instance) + expect(results).to eq('wmp' => 'Yes') + end + + it 'calls BrowserDetails.set when results matches wmp=(Yes|No)' do + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + instance = build_command_instance(described_class, + 'wmp' => 'No', + 'beefhook' => 'hook1', + 'results' => 'wmp=No') + run_post_execute(instance) + expect(BeEF::Core::Models::BrowserDetails).to have_received(:set).with('hook1', 'browser.capabilities.wmp', 'No') + end + end +end diff --git a/spec/beef/modules/browser/fingerprint_browser_spec.rb b/spec/beef/modules/browser/fingerprint_browser_spec.rb new file mode 100644 index 000000000..c67c8fa67 --- /dev/null +++ b/spec/beef/modules/browser/fingerprint_browser_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/fingerprint_browser/module' + +RSpec.describe Fingerprint_browser do + describe '#post_execute' do + it 'saves fingerprint and components from datastore' do + instance = build_command_instance(described_class, + 'fingerprint' => 'abc123', + 'components' => '[]') + results = run_post_execute(instance) + expect(results).to include('fingerprint' => 'abc123', 'components' => '[]') + end + + it 'sets fail message when content is empty' do + instance = build_command_instance(described_class, {}) + results = run_post_execute(instance) + expect(results).to eq('fail' => 'Failed to fingerprint browser.') + end + end +end diff --git a/spec/beef/modules/browser/get_visited_domains_spec.rb b/spec/beef/modules/browser/get_visited_domains_spec.rb new file mode 100644 index 000000000..4753aa39a --- /dev/null +++ b/spec/beef/modules/browser/get_visited_domains_spec.rb @@ -0,0 +1,28 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/get_visited_domains/module' + +RSpec.describe Get_visited_domains do + describe '.options' do + it 'returns domains option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.size).to eq(1) + expect(opts.first['name']).to eq('domains') + expect(opts.first['ui_label']).to eq('Specify custom page to check') + end + end + + describe '#post_execute' do + it 'saves results from datastore' do + instance = build_command_instance(described_class, 'results' => 'domain1.com, domain2.com') + results = run_post_execute(instance) + expect(results).to eq('results' => 'domain1.com, domain2.com') + end + end +end diff --git a/spec/beef/modules/browser/get_visited_urls_spec.rb b/spec/beef/modules/browser/get_visited_urls_spec.rb new file mode 100644 index 000000000..faedbbd0c --- /dev/null +++ b/spec/beef/modules/browser/get_visited_urls_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/get_visited_urls/module' + +RSpec.describe Get_visited_urls do + describe '.options' do + it 'returns url textarea option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.first).to include('name' => 'urls', 'type' => 'textarea', 'ui_label' => 'URL(s)') + end + end + + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'https://example.com') + results = run_post_execute(instance) + expect(results).to eq('result' => 'https://example.com') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/ajax_fingerprint_spec.rb b/spec/beef/modules/browser/hooked_origin/ajax_fingerprint_spec.rb new file mode 100644 index 000000000..76cdda73a --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/ajax_fingerprint_spec.rb @@ -0,0 +1,24 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/ajax_fingerprint/module' + +RSpec.describe Ajax_fingerprint do + describe '#post_execute' do + it 'saves script_urls from datastore' do + instance = build_command_instance(described_class, 'script_urls' => ['/a.js', '/b.js']) + results = run_post_execute(instance) + expect(results).to include('script_urls' => ['/a.js', '/b.js']) + end + + it 'sets fail when content is empty' do + instance = build_command_instance(described_class, {}) + results = run_post_execute(instance) + expect(results).to include('fail' => 'Failed to fingerprint ajax.') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/alert_dialog_spec.rb b/spec/beef/modules/browser/hooked_origin/alert_dialog_spec.rb new file mode 100644 index 000000000..404c4f75d --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/alert_dialog_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/alert_dialog/module' + +RSpec.describe Alert_dialog do + describe '.options' do + it 'returns text textarea option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.first).to include('name' => 'text', 'ui_label' => 'Alert text') + end + end + + describe '#post_execute' do + it 'saves User Response message' do + instance = build_command_instance(described_class, {}) + results = run_post_execute(instance) + expect(results).to include('User Response' => "The user clicked the 'OK' button when presented with an alert box.") + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/apache_tomcat_examples_cookie_disclosure_spec.rb b/spec/beef/modules/browser/hooked_origin/apache_tomcat_examples_cookie_disclosure_spec.rb new file mode 100644 index 000000000..7e2dbb896 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/apache_tomcat_examples_cookie_disclosure_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/apache_tomcat_examples_cookie_disclosure/module' + +RSpec.describe Apache_tomcat_examples_cookie_disclosure do + describe '.options' do + it 'returns request_header_servlet_path option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.first).to include('name' => 'request_header_servlet_path') + end + end + + describe '#post_execute' do + it 'saves cookies from datastore' do + instance = build_command_instance(described_class, 'cookies' => 'session=abc') + results = run_post_execute(instance) + expect(results).to eq('cookies' => 'session=abc') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/cisco_asa_password_disclosure_spec.rb b/spec/beef/modules/browser/hooked_origin/cisco_asa_password_disclosure_spec.rb new file mode 100644 index 000000000..02190381c --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/cisco_asa_password_disclosure_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/cisco_asa_password_disclosure/module' + +RSpec.describe Cisco_asa_passwords do + describe '#post_execute' do + it 'saves cisco_asa_passwords from datastore' do + instance = build_command_instance(described_class, 'cisco_asa_passwords' => 'pwd=secret') + results = run_post_execute(instance) + expect(results).to eq('cisco_asa_passwords' => 'pwd=secret') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/clear_console_spec.rb b/spec/beef/modules/browser/hooked_origin/clear_console_spec.rb new file mode 100644 index 000000000..b6f3f3419 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/clear_console_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/clear_console/module' + +RSpec.describe Clear_console do + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'cleared') + results = run_post_execute(instance) + expect(results).to eq('result' => 'cleared') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/deface_web_page_component_spec.rb b/spec/beef/modules/browser/hooked_origin/deface_web_page_component_spec.rb new file mode 100644 index 000000000..a66756db6 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/deface_web_page_component_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/deface_web_page_component/module' + +RSpec.describe Deface_web_page_component do + describe '.options' do + it 'returns deface_selector and deface_content options' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.map { |o| o['name'] }).to include('deface_selector', 'deface_content') + end + end + + describe '#post_execute' do + it 'saves Result from datastore' do + instance = build_command_instance(described_class, 'result' => 'done') + results = run_post_execute(instance) + expect(results).to eq('Result' => 'done') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/deface_web_page_spec.rb b/spec/beef/modules/browser/hooked_origin/deface_web_page_spec.rb new file mode 100644 index 000000000..34b2d67d3 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/deface_web_page_spec.rb @@ -0,0 +1,33 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/deface_web_page/module' + +RSpec.describe Deface_web_page do + describe '.options' do + it 'returns deface options with beef base host' do + config = instance_double(BeEF::Core::Configuration) + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + allow(config).to receive(:beef_proto).and_return('https') + allow(config).to receive(:beef_host).and_return('beef.example') + allow(config).to receive(:beef_port).and_return('3000') + + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.map { |o| o['name'] }).to include('deface_title', 'deface_favicon', 'deface_content') + expect(opts.find { |o| o['name'] == 'deface_favicon' }['value']).to include('https://beef.example:3000') + end + end + + describe '#post_execute' do + it 'saves Result from datastore' do + instance = build_command_instance(described_class, 'result' => 'deface done') + results = run_post_execute(instance) + expect(results).to eq('Result' => 'deface done') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/disable_developer_tools_spec.rb b/spec/beef/modules/browser/hooked_origin/disable_developer_tools_spec.rb new file mode 100644 index 000000000..eaf920946 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/disable_developer_tools_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/disable_developer_tools/module' + +RSpec.describe Disable_developer_tools do + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'disabled') + results = run_post_execute(instance) + expect(results).to eq('result' => 'disabled') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/get_autocomplete_creds_spec.rb b/spec/beef/modules/browser/hooked_origin/get_autocomplete_creds_spec.rb new file mode 100644 index 000000000..ddf618f88 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/get_autocomplete_creds_spec.rb @@ -0,0 +1,24 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/get_autocomplete_creds/module' + +RSpec.describe Get_autocomplete_creds do + describe '.options' do + it 'returns empty array' do + expect(described_class.options).to eq([]) + end + end + + describe '#post_execute' do + it 'saves results from datastore' do + instance = build_command_instance(described_class, 'results' => [{ 'user' => 'a', 'pass' => 'b' }]) + results = run_post_execute(instance) + expect(results).to eq('results' => [{ 'user' => 'a', 'pass' => 'b' }]) + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/get_cookie_spec.rb b/spec/beef/modules/browser/hooked_origin/get_cookie_spec.rb new file mode 100644 index 000000000..f9d9dcf29 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/get_cookie_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/get_cookie/module' + +RSpec.describe Get_cookie do + describe '#post_execute' do + it 'saves cookie from datastore' do + instance = build_command_instance(described_class, 'cookie' => 'session=abc123') + results = run_post_execute(instance) + expect(results).to eq('cookie' => 'session=abc123') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/get_form_values_spec.rb b/spec/beef/modules/browser/hooked_origin/get_form_values_spec.rb new file mode 100644 index 000000000..bce8a144b --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/get_form_values_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/get_form_values/module' + +RSpec.describe Get_form_values do + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'field=value') + results = run_post_execute(instance) + expect(results).to eq('result' => 'field=value') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/get_local_storage_spec.rb b/spec/beef/modules/browser/hooked_origin/get_local_storage_spec.rb new file mode 100644 index 000000000..149409ae5 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/get_local_storage_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/get_local_storage/module' + +RSpec.describe Get_local_storage do + describe '#post_execute' do + it 'saves localStorage from datastore' do + instance = build_command_instance(described_class, 'localStorage' => 'key=value') + results = run_post_execute(instance) + expect(results).to eq('localStorage' => 'key=value') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/get_page_html_iframe_spec.rb b/spec/beef/modules/browser/hooked_origin/get_page_html_iframe_spec.rb new file mode 100644 index 000000000..dc3b369bf --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/get_page_html_iframe_spec.rb @@ -0,0 +1,21 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/get_page_html_iframe/module' + +RSpec.describe Get_page_html_iframe do + describe '#post_execute' do + it 'saves head, body, and iframe_ from datastore' do + instance = build_command_instance(described_class, + 'head' => 'Test', + 'body' => '

Body

', + 'iframe_' => '') + results = run_post_execute(instance) + expect(results).to eq('head' => 'Test', 'body' => '

Body

', 'iframe_' => '') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/get_page_html_spec.rb b/spec/beef/modules/browser/hooked_origin/get_page_html_spec.rb new file mode 100644 index 000000000..91daee32a --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/get_page_html_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/get_page_html/module' + +RSpec.describe Get_page_html do + describe '#post_execute' do + it 'saves head and body from datastore' do + instance = build_command_instance(described_class, 'head' => 'Test', 'body' => '

Hello

') + results = run_post_execute(instance) + expect(results).to eq('head' => 'Test', 'body' => '

Hello

') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/get_page_links_spec.rb b/spec/beef/modules/browser/hooked_origin/get_page_links_spec.rb new file mode 100644 index 000000000..d9b98a303 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/get_page_links_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/get_page_links/module' + +RSpec.describe Get_page_links do + describe '#post_execute' do + it 'saves links from datastore' do + instance = build_command_instance(described_class, 'links' => 'Home') + results = run_post_execute(instance) + expect(results).to eq('links' => 'Home') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/get_session_storage_spec.rb b/spec/beef/modules/browser/hooked_origin/get_session_storage_spec.rb new file mode 100644 index 000000000..9e08a9fc4 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/get_session_storage_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/get_session_storage/module' + +RSpec.describe Get_session_storage do + describe '#post_execute' do + it 'saves sessionStorage from datastore' do + instance = build_command_instance(described_class, 'sessionStorage' => 'key=value') + results = run_post_execute(instance) + expect(results).to eq('sessionStorage' => 'key=value') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/get_stored_credentials_spec.rb b/spec/beef/modules/browser/hooked_origin/get_stored_credentials_spec.rb new file mode 100644 index 000000000..1deed0af1 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/get_stored_credentials_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/get_stored_credentials/module' + +RSpec.describe Get_stored_credentials do + describe '#post_execute' do + it 'saves form_data from datastore' do + instance = build_command_instance(described_class, 'form_data' => 'user=admin&pass=secret') + results = run_post_execute(instance) + expect(results).to eq('form_data' => 'user=admin&pass=secret') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/link_rewrite_click_events_spec.rb b/spec/beef/modules/browser/hooked_origin/link_rewrite_click_events_spec.rb new file mode 100644 index 000000000..2c76f1e10 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/link_rewrite_click_events_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/link_rewrite_click_events/module' + +RSpec.describe Link_rewrite_click_events do + describe '.options' do + it 'returns url option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.first).to include('name' => 'url') + end + end + + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'ok') + results = run_post_execute(instance) + expect(results).to eq('result' => 'ok') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/link_rewrite_spec.rb b/spec/beef/modules/browser/hooked_origin/link_rewrite_spec.rb new file mode 100644 index 000000000..30d36353f --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/link_rewrite_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/link_rewrite/module' + +RSpec.describe Link_rewrite do + describe '.options' do + it 'returns url option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.first).to include('name' => 'url', 'ui_label' => 'URL') + end + end + + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'ok') + results = run_post_execute(instance) + expect(results).to eq('result' => 'ok') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/link_rewrite_sslstrip_spec.rb b/spec/beef/modules/browser/hooked_origin/link_rewrite_sslstrip_spec.rb new file mode 100644 index 000000000..e437a3425 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/link_rewrite_sslstrip_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/link_rewrite_sslstrip/module' + +RSpec.describe Link_rewrite_sslstrip do + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'ok') + results = run_post_execute(instance) + expect(results).to eq('result' => 'ok') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/link_rewrite_tel_spec.rb b/spec/beef/modules/browser/hooked_origin/link_rewrite_tel_spec.rb new file mode 100644 index 000000000..907b360dc --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/link_rewrite_tel_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/link_rewrite_tel/module' + +RSpec.describe Link_rewrite_tel do + describe '.options' do + it 'returns tel_number option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.first).to include('name' => 'tel_number', 'ui_label' => 'Number') + end + end + + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'ok') + results = run_post_execute(instance) + expect(results).to eq('result' => 'ok') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/mobilesafari_address_spoofing_spec.rb b/spec/beef/modules/browser/hooked_origin/mobilesafari_address_spoofing_spec.rb new file mode 100644 index 000000000..8edfcaf0c --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/mobilesafari_address_spoofing_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/mobilesafari_address_spoofing/module' + +RSpec.describe Mobilesafari_address_spoofing do + describe '.options' do + it 'returns fake_url, real_url, domselectah options' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.map { |o| o['name'] }).to include('fake_url', 'real_url', 'domselectah') + end + end + + describe '#post_execute' do + it 'saves results and query from datastore' do + instance = build_command_instance(described_class, 'results' => 'x', 'query' => 'y') + results = run_post_execute(instance) + expect(results).to include('results' => 'x', 'query' => 'y') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/overflow_cookiejar_spec.rb b/spec/beef/modules/browser/hooked_origin/overflow_cookiejar_spec.rb new file mode 100644 index 000000000..b828b4d9c --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/overflow_cookiejar_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/overflow_cookiejar/module' + +RSpec.describe Overflow_cookiejar do + describe '.options' do + it 'returns preserveCookies option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.first).to include('name' => 'preserveCookies') + end + end + + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'ok') + results = run_post_execute(instance) + expect(results).to eq('result' => 'ok') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/prompt_dialog_spec.rb b/spec/beef/modules/browser/hooked_origin/prompt_dialog_spec.rb new file mode 100644 index 000000000..19e19e597 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/prompt_dialog_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/prompt_dialog/module' + +RSpec.describe Prompt_dialog do + describe '.options' do + it 'returns question option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.first).to include('name' => 'question', 'ui_label' => 'Prompt text') + end + end + + describe '#post_execute' do + it 'saves answer from datastore' do + instance = build_command_instance(described_class, 'answer' => 'user input') + results = run_post_execute(instance) + expect(results).to eq('answer' => 'user input') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/remove_stuck_iframes_spec.rb b/spec/beef/modules/browser/hooked_origin/remove_stuck_iframes_spec.rb new file mode 100644 index 000000000..bc90a3a28 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/remove_stuck_iframes_spec.rb @@ -0,0 +1,21 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/remove_stuck_iframes/module' + +RSpec.describe Remove_stuck_iframes do + describe '#post_execute' do + it 'saves head, body, and iframe_ from datastore' do + instance = build_command_instance(described_class, + 'head' => 'Test', + 'body' => '

Body

', + 'iframe_' => '') + results = run_post_execute(instance) + expect(results).to eq('head' => 'Test', 'body' => '

Body

', 'iframe_' => '') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/replace_video_spec.rb b/spec/beef/modules/browser/hooked_origin/replace_video_spec.rb new file mode 100644 index 000000000..559622c06 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/replace_video_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/replace_video/module' + +RSpec.describe Replace_video do + describe '.options' do + it 'returns youtube_id and jquery_selector options' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.map { |o| o['name'] }).to contain_exactly('youtube_id', 'jquery_selector') + end + end + + describe '#post_execute' do + it 'saves Result from datastore' do + instance = build_command_instance(described_class, 'result' => 'replaced') + results = run_post_execute(instance) + expect(results).to eq('Result' => 'replaced') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/rickroll_spec.rb b/spec/beef/modules/browser/hooked_origin/rickroll_spec.rb new file mode 100644 index 000000000..2dbaeffd7 --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/rickroll_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/rickroll/module' + +RSpec.describe Rickroll do + describe '#post_execute' do + it 'saves Result from datastore' do + instance = build_command_instance(described_class, 'result' => 'played') + results = run_post_execute(instance) + expect(results).to eq('Result' => 'played') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/site_redirect_iframe_spec.rb b/spec/beef/modules/browser/hooked_origin/site_redirect_iframe_spec.rb new file mode 100644 index 000000000..0283697ed --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/site_redirect_iframe_spec.rb @@ -0,0 +1,33 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/site_redirect_iframe/module' + +RSpec.describe Site_redirect_iframe do + describe '.options' do + it 'returns iframe options with beef base host' do + config = instance_double(BeEF::Core::Configuration) + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:beef_host).and_return('localhost') + allow(config).to receive(:beef_port).and_return('3000') + + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.map { |o| o['name'] }).to include('iframe_title', 'iframe_favicon', 'iframe_src', 'iframe_timeout') + expect(opts.find { |o| o['name'] == 'iframe_favicon' }['value']).to include('http://localhost:3000') + end + end + + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'done') + results = run_post_execute(instance) + expect(results).to eq('result' => 'done') + end + end +end diff --git a/spec/beef/modules/browser/hooked_origin/site_redirect_spec.rb b/spec/beef/modules/browser/hooked_origin/site_redirect_spec.rb new file mode 100644 index 000000000..937064fdc --- /dev/null +++ b/spec/beef/modules/browser/hooked_origin/site_redirect_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../../spec_helper' +require_relative '../../../../../modules/browser/hooked_origin/site_redirect/module' + +RSpec.describe Site_redirect do + describe '.options' do + it 'returns redirect_url option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.first).to include('name' => 'redirect_url', 'ui_label' => 'Redirect URL') + end + end + + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'redirected') + results = run_post_execute(instance) + expect(results).to eq('result' => 'redirected') + end + end +end diff --git a/spec/beef/modules/browser/play_sound_spec.rb b/spec/beef/modules/browser/play_sound_spec.rb new file mode 100644 index 000000000..de89c8421 --- /dev/null +++ b/spec/beef/modules/browser/play_sound_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/play_sound/module' + +RSpec.describe Play_sound do + describe '.options' do + it 'returns sound_file_uri option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.first).to include('name' => 'sound_file_uri', 'ui_label' => 'Sound File Path') + end + end + + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'played') + results = run_post_execute(instance) + expect(results).to eq('result' => 'played') + end + end +end diff --git a/spec/beef/modules/browser/remove_hook_element_spec.rb b/spec/beef/modules/browser/remove_hook_element_spec.rb new file mode 100644 index 000000000..3b04fcdcb --- /dev/null +++ b/spec/beef/modules/browser/remove_hook_element_spec.rb @@ -0,0 +1,24 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/remove_hook_element/module' + +RSpec.describe Remove_hook_element do + describe '#post_execute' do + it 'saves result from datastore when present' do + instance = build_command_instance(described_class, 'result' => 'removed') + results = run_post_execute(instance) + expect(results).to eq('result' => 'removed') + end + + it 'saves empty content when result is nil' do + instance = build_command_instance(described_class, 'result' => nil) + results = run_post_execute(instance) + expect(results).to eq({}) + end + end +end diff --git a/spec/beef/modules/browser/spyder_eye_spec.rb b/spec/beef/modules/browser/spyder_eye_spec.rb new file mode 100644 index 000000000..88ba9f270 --- /dev/null +++ b/spec/beef/modules/browser/spyder_eye_spec.rb @@ -0,0 +1,41 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/spyder_eye/module' + +RSpec.describe Spyder_eye do + describe '.options' do + it 'returns repeat and delay options' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.map { |o| o['name'] }).to contain_exactly('repeat', 'delay') + end + end + + describe '#post_execute' do + it 'saves results from datastore and unbinds asset' do + handler = instance_double('AssetHandler') + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + allow(handler).to receive(:unbind) + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).and_return('127.0.0.1') + allow(File).to receive(:open).and_yield(StringIO.new) + + instance = build_command_instance(described_class, + 'results' => 'image=data:image/png;base64,iVBORw0KGgo=', + 'beefhook' => 'hook1', + 'cid' => '1') + allow(instance).to receive(:print_info) + allow(instance).to receive(:print_error) + allow(BeEF::Core::Logger.instance).to receive(:register) + + results = run_post_execute(instance) + + expect(results).to eq('results' => 'image=data:image/png;base64,iVBORw0KGgo=') + expect(handler).to have_received(:unbind).with('/h2c.js') + end + end +end diff --git a/spec/beef/modules/browser/unhook_spec.rb b/spec/beef/modules/browser/unhook_spec.rb new file mode 100644 index 000000000..bb0fd24a4 --- /dev/null +++ b/spec/beef/modules/browser/unhook_spec.rb @@ -0,0 +1,24 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/unhook/module' + +RSpec.describe Unhook do + describe '#post_execute' do + it 'saves result from datastore when present' do + instance = build_command_instance(described_class, 'result' => 'unhooked') + results = run_post_execute(instance) + expect(results).to eq('result' => 'unhooked') + end + + it 'saves empty content when result is nil' do + instance = build_command_instance(described_class, 'result' => nil) + results = run_post_execute(instance) + expect(results).to eq({}) + end + end +end diff --git a/spec/beef/modules/browser/webcam_flash_spec.rb b/spec/beef/modules/browser/webcam_flash_spec.rb new file mode 100644 index 000000000..dd52da645 --- /dev/null +++ b/spec/beef/modules/browser/webcam_flash_spec.rb @@ -0,0 +1,33 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/webcam_flash/module' + +RSpec.describe Webcam_flash do + describe '.options' do + it 'returns social_engineering and picture options' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.map { |o| o['name'] }).to include('social_engineering_title', 'no_of_pictures', 'interval') + end + end + + describe '#post_execute' do + it 'saves result and picture from datastore and unbinds assets' do + handler = instance_double('AssetHandler') + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + allow(handler).to receive(:unbind) + + instance = build_command_instance(described_class, 'result' => 'ok', 'picture' => 'base64data') + results = run_post_execute(instance) + + expect(results).to eq('result' => 'ok', 'picture' => 'base64data') + expect(handler).to have_received(:unbind).with('/takeit.swf') + expect(handler).to have_received(:unbind).with('/swfobject.js') + end + end +end diff --git a/spec/beef/modules/browser/webcam_html5_spec.rb b/spec/beef/modules/browser/webcam_html5_spec.rb new file mode 100644 index 000000000..d043477dd --- /dev/null +++ b/spec/beef/modules/browser/webcam_html5_spec.rb @@ -0,0 +1,32 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/webcam_html5/module' + +RSpec.describe Webcam_html5 do + describe '.options' do + it 'returns choice combobox option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.first).to include('name' => 'choice', 'type' => 'combobox') + end + end + + describe '#post_execute' do + it 'saves result and image from datastore' do + instance = build_command_instance(described_class, 'result' => 'ok', 'image' => 'data:image/png;base64,abc') + results = run_post_execute(instance) + expect(results).to eq('result' => 'ok', 'image' => 'data:image/png;base64,abc') + end + + it 'omits nil keys' do + instance = build_command_instance(described_class, {}) + results = run_post_execute(instance) + expect(results).to eq({}) + end + end +end diff --git a/spec/beef/modules/browser/webcam_permission_check_spec.rb b/spec/beef/modules/browser/webcam_permission_check_spec.rb new file mode 100644 index 000000000..e04a6ff07 --- /dev/null +++ b/spec/beef/modules/browser/webcam_permission_check_spec.rb @@ -0,0 +1,24 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/browser/webcam_permission_check/module' + +RSpec.describe Webcam_permission_check do + describe '#post_execute' do + it 'unbinds cameraCheck and swfobject assets' do + handler = instance_double('AssetHandler') + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + allow(handler).to receive(:unbind) + + instance = build_command_instance(described_class, {}) + instance.post_execute + + expect(handler).to have_received(:unbind).with('/cameraCheck.swf') + expect(handler).to have_received(:unbind).with('/swfobject.js') + end + end +end From 238922c7dff8f948d22d27e61549d0018027cce3 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Mon, 2 Feb 2026 16:58:04 +1000 Subject: [PATCH 21/37] TEST: module/debug directory specs --- .../modules/debug/test_beef_debug_spec.rb | 28 +++++++++++++ .../modules/debug/test_cors_request_spec.rb | 29 ++++++++++++++ .../debug/test_dns_tunnel_client_spec.rb | 34 ++++++++++++++++ .../modules/debug/test_get_variable_spec.rb | 19 +++++++++ .../modules/debug/test_http_redirect_spec.rb | 28 +++++++++++++ .../debug/test_network_request_spec.rb | 39 +++++++++++++++++++ .../debug/test_return_ascii_chars_spec.rb | 18 +++++++++ .../modules/debug/test_return_image_spec.rb | 18 +++++++++ .../debug/test_return_long_string_spec.rb | 30 ++++++++++++++ 9 files changed, 243 insertions(+) create mode 100644 spec/beef/modules/debug/test_beef_debug_spec.rb create mode 100644 spec/beef/modules/debug/test_cors_request_spec.rb create mode 100644 spec/beef/modules/debug/test_dns_tunnel_client_spec.rb create mode 100644 spec/beef/modules/debug/test_get_variable_spec.rb create mode 100644 spec/beef/modules/debug/test_http_redirect_spec.rb create mode 100644 spec/beef/modules/debug/test_network_request_spec.rb create mode 100644 spec/beef/modules/debug/test_return_ascii_chars_spec.rb create mode 100644 spec/beef/modules/debug/test_return_image_spec.rb create mode 100644 spec/beef/modules/debug/test_return_long_string_spec.rb diff --git a/spec/beef/modules/debug/test_beef_debug_spec.rb b/spec/beef/modules/debug/test_beef_debug_spec.rb new file mode 100644 index 000000000..f143d4e1e --- /dev/null +++ b/spec/beef/modules/debug/test_beef_debug_spec.rb @@ -0,0 +1,28 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/debug/test_beef_debug/module' + +RSpec.describe Test_beef_debug do + describe '.options' do + it 'returns an array of option hashes' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.size).to eq(1) + expect(opts.first).to include('name' => 'msg', 'ui_label' => 'Debug Message') + expect(opts.first['value']).to include('beef.debug()') + end + end + + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'debug output') + results = run_post_execute(instance) + expect(results).to eq('Result' => 'debug output') + end + end +end diff --git a/spec/beef/modules/debug/test_cors_request_spec.rb b/spec/beef/modules/debug/test_cors_request_spec.rb new file mode 100644 index 000000000..c24fdd6e5 --- /dev/null +++ b/spec/beef/modules/debug/test_cors_request_spec.rb @@ -0,0 +1,29 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/debug/test_cors_request/module' + +RSpec.describe Test_cors_request do + describe '.options' do + it 'returns method, url, and data options' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.size).to eq(3) + expect(opts.map { |o| o['name'] }).to contain_exactly('method', 'url', 'data') + expect(opts.find { |o| o['name'] == 'method' }['value']).to eq('GET') + expect(opts.find { |o| o['name'] == 'data' }['value']).to eq('postdata') + end + end + + describe '#post_execute' do + it 'saves response from datastore' do + instance = build_command_instance(described_class, 'response' => 'cors body') + results = run_post_execute(instance) + expect(results).to eq('response' => 'cors body') + end + end +end diff --git a/spec/beef/modules/debug/test_dns_tunnel_client_spec.rb b/spec/beef/modules/debug/test_dns_tunnel_client_spec.rb new file mode 100644 index 000000000..8089bf5d1 --- /dev/null +++ b/spec/beef/modules/debug/test_dns_tunnel_client_spec.rb @@ -0,0 +1,34 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/debug/test_dns_tunnel_client/module' + +RSpec.describe Test_dns_tunnel_client do + before do + config = instance_double(BeEF::Core::Configuration) + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + end + + describe '.options' do + it 'returns domain and data options' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.size).to eq(2) + expect(opts.map { |o| o['name'] }).to contain_exactly('domain', 'data') + expect(opts.find { |o| o['name'] == 'domain' }['value']).to eq('browserhacker.com') + expect(opts.find { |o| o['name'] == 'data' }['value']).to include('Lorem ipsum') + end + end + + describe '#post_execute' do + it 'saves dns_requests from datastore' do + instance = build_command_instance(described_class, 'dns_requests' => 'request log') + results = run_post_execute(instance) + expect(results).to eq('dns_requests' => 'request log') + end + end +end diff --git a/spec/beef/modules/debug/test_get_variable_spec.rb b/spec/beef/modules/debug/test_get_variable_spec.rb new file mode 100644 index 000000000..21c12d8f1 --- /dev/null +++ b/spec/beef/modules/debug/test_get_variable_spec.rb @@ -0,0 +1,19 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/debug/test_get_variable/module' + +RSpec.describe Test_get_variable do + describe '.options' do + it 'returns payload_name option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.size).to eq(1) + expect(opts.first).to include('name' => 'payload_name', 'ui_label' => 'Payload Name', 'value' => 'message') + end + end +end diff --git a/spec/beef/modules/debug/test_http_redirect_spec.rb b/spec/beef/modules/debug/test_http_redirect_spec.rb new file mode 100644 index 000000000..3c0371dd6 --- /dev/null +++ b/spec/beef/modules/debug/test_http_redirect_spec.rb @@ -0,0 +1,28 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/debug/test_http_redirect/module' + +RSpec.describe Test_http_redirect do + describe '#post_execute' do + it 'saves result from datastore' do + instance = build_command_instance(described_class, 'result' => 'redirect done') + results = run_post_execute(instance) + expect(results).to eq('Result' => 'redirect done') + end + end + + describe '#pre_send' do + it 'binds redirect via AssetHandler' do + instance = described_class.allocate + handler = instance_double(BeEF::Core::NetworkStack::Handlers::AssetHandler) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + expect(handler).to receive(:bind_redirect).with('https://beefproject.com', '/redirect') + instance.pre_send + end + end +end diff --git a/spec/beef/modules/debug/test_network_request_spec.rb b/spec/beef/modules/debug/test_network_request_spec.rb new file mode 100644 index 000000000..f2c192903 --- /dev/null +++ b/spec/beef/modules/debug/test_network_request_spec.rb @@ -0,0 +1,39 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/debug/test_network_request/module' + +RSpec.describe Test_network_request do + before do + config = instance_double(BeEF::Core::Configuration) + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:get).with('beef.http.hook_file').and_return('/hook.js') + end + + describe '.options' do + it 'returns scheme, method, domain, port, path, anchor, data, timeout, dataType' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.size).to eq(9) + names = opts.map { |o| o['name'] } + expect(names).to include('scheme', 'method', 'domain', 'port', 'path', 'anchor', 'data', 'timeout', 'dataType') + expect(opts.find { |o| o['name'] == 'domain' }['value']).to eq('127.0.0.1') + expect(opts.find { |o| o['name'] == 'port' }['value']).to eq('3000') + expect(opts.find { |o| o['name'] == 'path' }['value']).to eq('/hook.js') + end + end + + describe '#post_execute' do + it 'saves response from datastore' do + instance = build_command_instance(described_class, 'response' => 'ok') + results = run_post_execute(instance) + expect(results).to eq('response' => 'ok') + end + end +end diff --git a/spec/beef/modules/debug/test_return_ascii_chars_spec.rb b/spec/beef/modules/debug/test_return_ascii_chars_spec.rb new file mode 100644 index 000000000..19253b221 --- /dev/null +++ b/spec/beef/modules/debug/test_return_ascii_chars_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/debug/test_return_ascii_chars/module' + +RSpec.describe Test_return_ascii_chars do + describe '#post_execute' do + it 'saves result_string from datastore' do + instance = build_command_instance(described_class, 'result_string' => 'ascii data') + results = run_post_execute(instance) + expect(results).to eq('Result String' => 'ascii data') + end + end +end diff --git a/spec/beef/modules/debug/test_return_image_spec.rb b/spec/beef/modules/debug/test_return_image_spec.rb new file mode 100644 index 000000000..167354253 --- /dev/null +++ b/spec/beef/modules/debug/test_return_image_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/debug/test_return_image/module' + +RSpec.describe Test_return_image do + describe '#post_execute' do + it 'saves image from datastore' do + instance = build_command_instance(described_class, 'image' => 'base64data') + results = run_post_execute(instance) + expect(results).to eq('image' => 'base64data') + end + end +end diff --git a/spec/beef/modules/debug/test_return_long_string_spec.rb b/spec/beef/modules/debug/test_return_long_string_spec.rb new file mode 100644 index 000000000..af1504b6b --- /dev/null +++ b/spec/beef/modules/debug/test_return_long_string_spec.rb @@ -0,0 +1,30 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/debug/test_return_long_string/module' + +RSpec.describe Test_return_long_string do + describe '.options' do + it 'returns repeat and repeat_string options' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.size).to eq(2) + names = opts.map { |o| o['name'] } + expect(names).to contain_exactly('repeat', 'repeat_string') + expect(opts.find { |o| o['name'] == 'repeat' }['value']).to eq('1024') + expect(opts.find { |o| o['name'] == 'repeat_string' }['value']).to eq('\u00AE') + end + end + + describe '#post_execute' do + it 'saves result_string from datastore' do + instance = build_command_instance(described_class, 'result_string' => 'long string') + results = run_post_execute(instance) + expect(results).to eq('Result String' => 'long string') + end + end +end From 045b9435131724a791669def86fe50d4fdc5f134 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Mon, 2 Feb 2026 17:02:13 +1000 Subject: [PATCH 22/37] TEST: modules/chrome_extensions specs --- .../chrome_extensions/execute_tabs_spec.rb | 26 +++++++++++++++++ .../chrome_extensions/get_all_cookies_spec.rb | 26 +++++++++++++++++ .../grab_google_contacts_spec.rb | 18 ++++++++++++ .../chrome_extensions/inject_beef_spec.rb | 18 ++++++++++++ .../chrome_extensions/screenshot_spec.rb | 18 ++++++++++++ .../chrome_extensions/send_gvoice_sms_spec.rb | 29 +++++++++++++++++++ 6 files changed, 135 insertions(+) create mode 100644 spec/beef/modules/chrome_extensions/execute_tabs_spec.rb create mode 100644 spec/beef/modules/chrome_extensions/get_all_cookies_spec.rb create mode 100644 spec/beef/modules/chrome_extensions/grab_google_contacts_spec.rb create mode 100644 spec/beef/modules/chrome_extensions/inject_beef_spec.rb create mode 100644 spec/beef/modules/chrome_extensions/screenshot_spec.rb create mode 100644 spec/beef/modules/chrome_extensions/send_gvoice_sms_spec.rb diff --git a/spec/beef/modules/chrome_extensions/execute_tabs_spec.rb b/spec/beef/modules/chrome_extensions/execute_tabs_spec.rb new file mode 100644 index 000000000..f99cfe716 --- /dev/null +++ b/spec/beef/modules/chrome_extensions/execute_tabs_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/chrome_extensions/execute_tabs/module' + +RSpec.describe Execute_tabs do + describe '.options' do + it 'returns url and theJS options' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.map { |o| o['name'] }).to include('url', 'theJS') + end + end + + describe '#post_execute' do + it 'saves Return from datastore' do + instance = build_command_instance(described_class, 'return' => 'executed') + results = run_post_execute(instance) + expect(results).to eq('Return' => 'executed') + end + end +end diff --git a/spec/beef/modules/chrome_extensions/get_all_cookies_spec.rb b/spec/beef/modules/chrome_extensions/get_all_cookies_spec.rb new file mode 100644 index 000000000..bb83a50ec --- /dev/null +++ b/spec/beef/modules/chrome_extensions/get_all_cookies_spec.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/chrome_extensions/get_all_cookies/module' + +RSpec.describe Get_all_cookies do + describe '.options' do + it 'returns url option' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.first).to include('name' => 'url', 'ui_label' => 'Domain (e.g. http://facebook.com)') + end + end + + describe '#post_execute' do + it 'saves Return from datastore' do + instance = build_command_instance(described_class, 'return' => 'cookies data') + results = run_post_execute(instance) + expect(results).to eq('Return' => 'cookies data') + end + end +end diff --git a/spec/beef/modules/chrome_extensions/grab_google_contacts_spec.rb b/spec/beef/modules/chrome_extensions/grab_google_contacts_spec.rb new file mode 100644 index 000000000..3837236ea --- /dev/null +++ b/spec/beef/modules/chrome_extensions/grab_google_contacts_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/chrome_extensions/grab_google_contacts/module' + +RSpec.describe Grab_google_contacts do + describe '#post_execute' do + it 'saves Return from datastore' do + instance = build_command_instance(described_class, 'return' => 'contacts data') + results = run_post_execute(instance) + expect(results).to eq('Return' => 'contacts data') + end + end +end diff --git a/spec/beef/modules/chrome_extensions/inject_beef_spec.rb b/spec/beef/modules/chrome_extensions/inject_beef_spec.rb new file mode 100644 index 000000000..7e54d40c0 --- /dev/null +++ b/spec/beef/modules/chrome_extensions/inject_beef_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/chrome_extensions/inject_beef/module' + +RSpec.describe Inject_beef do + describe '#post_execute' do + it 'saves Return from datastore' do + instance = build_command_instance(described_class, 'return' => 'injected') + results = run_post_execute(instance) + expect(results).to eq('Return' => 'injected') + end + end +end diff --git a/spec/beef/modules/chrome_extensions/screenshot_spec.rb b/spec/beef/modules/chrome_extensions/screenshot_spec.rb new file mode 100644 index 000000000..a684dabab --- /dev/null +++ b/spec/beef/modules/chrome_extensions/screenshot_spec.rb @@ -0,0 +1,18 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/chrome_extensions/screenshot/module' + +RSpec.describe Screenshot do + describe '#post_execute' do + it 'saves Return from datastore' do + instance = build_command_instance(described_class, 'return' => 'screenshot data') + results = run_post_execute(instance) + expect(results).to eq('Return' => 'screenshot data') + end + end +end diff --git a/spec/beef/modules/chrome_extensions/send_gvoice_sms_spec.rb b/spec/beef/modules/chrome_extensions/send_gvoice_sms_spec.rb new file mode 100644 index 000000000..6e7a6b92c --- /dev/null +++ b/spec/beef/modules/chrome_extensions/send_gvoice_sms_spec.rb @@ -0,0 +1,29 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +require_relative '../../../spec_helper' +require_relative '../../../../modules/chrome_extensions/send_gvoice_sms/module' + +RSpec.describe Send_gvoice_sms do + describe '.options' do + it 'returns to and message options' do + opts = described_class.options + expect(opts).to be_an(Array) + expect(opts.map { |o| o['name'] }).to include('to', 'message') + end + end + + describe '#post_execute' do + it 'saves To, Message, Status from datastore' do + instance = build_command_instance(described_class, + 'to' => '1234567890', + 'message' => 'Hello', + 'status' => 'sent') + results = run_post_execute(instance) + expect(results).to include('To' => '1234567890', 'Message' => 'Hello', 'Status' => 'sent') + end + end +end From 6ffc9cd1927e51591e3634c0413ce39dbc89afca Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Thu, 5 Feb 2026 11:21:15 +1000 Subject: [PATCH 23/37] REMOVE: individual specs --- .../browser/avant_steal_history_spec.rb | 26 ------------ .../browser/browser_fingerprinting_spec.rb | 26 ------------ .../modules/browser/detect_activex_spec.rb | 38 ----------------- .../browser/detect_evernote_clipper_spec.rb | 24 ----------- .../modules/browser/detect_extensions_spec.rb | 18 -------- .../modules/browser/detect_firebug_spec.rb | 24 ----------- .../beef/modules/browser/detect_foxit_spec.rb | 38 ----------------- .../modules/browser/detect_lastpass_spec.rb | 24 ----------- .../modules/browser/detect_mime_types_spec.rb | 24 ----------- .../modules/browser/detect_office_spec.rb | 38 ----------------- .../browser/detect_popup_blocker_spec.rb | 18 -------- .../modules/browser/detect_quicktime_spec.rb | 18 -------- .../modules/browser/detect_realplayer_spec.rb | 18 -------- .../browser/detect_silverlight_spec.rb | 18 -------- .../browser/detect_simple_adblock_spec.rb | 24 ----------- .../modules/browser/detect_toolbars_spec.rb | 18 -------- .../beef/modules/browser/detect_unity_spec.rb | 18 -------- .../browser/detect_unsafe_activex_spec.rb | 18 -------- spec/beef/modules/browser/detect_vlc_spec.rb | 28 ------------- spec/beef/modules/browser/detect_wmp_spec.rb | 28 ------------- .../browser/fingerprint_browser_spec.rb | 26 ------------ .../browser/get_visited_domains_spec.rb | 28 ------------- .../modules/browser/get_visited_urls_spec.rb | 26 ------------ .../hooked_origin/ajax_fingerprint_spec.rb | 24 ----------- .../hooked_origin/alert_dialog_spec.rb | 26 ------------ ..._tomcat_examples_cookie_disclosure_spec.rb | 26 ------------ .../cisco_asa_password_disclosure_spec.rb | 18 -------- .../hooked_origin/clear_console_spec.rb | 18 -------- .../deface_web_page_component_spec.rb | 26 ------------ .../hooked_origin/deface_web_page_spec.rb | 33 --------------- .../disable_developer_tools_spec.rb | 18 -------- .../get_autocomplete_creds_spec.rb | 24 ----------- .../browser/hooked_origin/get_cookie_spec.rb | 18 -------- .../hooked_origin/get_form_values_spec.rb | 18 -------- .../hooked_origin/get_local_storage_spec.rb | 18 -------- .../get_page_html_iframe_spec.rb | 21 ---------- .../hooked_origin/get_page_html_spec.rb | 18 -------- .../hooked_origin/get_page_links_spec.rb | 18 -------- .../hooked_origin/get_session_storage_spec.rb | 18 -------- .../get_stored_credentials_spec.rb | 18 -------- .../link_rewrite_click_events_spec.rb | 26 ------------ .../hooked_origin/link_rewrite_spec.rb | 26 ------------ .../link_rewrite_sslstrip_spec.rb | 18 -------- .../hooked_origin/link_rewrite_tel_spec.rb | 26 ------------ .../mobilesafari_address_spoofing_spec.rb | 26 ------------ .../hooked_origin/overflow_cookiejar_spec.rb | 26 ------------ .../hooked_origin/prompt_dialog_spec.rb | 26 ------------ .../remove_stuck_iframes_spec.rb | 21 ---------- .../hooked_origin/replace_video_spec.rb | 26 ------------ .../browser/hooked_origin/rickroll_spec.rb | 18 -------- .../site_redirect_iframe_spec.rb | 33 --------------- .../hooked_origin/site_redirect_spec.rb | 26 ------------ spec/beef/modules/browser/play_sound_spec.rb | 26 ------------ .../browser/remove_hook_element_spec.rb | 24 ----------- spec/beef/modules/browser/spyder_eye_spec.rb | 41 ------------------- spec/beef/modules/browser/unhook_spec.rb | 24 ----------- .../beef/modules/browser/webcam_flash_spec.rb | 33 --------------- .../beef/modules/browser/webcam_html5_spec.rb | 32 --------------- .../browser/webcam_permission_check_spec.rb | 24 ----------- .../chrome_extensions/execute_tabs_spec.rb | 26 ------------ .../chrome_extensions/get_all_cookies_spec.rb | 26 ------------ .../grab_google_contacts_spec.rb | 18 -------- .../chrome_extensions/inject_beef_spec.rb | 18 -------- .../chrome_extensions/screenshot_spec.rb | 18 -------- .../chrome_extensions/send_gvoice_sms_spec.rb | 29 ------------- .../modules/debug/test_beef_debug_spec.rb | 28 ------------- .../modules/debug/test_cors_request_spec.rb | 29 ------------- .../debug/test_dns_tunnel_client_spec.rb | 34 --------------- .../modules/debug/test_get_variable_spec.rb | 19 --------- .../modules/debug/test_http_redirect_spec.rb | 28 ------------- .../debug/test_network_request_spec.rb | 39 ------------------ .../debug/test_return_ascii_chars_spec.rb | 18 -------- .../modules/debug/test_return_image_spec.rb | 18 -------- .../debug/test_return_long_string_spec.rb | 30 -------------- 74 files changed, 1806 deletions(-) delete mode 100644 spec/beef/modules/browser/avant_steal_history_spec.rb delete mode 100644 spec/beef/modules/browser/browser_fingerprinting_spec.rb delete mode 100644 spec/beef/modules/browser/detect_activex_spec.rb delete mode 100644 spec/beef/modules/browser/detect_evernote_clipper_spec.rb delete mode 100644 spec/beef/modules/browser/detect_extensions_spec.rb delete mode 100644 spec/beef/modules/browser/detect_firebug_spec.rb delete mode 100644 spec/beef/modules/browser/detect_foxit_spec.rb delete mode 100644 spec/beef/modules/browser/detect_lastpass_spec.rb delete mode 100644 spec/beef/modules/browser/detect_mime_types_spec.rb delete mode 100644 spec/beef/modules/browser/detect_office_spec.rb delete mode 100644 spec/beef/modules/browser/detect_popup_blocker_spec.rb delete mode 100644 spec/beef/modules/browser/detect_quicktime_spec.rb delete mode 100644 spec/beef/modules/browser/detect_realplayer_spec.rb delete mode 100644 spec/beef/modules/browser/detect_silverlight_spec.rb delete mode 100644 spec/beef/modules/browser/detect_simple_adblock_spec.rb delete mode 100644 spec/beef/modules/browser/detect_toolbars_spec.rb delete mode 100644 spec/beef/modules/browser/detect_unity_spec.rb delete mode 100644 spec/beef/modules/browser/detect_unsafe_activex_spec.rb delete mode 100644 spec/beef/modules/browser/detect_vlc_spec.rb delete mode 100644 spec/beef/modules/browser/detect_wmp_spec.rb delete mode 100644 spec/beef/modules/browser/fingerprint_browser_spec.rb delete mode 100644 spec/beef/modules/browser/get_visited_domains_spec.rb delete mode 100644 spec/beef/modules/browser/get_visited_urls_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/ajax_fingerprint_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/alert_dialog_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/apache_tomcat_examples_cookie_disclosure_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/cisco_asa_password_disclosure_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/clear_console_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/deface_web_page_component_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/deface_web_page_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/disable_developer_tools_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/get_autocomplete_creds_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/get_cookie_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/get_form_values_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/get_local_storage_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/get_page_html_iframe_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/get_page_html_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/get_page_links_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/get_session_storage_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/get_stored_credentials_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/link_rewrite_click_events_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/link_rewrite_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/link_rewrite_sslstrip_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/link_rewrite_tel_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/mobilesafari_address_spoofing_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/overflow_cookiejar_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/prompt_dialog_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/remove_stuck_iframes_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/replace_video_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/rickroll_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/site_redirect_iframe_spec.rb delete mode 100644 spec/beef/modules/browser/hooked_origin/site_redirect_spec.rb delete mode 100644 spec/beef/modules/browser/play_sound_spec.rb delete mode 100644 spec/beef/modules/browser/remove_hook_element_spec.rb delete mode 100644 spec/beef/modules/browser/spyder_eye_spec.rb delete mode 100644 spec/beef/modules/browser/unhook_spec.rb delete mode 100644 spec/beef/modules/browser/webcam_flash_spec.rb delete mode 100644 spec/beef/modules/browser/webcam_html5_spec.rb delete mode 100644 spec/beef/modules/browser/webcam_permission_check_spec.rb delete mode 100644 spec/beef/modules/chrome_extensions/execute_tabs_spec.rb delete mode 100644 spec/beef/modules/chrome_extensions/get_all_cookies_spec.rb delete mode 100644 spec/beef/modules/chrome_extensions/grab_google_contacts_spec.rb delete mode 100644 spec/beef/modules/chrome_extensions/inject_beef_spec.rb delete mode 100644 spec/beef/modules/chrome_extensions/screenshot_spec.rb delete mode 100644 spec/beef/modules/chrome_extensions/send_gvoice_sms_spec.rb delete mode 100644 spec/beef/modules/debug/test_beef_debug_spec.rb delete mode 100644 spec/beef/modules/debug/test_cors_request_spec.rb delete mode 100644 spec/beef/modules/debug/test_dns_tunnel_client_spec.rb delete mode 100644 spec/beef/modules/debug/test_get_variable_spec.rb delete mode 100644 spec/beef/modules/debug/test_http_redirect_spec.rb delete mode 100644 spec/beef/modules/debug/test_network_request_spec.rb delete mode 100644 spec/beef/modules/debug/test_return_ascii_chars_spec.rb delete mode 100644 spec/beef/modules/debug/test_return_image_spec.rb delete mode 100644 spec/beef/modules/debug/test_return_long_string_spec.rb diff --git a/spec/beef/modules/browser/avant_steal_history_spec.rb b/spec/beef/modules/browser/avant_steal_history_spec.rb deleted file mode 100644 index 0d16649cb..000000000 --- a/spec/beef/modules/browser/avant_steal_history_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/avant_steal_history/module' - -RSpec.describe Avant_steal_history do - describe '.options' do - it 'returns cId option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.first).to include('name' => 'cId', 'ui_label' => 'Command ID') - end - end - - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'done') - results = run_post_execute(instance) - expect(results).to eq('result' => 'done') - end - end -end diff --git a/spec/beef/modules/browser/browser_fingerprinting_spec.rb b/spec/beef/modules/browser/browser_fingerprinting_spec.rb deleted file mode 100644 index 00b310710..000000000 --- a/spec/beef/modules/browser/browser_fingerprinting_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/browser_fingerprinting/module' - -RSpec.describe Browser_fingerprinting do - describe '#post_execute' do - it 'saves browser_type and browser_version from datastore' do - instance = build_command_instance(described_class, - 'browser_type' => 'Chrome', - 'browser_version' => '120') - results = run_post_execute(instance) - expect(results).to include('browser_type' => 'Chrome', 'browser_version' => '120') - end - - it 'sets fail message when content is empty' do - instance = build_command_instance(described_class, {}) - results = run_post_execute(instance) - expect(results).to eq('fail' => 'Failed to fingerprint browser.') - end - end -end diff --git a/spec/beef/modules/browser/detect_activex_spec.rb b/spec/beef/modules/browser/detect_activex_spec.rb deleted file mode 100644 index 1a6f1d6d9..000000000 --- a/spec/beef/modules/browser/detect_activex_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_activex/module' - -RSpec.describe Detect_activex do - describe '#post_execute' do - it 'saves activex from datastore' do - instance = build_command_instance(described_class, 'activex' => 'Yes', 'results' => '') - results = run_post_execute(instance) - expect(results).to eq('activex' => 'Yes') - end - - it 'calls BrowserDetails.set when results matches activex=(Yes|No)' do - allow(BeEF::Core::Models::BrowserDetails).to receive(:set) - instance = build_command_instance(described_class, - 'activex' => 'Yes', - 'beefhook' => 'hook123', - 'results' => 'activex=Yes') - run_post_execute(instance) - expect(BeEF::Core::Models::BrowserDetails).to have_received(:set).with('hook123', 'browser.capabilities.activex', 'Yes') - end - - it 'does not call BrowserDetails.set when results does not match' do - allow(BeEF::Core::Models::BrowserDetails).to receive(:set) - instance = build_command_instance(described_class, - 'activex' => 'Yes', - 'beefhook' => 'hook123', - 'results' => 'unknown') - run_post_execute(instance) - expect(BeEF::Core::Models::BrowserDetails).not_to have_received(:set) - end - end -end diff --git a/spec/beef/modules/browser/detect_evernote_clipper_spec.rb b/spec/beef/modules/browser/detect_evernote_clipper_spec.rb deleted file mode 100644 index a471b7cf0..000000000 --- a/spec/beef/modules/browser/detect_evernote_clipper_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_evernote_clipper/module' - -RSpec.describe Detect_evernote_clipper do - describe '#post_execute' do - it 'saves evernote_clipper from datastore' do - instance = build_command_instance(described_class, 'evernote_clipper' => 'Yes') - results = run_post_execute(instance) - expect(results).to eq('evernote_clipper' => 'Yes') - end - - it 'does not set key when evernote_clipper is nil' do - instance = build_command_instance(described_class, {}) - results = run_post_execute(instance) - expect(results).to eq({}) - end - end -end diff --git a/spec/beef/modules/browser/detect_extensions_spec.rb b/spec/beef/modules/browser/detect_extensions_spec.rb deleted file mode 100644 index 18f3b341b..000000000 --- a/spec/beef/modules/browser/detect_extensions_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_extensions/module' - -RSpec.describe Detect_extensions do - describe '#post_execute' do - it 'saves extension from datastore' do - instance = build_command_instance(described_class, 'extension' => 'Chrome, AdBlock') - results = run_post_execute(instance) - expect(results).to eq('extension' => 'Chrome, AdBlock') - end - end -end diff --git a/spec/beef/modules/browser/detect_firebug_spec.rb b/spec/beef/modules/browser/detect_firebug_spec.rb deleted file mode 100644 index 32deafc82..000000000 --- a/spec/beef/modules/browser/detect_firebug_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_firebug/module' - -RSpec.describe Detect_firebug do - describe '#post_execute' do - it 'saves firebug from datastore when present' do - instance = build_command_instance(described_class, 'firebug' => 'Yes') - results = run_post_execute(instance) - expect(results).to eq('firebug' => 'Yes') - end - - it 'saves empty content when firebug is nil' do - instance = build_command_instance(described_class, 'firebug' => nil) - results = run_post_execute(instance) - expect(results).to eq({}) - end - end -end diff --git a/spec/beef/modules/browser/detect_foxit_spec.rb b/spec/beef/modules/browser/detect_foxit_spec.rb deleted file mode 100644 index 154b966d6..000000000 --- a/spec/beef/modules/browser/detect_foxit_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_foxit/module' - -RSpec.describe Detect_foxit do - describe '#post_execute' do - it 'saves foxit from datastore' do - instance = build_command_instance(described_class, 'foxit' => 'Yes') - results = run_post_execute(instance) - expect(results).to eq('foxit' => 'Yes') - end - - it 'calls BrowserDetails.set when results matches foxit=(Yes|No)' do - allow(BeEF::Core::Models::BrowserDetails).to receive(:set) - instance = build_command_instance(described_class, - 'foxit' => 'Yes', - 'beefhook' => 'hook123', - 'results' => 'foxit=Yes') - run_post_execute(instance) - expect(BeEF::Core::Models::BrowserDetails).to have_received(:set).with('hook123', 'HasFoxit', 'Yes') - end - - it 'does not call BrowserDetails.set when results does not match' do - allow(BeEF::Core::Models::BrowserDetails).to receive(:set) - instance = build_command_instance(described_class, - 'foxit' => 'Yes', - 'beefhook' => 'hook123', - 'results' => 'unknown') - run_post_execute(instance) - expect(BeEF::Core::Models::BrowserDetails).not_to have_received(:set) - end - end -end diff --git a/spec/beef/modules/browser/detect_lastpass_spec.rb b/spec/beef/modules/browser/detect_lastpass_spec.rb deleted file mode 100644 index 66ba9df9b..000000000 --- a/spec/beef/modules/browser/detect_lastpass_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_lastpass/module' - -RSpec.describe Detect_lastpass do - describe '#post_execute' do - it 'saves lastpass from datastore' do - instance = build_command_instance(described_class, 'lastpass' => 'Yes') - results = run_post_execute(instance) - expect(results).to eq('lastpass' => 'Yes') - end - - it 'does not set key when lastpass is nil' do - instance = build_command_instance(described_class, {}) - results = run_post_execute(instance) - expect(results).to eq({}) - end - end -end diff --git a/spec/beef/modules/browser/detect_mime_types_spec.rb b/spec/beef/modules/browser/detect_mime_types_spec.rb deleted file mode 100644 index 78480744a..000000000 --- a/spec/beef/modules/browser/detect_mime_types_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_mime_types/module' - -RSpec.describe Detect_mime_types do - describe '#post_execute' do - it 'saves mime_types from datastore' do - instance = build_command_instance(described_class, 'mime_types' => 'application/pdf') - results = run_post_execute(instance) - expect(results).to eq('mime_types' => 'application/pdf') - end - - it 'does not set key when mime_types is nil' do - instance = build_command_instance(described_class, {}) - results = run_post_execute(instance) - expect(results).to eq({}) - end - end -end diff --git a/spec/beef/modules/browser/detect_office_spec.rb b/spec/beef/modules/browser/detect_office_spec.rb deleted file mode 100644 index db3d8a1f8..000000000 --- a/spec/beef/modules/browser/detect_office_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_office/module' - -RSpec.describe Detect_office do - describe '#post_execute' do - it 'saves office from datastore' do - instance = build_command_instance(described_class, 'office' => 'Office 2016') - results = run_post_execute(instance) - expect(results).to eq('office' => 'Office 2016') - end - - it 'calls BrowserDetails.set when results matches office=Office N' do - allow(BeEF::Core::Models::BrowserDetails).to receive(:set) - instance = build_command_instance(described_class, - 'office' => 'Office 2016', - 'beefhook' => 'hook123', - 'results' => 'office=Office 2016') - run_post_execute(instance) - expect(BeEF::Core::Models::BrowserDetails).to have_received(:set).with('hook123', 'HasOffice', '2016') - end - - it 'does not call BrowserDetails.set when results does not match' do - allow(BeEF::Core::Models::BrowserDetails).to receive(:set) - instance = build_command_instance(described_class, - 'office' => 'None', - 'beefhook' => 'hook123', - 'results' => 'office=None') - run_post_execute(instance) - expect(BeEF::Core::Models::BrowserDetails).not_to have_received(:set) - end - end -end diff --git a/spec/beef/modules/browser/detect_popup_blocker_spec.rb b/spec/beef/modules/browser/detect_popup_blocker_spec.rb deleted file mode 100644 index cec5d0a44..000000000 --- a/spec/beef/modules/browser/detect_popup_blocker_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_popup_blocker/module' - -RSpec.describe Detect_popup_blocker do - describe '#post_execute' do - it 'saves popup_blocker_enabled from datastore' do - instance = build_command_instance(described_class, 'popup_blocker_enabled' => 'Yes') - results = run_post_execute(instance) - expect(results).to eq('popup_blocker_enabled' => 'Yes') - end - end -end diff --git a/spec/beef/modules/browser/detect_quicktime_spec.rb b/spec/beef/modules/browser/detect_quicktime_spec.rb deleted file mode 100644 index e5a339f5b..000000000 --- a/spec/beef/modules/browser/detect_quicktime_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_quicktime/module' - -RSpec.describe Detect_quicktime do - describe '#post_execute' do - it 'saves quicktime from datastore' do - instance = build_command_instance(described_class, 'quicktime' => 'Yes') - results = run_post_execute(instance) - expect(results).to eq('quicktime' => 'Yes') - end - end -end diff --git a/spec/beef/modules/browser/detect_realplayer_spec.rb b/spec/beef/modules/browser/detect_realplayer_spec.rb deleted file mode 100644 index f2a3a1cfc..000000000 --- a/spec/beef/modules/browser/detect_realplayer_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_realplayer/module' - -RSpec.describe Detect_realplayer do - describe '#post_execute' do - it 'saves realplayer from datastore' do - instance = build_command_instance(described_class, 'realplayer' => 'Yes') - results = run_post_execute(instance) - expect(results).to eq('realplayer' => 'Yes') - end - end -end diff --git a/spec/beef/modules/browser/detect_silverlight_spec.rb b/spec/beef/modules/browser/detect_silverlight_spec.rb deleted file mode 100644 index 507e117ae..000000000 --- a/spec/beef/modules/browser/detect_silverlight_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_silverlight/module' - -RSpec.describe Detect_silverlight do - describe '#post_execute' do - it 'saves silverlight from datastore' do - instance = build_command_instance(described_class, 'silverlight' => 'Yes') - results = run_post_execute(instance) - expect(results).to eq('silverlight' => 'Yes') - end - end -end diff --git a/spec/beef/modules/browser/detect_simple_adblock_spec.rb b/spec/beef/modules/browser/detect_simple_adblock_spec.rb deleted file mode 100644 index 4ea6be255..000000000 --- a/spec/beef/modules/browser/detect_simple_adblock_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_simple_adblock/module' - -RSpec.describe Detect_simple_adblock do - describe '#post_execute' do - it 'saves simple_adblock from datastore' do - instance = build_command_instance(described_class, 'simple_adblock' => 'Yes') - results = run_post_execute(instance) - expect(results).to eq('simple_adblock' => 'Yes') - end - - it 'does not set key when simple_adblock is nil' do - instance = build_command_instance(described_class, {}) - results = run_post_execute(instance) - expect(results).to eq({}) - end - end -end diff --git a/spec/beef/modules/browser/detect_toolbars_spec.rb b/spec/beef/modules/browser/detect_toolbars_spec.rb deleted file mode 100644 index 88e942d22..000000000 --- a/spec/beef/modules/browser/detect_toolbars_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_toolbars/module' - -RSpec.describe Detect_toolbars do - describe '#post_execute' do - it 'saves toolbars from datastore' do - instance = build_command_instance(described_class, 'toolbars' => 'Toolbar1, Toolbar2') - results = run_post_execute(instance) - expect(results).to eq('toolbars' => 'Toolbar1, Toolbar2') - end - end -end diff --git a/spec/beef/modules/browser/detect_unity_spec.rb b/spec/beef/modules/browser/detect_unity_spec.rb deleted file mode 100644 index c19995da8..000000000 --- a/spec/beef/modules/browser/detect_unity_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_unity/module' - -RSpec.describe Detect_unity do - describe '#post_execute' do - it 'saves unity from datastore' do - instance = build_command_instance(described_class, 'unity' => 'Yes') - results = run_post_execute(instance) - expect(results).to eq('unity' => 'Yes') - end - end -end diff --git a/spec/beef/modules/browser/detect_unsafe_activex_spec.rb b/spec/beef/modules/browser/detect_unsafe_activex_spec.rb deleted file mode 100644 index d605c7d85..000000000 --- a/spec/beef/modules/browser/detect_unsafe_activex_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_unsafe_activex/module' - -RSpec.describe Detect_unsafe_activex do - describe '#post_execute' do - it 'saves unsafe_activex from datastore' do - instance = build_command_instance(described_class, 'unsafe_activex' => 'Yes') - results = run_post_execute(instance) - expect(results).to eq('unsafe_activex' => 'Yes') - end - end -end diff --git a/spec/beef/modules/browser/detect_vlc_spec.rb b/spec/beef/modules/browser/detect_vlc_spec.rb deleted file mode 100644 index 82d6f5238..000000000 --- a/spec/beef/modules/browser/detect_vlc_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_vlc/module' - -RSpec.describe Detect_vlc do - describe '#post_execute' do - it 'saves vlc from datastore' do - instance = build_command_instance(described_class, 'vlc' => 'Yes') - results = run_post_execute(instance) - expect(results).to eq('vlc' => 'Yes') - end - - it 'calls BrowserDetails.set when results matches vlc=(Yes|No)' do - allow(BeEF::Core::Models::BrowserDetails).to receive(:set) - instance = build_command_instance(described_class, - 'vlc' => 'Yes', - 'beefhook' => 'hook123', - 'results' => 'vlc=Yes') - run_post_execute(instance) - expect(BeEF::Core::Models::BrowserDetails).to have_received(:set).with('hook123', 'browser.capabilities.vlc', 'Yes') - end - end -end diff --git a/spec/beef/modules/browser/detect_wmp_spec.rb b/spec/beef/modules/browser/detect_wmp_spec.rb deleted file mode 100644 index a56a1e0c9..000000000 --- a/spec/beef/modules/browser/detect_wmp_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/detect_wmp/module' - -RSpec.describe Detect_wmp do - describe '#post_execute' do - it 'saves wmp from datastore' do - instance = build_command_instance(described_class, 'wmp' => 'Yes') - results = run_post_execute(instance) - expect(results).to eq('wmp' => 'Yes') - end - - it 'calls BrowserDetails.set when results matches wmp=(Yes|No)' do - allow(BeEF::Core::Models::BrowserDetails).to receive(:set) - instance = build_command_instance(described_class, - 'wmp' => 'No', - 'beefhook' => 'hook1', - 'results' => 'wmp=No') - run_post_execute(instance) - expect(BeEF::Core::Models::BrowserDetails).to have_received(:set).with('hook1', 'browser.capabilities.wmp', 'No') - end - end -end diff --git a/spec/beef/modules/browser/fingerprint_browser_spec.rb b/spec/beef/modules/browser/fingerprint_browser_spec.rb deleted file mode 100644 index c67c8fa67..000000000 --- a/spec/beef/modules/browser/fingerprint_browser_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/fingerprint_browser/module' - -RSpec.describe Fingerprint_browser do - describe '#post_execute' do - it 'saves fingerprint and components from datastore' do - instance = build_command_instance(described_class, - 'fingerprint' => 'abc123', - 'components' => '[]') - results = run_post_execute(instance) - expect(results).to include('fingerprint' => 'abc123', 'components' => '[]') - end - - it 'sets fail message when content is empty' do - instance = build_command_instance(described_class, {}) - results = run_post_execute(instance) - expect(results).to eq('fail' => 'Failed to fingerprint browser.') - end - end -end diff --git a/spec/beef/modules/browser/get_visited_domains_spec.rb b/spec/beef/modules/browser/get_visited_domains_spec.rb deleted file mode 100644 index 4753aa39a..000000000 --- a/spec/beef/modules/browser/get_visited_domains_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/get_visited_domains/module' - -RSpec.describe Get_visited_domains do - describe '.options' do - it 'returns domains option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.size).to eq(1) - expect(opts.first['name']).to eq('domains') - expect(opts.first['ui_label']).to eq('Specify custom page to check') - end - end - - describe '#post_execute' do - it 'saves results from datastore' do - instance = build_command_instance(described_class, 'results' => 'domain1.com, domain2.com') - results = run_post_execute(instance) - expect(results).to eq('results' => 'domain1.com, domain2.com') - end - end -end diff --git a/spec/beef/modules/browser/get_visited_urls_spec.rb b/spec/beef/modules/browser/get_visited_urls_spec.rb deleted file mode 100644 index faedbbd0c..000000000 --- a/spec/beef/modules/browser/get_visited_urls_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/get_visited_urls/module' - -RSpec.describe Get_visited_urls do - describe '.options' do - it 'returns url textarea option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.first).to include('name' => 'urls', 'type' => 'textarea', 'ui_label' => 'URL(s)') - end - end - - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'https://example.com') - results = run_post_execute(instance) - expect(results).to eq('result' => 'https://example.com') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/ajax_fingerprint_spec.rb b/spec/beef/modules/browser/hooked_origin/ajax_fingerprint_spec.rb deleted file mode 100644 index 76cdda73a..000000000 --- a/spec/beef/modules/browser/hooked_origin/ajax_fingerprint_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/ajax_fingerprint/module' - -RSpec.describe Ajax_fingerprint do - describe '#post_execute' do - it 'saves script_urls from datastore' do - instance = build_command_instance(described_class, 'script_urls' => ['/a.js', '/b.js']) - results = run_post_execute(instance) - expect(results).to include('script_urls' => ['/a.js', '/b.js']) - end - - it 'sets fail when content is empty' do - instance = build_command_instance(described_class, {}) - results = run_post_execute(instance) - expect(results).to include('fail' => 'Failed to fingerprint ajax.') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/alert_dialog_spec.rb b/spec/beef/modules/browser/hooked_origin/alert_dialog_spec.rb deleted file mode 100644 index 404c4f75d..000000000 --- a/spec/beef/modules/browser/hooked_origin/alert_dialog_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/alert_dialog/module' - -RSpec.describe Alert_dialog do - describe '.options' do - it 'returns text textarea option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.first).to include('name' => 'text', 'ui_label' => 'Alert text') - end - end - - describe '#post_execute' do - it 'saves User Response message' do - instance = build_command_instance(described_class, {}) - results = run_post_execute(instance) - expect(results).to include('User Response' => "The user clicked the 'OK' button when presented with an alert box.") - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/apache_tomcat_examples_cookie_disclosure_spec.rb b/spec/beef/modules/browser/hooked_origin/apache_tomcat_examples_cookie_disclosure_spec.rb deleted file mode 100644 index 7e2dbb896..000000000 --- a/spec/beef/modules/browser/hooked_origin/apache_tomcat_examples_cookie_disclosure_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/apache_tomcat_examples_cookie_disclosure/module' - -RSpec.describe Apache_tomcat_examples_cookie_disclosure do - describe '.options' do - it 'returns request_header_servlet_path option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.first).to include('name' => 'request_header_servlet_path') - end - end - - describe '#post_execute' do - it 'saves cookies from datastore' do - instance = build_command_instance(described_class, 'cookies' => 'session=abc') - results = run_post_execute(instance) - expect(results).to eq('cookies' => 'session=abc') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/cisco_asa_password_disclosure_spec.rb b/spec/beef/modules/browser/hooked_origin/cisco_asa_password_disclosure_spec.rb deleted file mode 100644 index 02190381c..000000000 --- a/spec/beef/modules/browser/hooked_origin/cisco_asa_password_disclosure_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/cisco_asa_password_disclosure/module' - -RSpec.describe Cisco_asa_passwords do - describe '#post_execute' do - it 'saves cisco_asa_passwords from datastore' do - instance = build_command_instance(described_class, 'cisco_asa_passwords' => 'pwd=secret') - results = run_post_execute(instance) - expect(results).to eq('cisco_asa_passwords' => 'pwd=secret') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/clear_console_spec.rb b/spec/beef/modules/browser/hooked_origin/clear_console_spec.rb deleted file mode 100644 index b6f3f3419..000000000 --- a/spec/beef/modules/browser/hooked_origin/clear_console_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/clear_console/module' - -RSpec.describe Clear_console do - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'cleared') - results = run_post_execute(instance) - expect(results).to eq('result' => 'cleared') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/deface_web_page_component_spec.rb b/spec/beef/modules/browser/hooked_origin/deface_web_page_component_spec.rb deleted file mode 100644 index a66756db6..000000000 --- a/spec/beef/modules/browser/hooked_origin/deface_web_page_component_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/deface_web_page_component/module' - -RSpec.describe Deface_web_page_component do - describe '.options' do - it 'returns deface_selector and deface_content options' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.map { |o| o['name'] }).to include('deface_selector', 'deface_content') - end - end - - describe '#post_execute' do - it 'saves Result from datastore' do - instance = build_command_instance(described_class, 'result' => 'done') - results = run_post_execute(instance) - expect(results).to eq('Result' => 'done') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/deface_web_page_spec.rb b/spec/beef/modules/browser/hooked_origin/deface_web_page_spec.rb deleted file mode 100644 index 34b2d67d3..000000000 --- a/spec/beef/modules/browser/hooked_origin/deface_web_page_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/deface_web_page/module' - -RSpec.describe Deface_web_page do - describe '.options' do - it 'returns deface options with beef base host' do - config = instance_double(BeEF::Core::Configuration) - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - allow(config).to receive(:beef_proto).and_return('https') - allow(config).to receive(:beef_host).and_return('beef.example') - allow(config).to receive(:beef_port).and_return('3000') - - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.map { |o| o['name'] }).to include('deface_title', 'deface_favicon', 'deface_content') - expect(opts.find { |o| o['name'] == 'deface_favicon' }['value']).to include('https://beef.example:3000') - end - end - - describe '#post_execute' do - it 'saves Result from datastore' do - instance = build_command_instance(described_class, 'result' => 'deface done') - results = run_post_execute(instance) - expect(results).to eq('Result' => 'deface done') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/disable_developer_tools_spec.rb b/spec/beef/modules/browser/hooked_origin/disable_developer_tools_spec.rb deleted file mode 100644 index eaf920946..000000000 --- a/spec/beef/modules/browser/hooked_origin/disable_developer_tools_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/disable_developer_tools/module' - -RSpec.describe Disable_developer_tools do - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'disabled') - results = run_post_execute(instance) - expect(results).to eq('result' => 'disabled') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/get_autocomplete_creds_spec.rb b/spec/beef/modules/browser/hooked_origin/get_autocomplete_creds_spec.rb deleted file mode 100644 index ddf618f88..000000000 --- a/spec/beef/modules/browser/hooked_origin/get_autocomplete_creds_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/get_autocomplete_creds/module' - -RSpec.describe Get_autocomplete_creds do - describe '.options' do - it 'returns empty array' do - expect(described_class.options).to eq([]) - end - end - - describe '#post_execute' do - it 'saves results from datastore' do - instance = build_command_instance(described_class, 'results' => [{ 'user' => 'a', 'pass' => 'b' }]) - results = run_post_execute(instance) - expect(results).to eq('results' => [{ 'user' => 'a', 'pass' => 'b' }]) - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/get_cookie_spec.rb b/spec/beef/modules/browser/hooked_origin/get_cookie_spec.rb deleted file mode 100644 index f9d9dcf29..000000000 --- a/spec/beef/modules/browser/hooked_origin/get_cookie_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/get_cookie/module' - -RSpec.describe Get_cookie do - describe '#post_execute' do - it 'saves cookie from datastore' do - instance = build_command_instance(described_class, 'cookie' => 'session=abc123') - results = run_post_execute(instance) - expect(results).to eq('cookie' => 'session=abc123') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/get_form_values_spec.rb b/spec/beef/modules/browser/hooked_origin/get_form_values_spec.rb deleted file mode 100644 index bce8a144b..000000000 --- a/spec/beef/modules/browser/hooked_origin/get_form_values_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/get_form_values/module' - -RSpec.describe Get_form_values do - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'field=value') - results = run_post_execute(instance) - expect(results).to eq('result' => 'field=value') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/get_local_storage_spec.rb b/spec/beef/modules/browser/hooked_origin/get_local_storage_spec.rb deleted file mode 100644 index 149409ae5..000000000 --- a/spec/beef/modules/browser/hooked_origin/get_local_storage_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/get_local_storage/module' - -RSpec.describe Get_local_storage do - describe '#post_execute' do - it 'saves localStorage from datastore' do - instance = build_command_instance(described_class, 'localStorage' => 'key=value') - results = run_post_execute(instance) - expect(results).to eq('localStorage' => 'key=value') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/get_page_html_iframe_spec.rb b/spec/beef/modules/browser/hooked_origin/get_page_html_iframe_spec.rb deleted file mode 100644 index dc3b369bf..000000000 --- a/spec/beef/modules/browser/hooked_origin/get_page_html_iframe_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/get_page_html_iframe/module' - -RSpec.describe Get_page_html_iframe do - describe '#post_execute' do - it 'saves head, body, and iframe_ from datastore' do - instance = build_command_instance(described_class, - 'head' => 'Test', - 'body' => '

Body

', - 'iframe_' => '') - results = run_post_execute(instance) - expect(results).to eq('head' => 'Test', 'body' => '

Body

', 'iframe_' => '') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/get_page_html_spec.rb b/spec/beef/modules/browser/hooked_origin/get_page_html_spec.rb deleted file mode 100644 index 91daee32a..000000000 --- a/spec/beef/modules/browser/hooked_origin/get_page_html_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/get_page_html/module' - -RSpec.describe Get_page_html do - describe '#post_execute' do - it 'saves head and body from datastore' do - instance = build_command_instance(described_class, 'head' => 'Test', 'body' => '

Hello

') - results = run_post_execute(instance) - expect(results).to eq('head' => 'Test', 'body' => '

Hello

') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/get_page_links_spec.rb b/spec/beef/modules/browser/hooked_origin/get_page_links_spec.rb deleted file mode 100644 index d9b98a303..000000000 --- a/spec/beef/modules/browser/hooked_origin/get_page_links_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/get_page_links/module' - -RSpec.describe Get_page_links do - describe '#post_execute' do - it 'saves links from datastore' do - instance = build_command_instance(described_class, 'links' => 'Home') - results = run_post_execute(instance) - expect(results).to eq('links' => 'Home') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/get_session_storage_spec.rb b/spec/beef/modules/browser/hooked_origin/get_session_storage_spec.rb deleted file mode 100644 index 9e08a9fc4..000000000 --- a/spec/beef/modules/browser/hooked_origin/get_session_storage_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/get_session_storage/module' - -RSpec.describe Get_session_storage do - describe '#post_execute' do - it 'saves sessionStorage from datastore' do - instance = build_command_instance(described_class, 'sessionStorage' => 'key=value') - results = run_post_execute(instance) - expect(results).to eq('sessionStorage' => 'key=value') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/get_stored_credentials_spec.rb b/spec/beef/modules/browser/hooked_origin/get_stored_credentials_spec.rb deleted file mode 100644 index 1deed0af1..000000000 --- a/spec/beef/modules/browser/hooked_origin/get_stored_credentials_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/get_stored_credentials/module' - -RSpec.describe Get_stored_credentials do - describe '#post_execute' do - it 'saves form_data from datastore' do - instance = build_command_instance(described_class, 'form_data' => 'user=admin&pass=secret') - results = run_post_execute(instance) - expect(results).to eq('form_data' => 'user=admin&pass=secret') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/link_rewrite_click_events_spec.rb b/spec/beef/modules/browser/hooked_origin/link_rewrite_click_events_spec.rb deleted file mode 100644 index 2c76f1e10..000000000 --- a/spec/beef/modules/browser/hooked_origin/link_rewrite_click_events_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/link_rewrite_click_events/module' - -RSpec.describe Link_rewrite_click_events do - describe '.options' do - it 'returns url option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.first).to include('name' => 'url') - end - end - - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'ok') - results = run_post_execute(instance) - expect(results).to eq('result' => 'ok') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/link_rewrite_spec.rb b/spec/beef/modules/browser/hooked_origin/link_rewrite_spec.rb deleted file mode 100644 index 30d36353f..000000000 --- a/spec/beef/modules/browser/hooked_origin/link_rewrite_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/link_rewrite/module' - -RSpec.describe Link_rewrite do - describe '.options' do - it 'returns url option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.first).to include('name' => 'url', 'ui_label' => 'URL') - end - end - - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'ok') - results = run_post_execute(instance) - expect(results).to eq('result' => 'ok') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/link_rewrite_sslstrip_spec.rb b/spec/beef/modules/browser/hooked_origin/link_rewrite_sslstrip_spec.rb deleted file mode 100644 index e437a3425..000000000 --- a/spec/beef/modules/browser/hooked_origin/link_rewrite_sslstrip_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/link_rewrite_sslstrip/module' - -RSpec.describe Link_rewrite_sslstrip do - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'ok') - results = run_post_execute(instance) - expect(results).to eq('result' => 'ok') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/link_rewrite_tel_spec.rb b/spec/beef/modules/browser/hooked_origin/link_rewrite_tel_spec.rb deleted file mode 100644 index 907b360dc..000000000 --- a/spec/beef/modules/browser/hooked_origin/link_rewrite_tel_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/link_rewrite_tel/module' - -RSpec.describe Link_rewrite_tel do - describe '.options' do - it 'returns tel_number option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.first).to include('name' => 'tel_number', 'ui_label' => 'Number') - end - end - - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'ok') - results = run_post_execute(instance) - expect(results).to eq('result' => 'ok') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/mobilesafari_address_spoofing_spec.rb b/spec/beef/modules/browser/hooked_origin/mobilesafari_address_spoofing_spec.rb deleted file mode 100644 index 8edfcaf0c..000000000 --- a/spec/beef/modules/browser/hooked_origin/mobilesafari_address_spoofing_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/mobilesafari_address_spoofing/module' - -RSpec.describe Mobilesafari_address_spoofing do - describe '.options' do - it 'returns fake_url, real_url, domselectah options' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.map { |o| o['name'] }).to include('fake_url', 'real_url', 'domselectah') - end - end - - describe '#post_execute' do - it 'saves results and query from datastore' do - instance = build_command_instance(described_class, 'results' => 'x', 'query' => 'y') - results = run_post_execute(instance) - expect(results).to include('results' => 'x', 'query' => 'y') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/overflow_cookiejar_spec.rb b/spec/beef/modules/browser/hooked_origin/overflow_cookiejar_spec.rb deleted file mode 100644 index b828b4d9c..000000000 --- a/spec/beef/modules/browser/hooked_origin/overflow_cookiejar_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/overflow_cookiejar/module' - -RSpec.describe Overflow_cookiejar do - describe '.options' do - it 'returns preserveCookies option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.first).to include('name' => 'preserveCookies') - end - end - - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'ok') - results = run_post_execute(instance) - expect(results).to eq('result' => 'ok') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/prompt_dialog_spec.rb b/spec/beef/modules/browser/hooked_origin/prompt_dialog_spec.rb deleted file mode 100644 index 19e19e597..000000000 --- a/spec/beef/modules/browser/hooked_origin/prompt_dialog_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/prompt_dialog/module' - -RSpec.describe Prompt_dialog do - describe '.options' do - it 'returns question option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.first).to include('name' => 'question', 'ui_label' => 'Prompt text') - end - end - - describe '#post_execute' do - it 'saves answer from datastore' do - instance = build_command_instance(described_class, 'answer' => 'user input') - results = run_post_execute(instance) - expect(results).to eq('answer' => 'user input') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/remove_stuck_iframes_spec.rb b/spec/beef/modules/browser/hooked_origin/remove_stuck_iframes_spec.rb deleted file mode 100644 index bc90a3a28..000000000 --- a/spec/beef/modules/browser/hooked_origin/remove_stuck_iframes_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/remove_stuck_iframes/module' - -RSpec.describe Remove_stuck_iframes do - describe '#post_execute' do - it 'saves head, body, and iframe_ from datastore' do - instance = build_command_instance(described_class, - 'head' => 'Test', - 'body' => '

Body

', - 'iframe_' => '') - results = run_post_execute(instance) - expect(results).to eq('head' => 'Test', 'body' => '

Body

', 'iframe_' => '') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/replace_video_spec.rb b/spec/beef/modules/browser/hooked_origin/replace_video_spec.rb deleted file mode 100644 index 559622c06..000000000 --- a/spec/beef/modules/browser/hooked_origin/replace_video_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/replace_video/module' - -RSpec.describe Replace_video do - describe '.options' do - it 'returns youtube_id and jquery_selector options' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.map { |o| o['name'] }).to contain_exactly('youtube_id', 'jquery_selector') - end - end - - describe '#post_execute' do - it 'saves Result from datastore' do - instance = build_command_instance(described_class, 'result' => 'replaced') - results = run_post_execute(instance) - expect(results).to eq('Result' => 'replaced') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/rickroll_spec.rb b/spec/beef/modules/browser/hooked_origin/rickroll_spec.rb deleted file mode 100644 index 2dbaeffd7..000000000 --- a/spec/beef/modules/browser/hooked_origin/rickroll_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/rickroll/module' - -RSpec.describe Rickroll do - describe '#post_execute' do - it 'saves Result from datastore' do - instance = build_command_instance(described_class, 'result' => 'played') - results = run_post_execute(instance) - expect(results).to eq('Result' => 'played') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/site_redirect_iframe_spec.rb b/spec/beef/modules/browser/hooked_origin/site_redirect_iframe_spec.rb deleted file mode 100644 index 0283697ed..000000000 --- a/spec/beef/modules/browser/hooked_origin/site_redirect_iframe_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/site_redirect_iframe/module' - -RSpec.describe Site_redirect_iframe do - describe '.options' do - it 'returns iframe options with beef base host' do - config = instance_double(BeEF::Core::Configuration) - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - allow(config).to receive(:beef_proto).and_return('http') - allow(config).to receive(:beef_host).and_return('localhost') - allow(config).to receive(:beef_port).and_return('3000') - - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.map { |o| o['name'] }).to include('iframe_title', 'iframe_favicon', 'iframe_src', 'iframe_timeout') - expect(opts.find { |o| o['name'] == 'iframe_favicon' }['value']).to include('http://localhost:3000') - end - end - - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'done') - results = run_post_execute(instance) - expect(results).to eq('result' => 'done') - end - end -end diff --git a/spec/beef/modules/browser/hooked_origin/site_redirect_spec.rb b/spec/beef/modules/browser/hooked_origin/site_redirect_spec.rb deleted file mode 100644 index 937064fdc..000000000 --- a/spec/beef/modules/browser/hooked_origin/site_redirect_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../../spec_helper' -require_relative '../../../../../modules/browser/hooked_origin/site_redirect/module' - -RSpec.describe Site_redirect do - describe '.options' do - it 'returns redirect_url option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.first).to include('name' => 'redirect_url', 'ui_label' => 'Redirect URL') - end - end - - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'redirected') - results = run_post_execute(instance) - expect(results).to eq('result' => 'redirected') - end - end -end diff --git a/spec/beef/modules/browser/play_sound_spec.rb b/spec/beef/modules/browser/play_sound_spec.rb deleted file mode 100644 index de89c8421..000000000 --- a/spec/beef/modules/browser/play_sound_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/play_sound/module' - -RSpec.describe Play_sound do - describe '.options' do - it 'returns sound_file_uri option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.first).to include('name' => 'sound_file_uri', 'ui_label' => 'Sound File Path') - end - end - - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'played') - results = run_post_execute(instance) - expect(results).to eq('result' => 'played') - end - end -end diff --git a/spec/beef/modules/browser/remove_hook_element_spec.rb b/spec/beef/modules/browser/remove_hook_element_spec.rb deleted file mode 100644 index 3b04fcdcb..000000000 --- a/spec/beef/modules/browser/remove_hook_element_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/remove_hook_element/module' - -RSpec.describe Remove_hook_element do - describe '#post_execute' do - it 'saves result from datastore when present' do - instance = build_command_instance(described_class, 'result' => 'removed') - results = run_post_execute(instance) - expect(results).to eq('result' => 'removed') - end - - it 'saves empty content when result is nil' do - instance = build_command_instance(described_class, 'result' => nil) - results = run_post_execute(instance) - expect(results).to eq({}) - end - end -end diff --git a/spec/beef/modules/browser/spyder_eye_spec.rb b/spec/beef/modules/browser/spyder_eye_spec.rb deleted file mode 100644 index 88ba9f270..000000000 --- a/spec/beef/modules/browser/spyder_eye_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/spyder_eye/module' - -RSpec.describe Spyder_eye do - describe '.options' do - it 'returns repeat and delay options' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.map { |o| o['name'] }).to contain_exactly('repeat', 'delay') - end - end - - describe '#post_execute' do - it 'saves results from datastore and unbinds asset' do - handler = instance_double('AssetHandler') - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - allow(handler).to receive(:unbind) - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).and_return('127.0.0.1') - allow(File).to receive(:open).and_yield(StringIO.new) - - instance = build_command_instance(described_class, - 'results' => 'image=data:image/png;base64,iVBORw0KGgo=', - 'beefhook' => 'hook1', - 'cid' => '1') - allow(instance).to receive(:print_info) - allow(instance).to receive(:print_error) - allow(BeEF::Core::Logger.instance).to receive(:register) - - results = run_post_execute(instance) - - expect(results).to eq('results' => 'image=data:image/png;base64,iVBORw0KGgo=') - expect(handler).to have_received(:unbind).with('/h2c.js') - end - end -end diff --git a/spec/beef/modules/browser/unhook_spec.rb b/spec/beef/modules/browser/unhook_spec.rb deleted file mode 100644 index bb0fd24a4..000000000 --- a/spec/beef/modules/browser/unhook_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/unhook/module' - -RSpec.describe Unhook do - describe '#post_execute' do - it 'saves result from datastore when present' do - instance = build_command_instance(described_class, 'result' => 'unhooked') - results = run_post_execute(instance) - expect(results).to eq('result' => 'unhooked') - end - - it 'saves empty content when result is nil' do - instance = build_command_instance(described_class, 'result' => nil) - results = run_post_execute(instance) - expect(results).to eq({}) - end - end -end diff --git a/spec/beef/modules/browser/webcam_flash_spec.rb b/spec/beef/modules/browser/webcam_flash_spec.rb deleted file mode 100644 index dd52da645..000000000 --- a/spec/beef/modules/browser/webcam_flash_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/webcam_flash/module' - -RSpec.describe Webcam_flash do - describe '.options' do - it 'returns social_engineering and picture options' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.map { |o| o['name'] }).to include('social_engineering_title', 'no_of_pictures', 'interval') - end - end - - describe '#post_execute' do - it 'saves result and picture from datastore and unbinds assets' do - handler = instance_double('AssetHandler') - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - allow(handler).to receive(:unbind) - - instance = build_command_instance(described_class, 'result' => 'ok', 'picture' => 'base64data') - results = run_post_execute(instance) - - expect(results).to eq('result' => 'ok', 'picture' => 'base64data') - expect(handler).to have_received(:unbind).with('/takeit.swf') - expect(handler).to have_received(:unbind).with('/swfobject.js') - end - end -end diff --git a/spec/beef/modules/browser/webcam_html5_spec.rb b/spec/beef/modules/browser/webcam_html5_spec.rb deleted file mode 100644 index d043477dd..000000000 --- a/spec/beef/modules/browser/webcam_html5_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/webcam_html5/module' - -RSpec.describe Webcam_html5 do - describe '.options' do - it 'returns choice combobox option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.first).to include('name' => 'choice', 'type' => 'combobox') - end - end - - describe '#post_execute' do - it 'saves result and image from datastore' do - instance = build_command_instance(described_class, 'result' => 'ok', 'image' => 'data:image/png;base64,abc') - results = run_post_execute(instance) - expect(results).to eq('result' => 'ok', 'image' => 'data:image/png;base64,abc') - end - - it 'omits nil keys' do - instance = build_command_instance(described_class, {}) - results = run_post_execute(instance) - expect(results).to eq({}) - end - end -end diff --git a/spec/beef/modules/browser/webcam_permission_check_spec.rb b/spec/beef/modules/browser/webcam_permission_check_spec.rb deleted file mode 100644 index e04a6ff07..000000000 --- a/spec/beef/modules/browser/webcam_permission_check_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/browser/webcam_permission_check/module' - -RSpec.describe Webcam_permission_check do - describe '#post_execute' do - it 'unbinds cameraCheck and swfobject assets' do - handler = instance_double('AssetHandler') - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - allow(handler).to receive(:unbind) - - instance = build_command_instance(described_class, {}) - instance.post_execute - - expect(handler).to have_received(:unbind).with('/cameraCheck.swf') - expect(handler).to have_received(:unbind).with('/swfobject.js') - end - end -end diff --git a/spec/beef/modules/chrome_extensions/execute_tabs_spec.rb b/spec/beef/modules/chrome_extensions/execute_tabs_spec.rb deleted file mode 100644 index f99cfe716..000000000 --- a/spec/beef/modules/chrome_extensions/execute_tabs_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/chrome_extensions/execute_tabs/module' - -RSpec.describe Execute_tabs do - describe '.options' do - it 'returns url and theJS options' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.map { |o| o['name'] }).to include('url', 'theJS') - end - end - - describe '#post_execute' do - it 'saves Return from datastore' do - instance = build_command_instance(described_class, 'return' => 'executed') - results = run_post_execute(instance) - expect(results).to eq('Return' => 'executed') - end - end -end diff --git a/spec/beef/modules/chrome_extensions/get_all_cookies_spec.rb b/spec/beef/modules/chrome_extensions/get_all_cookies_spec.rb deleted file mode 100644 index bb83a50ec..000000000 --- a/spec/beef/modules/chrome_extensions/get_all_cookies_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/chrome_extensions/get_all_cookies/module' - -RSpec.describe Get_all_cookies do - describe '.options' do - it 'returns url option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.first).to include('name' => 'url', 'ui_label' => 'Domain (e.g. http://facebook.com)') - end - end - - describe '#post_execute' do - it 'saves Return from datastore' do - instance = build_command_instance(described_class, 'return' => 'cookies data') - results = run_post_execute(instance) - expect(results).to eq('Return' => 'cookies data') - end - end -end diff --git a/spec/beef/modules/chrome_extensions/grab_google_contacts_spec.rb b/spec/beef/modules/chrome_extensions/grab_google_contacts_spec.rb deleted file mode 100644 index 3837236ea..000000000 --- a/spec/beef/modules/chrome_extensions/grab_google_contacts_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/chrome_extensions/grab_google_contacts/module' - -RSpec.describe Grab_google_contacts do - describe '#post_execute' do - it 'saves Return from datastore' do - instance = build_command_instance(described_class, 'return' => 'contacts data') - results = run_post_execute(instance) - expect(results).to eq('Return' => 'contacts data') - end - end -end diff --git a/spec/beef/modules/chrome_extensions/inject_beef_spec.rb b/spec/beef/modules/chrome_extensions/inject_beef_spec.rb deleted file mode 100644 index 7e54d40c0..000000000 --- a/spec/beef/modules/chrome_extensions/inject_beef_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/chrome_extensions/inject_beef/module' - -RSpec.describe Inject_beef do - describe '#post_execute' do - it 'saves Return from datastore' do - instance = build_command_instance(described_class, 'return' => 'injected') - results = run_post_execute(instance) - expect(results).to eq('Return' => 'injected') - end - end -end diff --git a/spec/beef/modules/chrome_extensions/screenshot_spec.rb b/spec/beef/modules/chrome_extensions/screenshot_spec.rb deleted file mode 100644 index a684dabab..000000000 --- a/spec/beef/modules/chrome_extensions/screenshot_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/chrome_extensions/screenshot/module' - -RSpec.describe Screenshot do - describe '#post_execute' do - it 'saves Return from datastore' do - instance = build_command_instance(described_class, 'return' => 'screenshot data') - results = run_post_execute(instance) - expect(results).to eq('Return' => 'screenshot data') - end - end -end diff --git a/spec/beef/modules/chrome_extensions/send_gvoice_sms_spec.rb b/spec/beef/modules/chrome_extensions/send_gvoice_sms_spec.rb deleted file mode 100644 index 6e7a6b92c..000000000 --- a/spec/beef/modules/chrome_extensions/send_gvoice_sms_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/chrome_extensions/send_gvoice_sms/module' - -RSpec.describe Send_gvoice_sms do - describe '.options' do - it 'returns to and message options' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.map { |o| o['name'] }).to include('to', 'message') - end - end - - describe '#post_execute' do - it 'saves To, Message, Status from datastore' do - instance = build_command_instance(described_class, - 'to' => '1234567890', - 'message' => 'Hello', - 'status' => 'sent') - results = run_post_execute(instance) - expect(results).to include('To' => '1234567890', 'Message' => 'Hello', 'Status' => 'sent') - end - end -end diff --git a/spec/beef/modules/debug/test_beef_debug_spec.rb b/spec/beef/modules/debug/test_beef_debug_spec.rb deleted file mode 100644 index f143d4e1e..000000000 --- a/spec/beef/modules/debug/test_beef_debug_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/debug/test_beef_debug/module' - -RSpec.describe Test_beef_debug do - describe '.options' do - it 'returns an array of option hashes' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.size).to eq(1) - expect(opts.first).to include('name' => 'msg', 'ui_label' => 'Debug Message') - expect(opts.first['value']).to include('beef.debug()') - end - end - - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'debug output') - results = run_post_execute(instance) - expect(results).to eq('Result' => 'debug output') - end - end -end diff --git a/spec/beef/modules/debug/test_cors_request_spec.rb b/spec/beef/modules/debug/test_cors_request_spec.rb deleted file mode 100644 index c24fdd6e5..000000000 --- a/spec/beef/modules/debug/test_cors_request_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/debug/test_cors_request/module' - -RSpec.describe Test_cors_request do - describe '.options' do - it 'returns method, url, and data options' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.size).to eq(3) - expect(opts.map { |o| o['name'] }).to contain_exactly('method', 'url', 'data') - expect(opts.find { |o| o['name'] == 'method' }['value']).to eq('GET') - expect(opts.find { |o| o['name'] == 'data' }['value']).to eq('postdata') - end - end - - describe '#post_execute' do - it 'saves response from datastore' do - instance = build_command_instance(described_class, 'response' => 'cors body') - results = run_post_execute(instance) - expect(results).to eq('response' => 'cors body') - end - end -end diff --git a/spec/beef/modules/debug/test_dns_tunnel_client_spec.rb b/spec/beef/modules/debug/test_dns_tunnel_client_spec.rb deleted file mode 100644 index 8089bf5d1..000000000 --- a/spec/beef/modules/debug/test_dns_tunnel_client_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/debug/test_dns_tunnel_client/module' - -RSpec.describe Test_dns_tunnel_client do - before do - config = instance_double(BeEF::Core::Configuration) - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - end - - describe '.options' do - it 'returns domain and data options' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.size).to eq(2) - expect(opts.map { |o| o['name'] }).to contain_exactly('domain', 'data') - expect(opts.find { |o| o['name'] == 'domain' }['value']).to eq('browserhacker.com') - expect(opts.find { |o| o['name'] == 'data' }['value']).to include('Lorem ipsum') - end - end - - describe '#post_execute' do - it 'saves dns_requests from datastore' do - instance = build_command_instance(described_class, 'dns_requests' => 'request log') - results = run_post_execute(instance) - expect(results).to eq('dns_requests' => 'request log') - end - end -end diff --git a/spec/beef/modules/debug/test_get_variable_spec.rb b/spec/beef/modules/debug/test_get_variable_spec.rb deleted file mode 100644 index 21c12d8f1..000000000 --- a/spec/beef/modules/debug/test_get_variable_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/debug/test_get_variable/module' - -RSpec.describe Test_get_variable do - describe '.options' do - it 'returns payload_name option' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.size).to eq(1) - expect(opts.first).to include('name' => 'payload_name', 'ui_label' => 'Payload Name', 'value' => 'message') - end - end -end diff --git a/spec/beef/modules/debug/test_http_redirect_spec.rb b/spec/beef/modules/debug/test_http_redirect_spec.rb deleted file mode 100644 index 3c0371dd6..000000000 --- a/spec/beef/modules/debug/test_http_redirect_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/debug/test_http_redirect/module' - -RSpec.describe Test_http_redirect do - describe '#post_execute' do - it 'saves result from datastore' do - instance = build_command_instance(described_class, 'result' => 'redirect done') - results = run_post_execute(instance) - expect(results).to eq('Result' => 'redirect done') - end - end - - describe '#pre_send' do - it 'binds redirect via AssetHandler' do - instance = described_class.allocate - handler = instance_double(BeEF::Core::NetworkStack::Handlers::AssetHandler) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - expect(handler).to receive(:bind_redirect).with('https://beefproject.com', '/redirect') - instance.pre_send - end - end -end diff --git a/spec/beef/modules/debug/test_network_request_spec.rb b/spec/beef/modules/debug/test_network_request_spec.rb deleted file mode 100644 index f2c192903..000000000 --- a/spec/beef/modules/debug/test_network_request_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/debug/test_network_request/module' - -RSpec.describe Test_network_request do - before do - config = instance_double(BeEF::Core::Configuration) - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:beef_port).and_return('3000') - allow(config).to receive(:get).with('beef.http.hook_file').and_return('/hook.js') - end - - describe '.options' do - it 'returns scheme, method, domain, port, path, anchor, data, timeout, dataType' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.size).to eq(9) - names = opts.map { |o| o['name'] } - expect(names).to include('scheme', 'method', 'domain', 'port', 'path', 'anchor', 'data', 'timeout', 'dataType') - expect(opts.find { |o| o['name'] == 'domain' }['value']).to eq('127.0.0.1') - expect(opts.find { |o| o['name'] == 'port' }['value']).to eq('3000') - expect(opts.find { |o| o['name'] == 'path' }['value']).to eq('/hook.js') - end - end - - describe '#post_execute' do - it 'saves response from datastore' do - instance = build_command_instance(described_class, 'response' => 'ok') - results = run_post_execute(instance) - expect(results).to eq('response' => 'ok') - end - end -end diff --git a/spec/beef/modules/debug/test_return_ascii_chars_spec.rb b/spec/beef/modules/debug/test_return_ascii_chars_spec.rb deleted file mode 100644 index 19253b221..000000000 --- a/spec/beef/modules/debug/test_return_ascii_chars_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/debug/test_return_ascii_chars/module' - -RSpec.describe Test_return_ascii_chars do - describe '#post_execute' do - it 'saves result_string from datastore' do - instance = build_command_instance(described_class, 'result_string' => 'ascii data') - results = run_post_execute(instance) - expect(results).to eq('Result String' => 'ascii data') - end - end -end diff --git a/spec/beef/modules/debug/test_return_image_spec.rb b/spec/beef/modules/debug/test_return_image_spec.rb deleted file mode 100644 index 167354253..000000000 --- a/spec/beef/modules/debug/test_return_image_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/debug/test_return_image/module' - -RSpec.describe Test_return_image do - describe '#post_execute' do - it 'saves image from datastore' do - instance = build_command_instance(described_class, 'image' => 'base64data') - results = run_post_execute(instance) - expect(results).to eq('image' => 'base64data') - end - end -end diff --git a/spec/beef/modules/debug/test_return_long_string_spec.rb b/spec/beef/modules/debug/test_return_long_string_spec.rb deleted file mode 100644 index af1504b6b..000000000 --- a/spec/beef/modules/debug/test_return_long_string_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require_relative '../../../spec_helper' -require_relative '../../../../modules/debug/test_return_long_string/module' - -RSpec.describe Test_return_long_string do - describe '.options' do - it 'returns repeat and repeat_string options' do - opts = described_class.options - expect(opts).to be_an(Array) - expect(opts.size).to eq(2) - names = opts.map { |o| o['name'] } - expect(names).to contain_exactly('repeat', 'repeat_string') - expect(opts.find { |o| o['name'] == 'repeat' }['value']).to eq('1024') - expect(opts.find { |o| o['name'] == 'repeat_string' }['value']).to eq('\u00AE') - end - end - - describe '#post_execute' do - it 'saves result_string from datastore' do - instance = build_command_instance(described_class, 'result_string' => 'long string') - results = run_post_execute(instance) - expect(results).to eq('Result String' => 'long string') - end - end -end From 57c615daf8d1dc57192073fe923b77b178f575ff Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Thu, 5 Feb 2026 14:12:41 +1000 Subject: [PATCH 24/37] ADD: module dynamic specs per section --- spec/beef/modules/browser/browser_spec.rb | 101 +++++++++ .../chrome_extensions_spec.rb | 72 +++++++ spec/beef/modules/debug/debug_spec.rb | 76 +++++++ spec/beef/modules/exploits/exploits_spec.rb | 112 ++++++++++ spec/beef/modules/host/host_spec.rb | 135 ++++++++++++ spec/beef/modules/ipec/ipec_spec.rb | 98 +++++++++ spec/beef/modules/misc/misc_spec.rb | 100 +++++++++ spec/beef/modules/network/network_spec.rb | 199 ++++++++++++++++++ .../modules/persistence/persistence_spec.rb | 72 +++++++ spec/beef/modules/phonegap/phonegap_spec.rb | 69 ++++++ .../social_engineering_spec.rb | 108 ++++++++++ spec/support/module_spec_helper.rb | 58 +++++ 12 files changed, 1200 insertions(+) create mode 100644 spec/beef/modules/browser/browser_spec.rb create mode 100644 spec/beef/modules/chrome_extensions/chrome_extensions_spec.rb create mode 100644 spec/beef/modules/debug/debug_spec.rb create mode 100644 spec/beef/modules/exploits/exploits_spec.rb create mode 100644 spec/beef/modules/host/host_spec.rb create mode 100644 spec/beef/modules/ipec/ipec_spec.rb create mode 100644 spec/beef/modules/misc/misc_spec.rb create mode 100644 spec/beef/modules/network/network_spec.rb create mode 100644 spec/beef/modules/persistence/persistence_spec.rb create mode 100644 spec/beef/modules/phonegap/phonegap_spec.rb create mode 100644 spec/beef/modules/social_engineering/social_engineering_spec.rb create mode 100644 spec/support/module_spec_helper.rb diff --git a/spec/beef/modules/browser/browser_spec.rb b/spec/beef/modules/browser/browser_spec.rb new file mode 100644 index 000000000..637899572 --- /dev/null +++ b/spec/beef/modules/browser/browser_spec.rb @@ -0,0 +1,101 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Unit tests for every module under modules/browser/ (including hooked_origin). +# Branch coverage: extra post_execute for modules that set BrowserDetails when results match. +# + +require_relative '../../../spec_helper' + +project_root = File.expand_path('../../../../', __dir__) +browser_module_paths = Dir[File.join(project_root, 'modules/browser/**/module.rb')].sort + +BRANCH_COVERAGE = { + 'modules/browser/detect_wmp' => { datastore: { 'results' => 'wmp=Yes', 'wmp' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }, + 'modules/browser/detect_vlc' => { datastore: { 'results' => 'vlc=Yes', 'vlc' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }, + 'modules/browser/detect_office' => { datastore: { 'results' => 'office=Office 2016', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }, + 'modules/browser/detect_foxit' => { datastore: { 'results' => 'foxit=Yes', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }, + 'modules/browser/detect_activex' => { datastore: { 'results' => 'activex=Yes', 'activex' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } } +}.freeze + +browser_module_paths.each do |path| + rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') + branch_key = File.dirname(path).sub("#{project_root}/", '') + require_path = File.join('../../../../', rel) + class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ } + next unless class_line + + klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1] + require_relative require_path + mod = Object.const_get(klass_name) + + RSpec.describe mod do + describe '.options' do + it 'returns an Array when defined' do + next unless described_class.respond_to?(:options) + + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + + opts = described_class.options + expect(opts).to be_an(Array) + end + end + + describe '#pre_send' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:pre_send) + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_pre_send(instance) }.not_to raise_error + end + end + + describe '#post_execute' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:post_execute) + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(handler).to receive(:bind_raw) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end + + it 'runs branch path when in BRANCH_COVERAGE' do + branch = BRANCH_COVERAGE[branch_key] + next unless described_class.method_defined?(:post_execute) && branch + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(handler).to receive(:bind_raw) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + instance = build_command_instance(described_class, branch[:datastore]) + expect { run_post_execute(instance) }.not_to raise_error + end + end + end +end diff --git a/spec/beef/modules/chrome_extensions/chrome_extensions_spec.rb b/spec/beef/modules/chrome_extensions/chrome_extensions_spec.rb new file mode 100644 index 000000000..f6a74c149 --- /dev/null +++ b/spec/beef/modules/chrome_extensions/chrome_extensions_spec.rb @@ -0,0 +1,72 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Unit tests for every module under modules/chrome_extensions/. +# Each directory is tested for .options (when present) and #post_execute. +# + +require_relative '../../../spec_helper' + +project_root = File.expand_path('../../../../', __dir__) +chrome_module_paths = Dir[File.join(project_root, 'modules/chrome_extensions/**/module.rb')].sort + +chrome_module_paths.each do |path| + rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') + require_path = File.join('../../../../', rel) + class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ } + next unless class_line + + klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1] + require_relative require_path + mod = Object.const_get(klass_name) + + RSpec.describe mod do + describe '.options' do + it 'returns an Array when defined' do + next unless described_class.respond_to?(:options) + + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + + opts = described_class.options + expect(opts).to be_an(Array) + end + end + + describe '#pre_send' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:pre_send) + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, 'return' => '', 'result' => '', 'cid' => '0') + expect { run_pre_send(instance) }.not_to raise_error + end + end + + describe '#post_execute' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:post_execute) + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + + instance = build_command_instance(described_class, 'return' => '', 'result' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end + end + end +end diff --git a/spec/beef/modules/debug/debug_spec.rb b/spec/beef/modules/debug/debug_spec.rb new file mode 100644 index 000000000..722d8e7d9 --- /dev/null +++ b/spec/beef/modules/debug/debug_spec.rb @@ -0,0 +1,76 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Unit tests for every module under modules/debug/. +# Each directory is tested for .options (when present) and #post_execute. +# See test_beef_debugs_spec.rb for integration/E2E tests (BrowserStack). +# + +require_relative '../../../spec_helper' + +project_root = File.expand_path('../../../../', __dir__) +debug_module_paths = Dir[File.join(project_root, 'modules/debug/**/module.rb')].sort + +debug_module_paths.each do |path| + rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') + require_path = File.join('../../../../', rel) + class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ } + next unless class_line + + klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1] + require_relative require_path + mod = Object.const_get(klass_name) + + RSpec.describe mod do + describe '.options' do + it 'returns an Array when defined' do + next unless described_class.respond_to?(:options) + + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + + opts = described_class.options + expect(opts).to be_an(Array) + end + end + + describe '#pre_send' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:pre_send) + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:bind_redirect).with(anything, anything).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_pre_send(instance) }.not_to raise_error + end + end + + describe '#post_execute' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:post_execute) + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end + end + end +end diff --git a/spec/beef/modules/exploits/exploits_spec.rb b/spec/beef/modules/exploits/exploits_spec.rb new file mode 100644 index 000000000..b94953986 --- /dev/null +++ b/spec/beef/modules/exploits/exploits_spec.rb @@ -0,0 +1,112 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Unit tests for every module under modules/exploits/. +# Branch coverage: extra post_execute for modules with conditional branches. +# + +require_relative '../../../spec_helper' + +project_root = File.expand_path('../../../../', __dir__) +exploit_module_paths = Dir[File.join(project_root, 'modules/exploits/**/module.rb')].sort + +BRANCH_COVERAGE = { + 'modules/exploits/vtiger_crm_upload_exploit' => { datastore: { 'result' => 'ok', 'cid' => '0' } }, + 'modules/exploits/router/asus_rt_n12e_get_info' => { + datastore: { + 'result' => '', 'results' => 'ip=192.168.1.1&clients=192.168.1.2,00:11:22:33:44:55&wanip=1.2.3.4&netmask=255.255.255.0&gateway=192.168.1.1&dns=8.8.8.8 8.8.4.4', + 'beefhook' => '1', 'cid' => '0' + }, + config_get: { 'beef.extension.network.enable' => true } + } +}.freeze + +exploit_module_paths.each do |path| + rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') + branch_key = File.dirname(path).sub("#{project_root}/", '') + require_path = File.join('../../../../', rel) + class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ } + next unless class_line + + klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1] + require_relative require_path + mod = Object.const_get(klass_name) + + RSpec.describe mod do + describe '.options' do + it 'returns an Array when defined' do + next unless described_class.respond_to?(:options) + + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + + opts = described_class.options + expect(opts).to be_an(Array) + end + end + + describe '#pre_send' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:pre_send) + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, {}) + expect { run_pre_send(instance) }.not_to raise_error + end + end + + describe '#post_execute' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:post_execute) + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + + instance = build_command_instance(described_class, {}) + expect { run_post_execute(instance) }.not_to raise_error + end + + it 'runs branch path when in BRANCH_COVERAGE' do + raw = BRANCH_COVERAGE[branch_key] + next unless described_class.method_defined?(:post_execute) && raw + + branches = raw.is_a?(Array) ? raw : [raw] + branches.each do |branch| + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + allow(BeEF::Core::Models::NetworkService).to receive(:create) + allow(BeEF::Core::Models::NetworkHost).to receive(:create) + allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) + if branch[:config_get] + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:get).with(anything) do |key| + branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1' + end + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + end + instance = build_command_instance(described_class, branch[:datastore]) + expect { run_post_execute(instance) }.not_to raise_error + end + end + end + end +end diff --git a/spec/beef/modules/host/host_spec.rb b/spec/beef/modules/host/host_spec.rb new file mode 100644 index 000000000..f75dc71e7 --- /dev/null +++ b/spec/beef/modules/host/host_spec.rb @@ -0,0 +1,135 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Unit tests for every module under modules/host/. +# Each directory is tested for .options (when present), #pre_send, and #post_execute. +# Branch coverage: extra post_execute runs for modules listed in BRANCH_COVERAGE. +# + +require_relative '../../../spec_helper' + +project_root = File.expand_path('../../../../', __dir__) +host_module_paths = Dir[File.join(project_root, 'modules/host/**/module.rb')].sort + +# Per-module datastore + config stubs to hit conditional branches in post_execute (e.g. NetworkService/NetworkHost path). +BRANCH_COVERAGE = { + 'modules/host/detect_cups' => { + datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=631&cups=Installed', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/host/detect_airdroid' => { + datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=8888&airdroid=Installed', 'airdroid' => '', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/host/get_internal_ip_java' => { + datastore: { 'results' => '192.168.1.1', 'Result' => '', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/host/get_internal_ip_webrtc' => { + datastore: { 'results' => 'IP is 192.168.1.1', 'Result' => '', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + } +}.freeze + +host_module_paths.each do |path| + rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') + branch_key = File.dirname(path).sub("#{project_root}/", '') + require_path = File.join('../../../../', rel) + class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ } + next unless class_line + + klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1] + require_relative require_path + mod = Object.const_get(klass_name) + + RSpec.describe mod do + describe '.options' do + it 'returns an Array when defined' do + next unless described_class.respond_to?(:options) + + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + + opts = described_class.options + expect(opts).to be_an(Array) + end + end + + describe '#pre_send' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:pre_send) + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_pre_send(instance) }.not_to raise_error + end + end + + describe '#post_execute' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:post_execute) + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + + file_double = double('File', write: nil, close: nil) + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') + allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') + + # Minimal datastore so modules that call .sub/.to_s on keys don't get nil + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end + + it 'runs branch path when in BRANCH_COVERAGE' do + branch = BRANCH_COVERAGE[branch_key] + next unless described_class.method_defined?(:post_execute) && branch + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + file_double = double('File', write: nil, close: nil) + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') + allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') + allow(BeEF::Core::Models::NetworkService).to receive(:create) + allow(BeEF::Core::Models::NetworkHost).to receive(:create) + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).and_return('Linux') + allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) + + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000') + allow(config).to receive(:get).with(anything) do |key| + branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1' + end + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + + instance = build_command_instance(described_class, branch[:datastore]) + expect { run_post_execute(instance) }.not_to raise_error + end + end + end +end diff --git a/spec/beef/modules/ipec/ipec_spec.rb b/spec/beef/modules/ipec/ipec_spec.rb new file mode 100644 index 000000000..c9be33f20 --- /dev/null +++ b/spec/beef/modules/ipec/ipec_spec.rb @@ -0,0 +1,98 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Unit tests for every module under modules/ipec/. +# + +require_relative '../../../spec_helper' + +# Stub extensions that some ipec modules reference (not loaded in unit tests) +module BeEF + module Extension + ETag = ::Module.new unless const_defined?(:ETag) + ServerClientDnsTunnel = ::Module.new unless const_defined?(:ServerClientDnsTunnel) + end +end +BeEF::Extension::ETag.const_set(:ETagMessages, ::Class.new do + def self.instance + @instance ||= Struct.new(:messages).new({}) + end +end) unless BeEF::Extension::ETag.const_defined?(:ETagMessages) +BeEF::Extension::ServerClientDnsTunnel.const_set(:Server, ::Class.new do + def self.instance + @instance ||= Struct.new(:messages).new({}) + end +end) unless BeEF::Extension::ServerClientDnsTunnel.const_defined?(:Server) + +project_root = File.expand_path('../../../../', __dir__) +paths = Dir[File.join(project_root, 'modules/ipec/**/module.rb')].sort + +paths.each do |path| + rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') + require_path = File.join('../../../../', rel) + class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ } + next unless class_line + + klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1] + require_relative require_path + mod = Object.const_get(klass_name) + + RSpec.describe mod do + describe '.options' do + it 'returns an Array when defined' do + next unless described_class.respond_to?(:options) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + expect(described_class.options).to be_an(Array) + end + end + + describe '#pre_send' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:pre_send) + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything) do |key| + case key + when 'beef.extension.etag.enable' then true + when 'beef.extension.s2c_dns_tunnel.enable' then true + when 'beef.extension.s2c_dns_tunnel.zone' then 'example.com' + else '127.0.0.1' + end + end + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, [{'name' => 'data', 'value' => 'test'}]) + expect { run_pre_send(instance) }.not_to raise_error + end + end + + describe '#post_execute' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:post_execute) + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + file_double = double('File', write: nil, close: nil) + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') + allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end + end + end +end diff --git a/spec/beef/modules/misc/misc_spec.rb b/spec/beef/modules/misc/misc_spec.rb new file mode 100644 index 000000000..64afe56be --- /dev/null +++ b/spec/beef/modules/misc/misc_spec.rb @@ -0,0 +1,100 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Unit tests for every module under modules/misc/ (including subdirs). +# Branch coverage: extra post_execute runs for modules in BRANCH_COVERAGE. +# + +require_relative '../../../spec_helper' + +project_root = File.expand_path('../../../../', __dir__) +paths = Dir[File.join(project_root, 'modules/misc/**/module.rb')].sort + +BRANCH_COVERAGE = { + 'modules/misc/wordpress_post_auth_rce' => { datastore: { 'result' => 'ok', 'cid' => '0' } } +}.freeze + +paths.each do |path| + rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') + branch_key = File.dirname(path).sub("#{project_root}/", '') + require_path = File.join('../../../../', rel) + class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ } + next unless class_line + + klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1] + require_relative require_path + mod = Object.const_get(klass_name) + + RSpec.describe mod do + describe '.options' do + it 'returns an Array when defined' do + next unless described_class.respond_to?(:options) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + expect(described_class.options).to be_an(Array) + end + end + + describe '#pre_send' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:pre_send) + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + allow(IO).to receive(:popen).and_return(StringIO.new('')) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_pre_send(instance) }.not_to raise_error + end + end + + describe '#post_execute' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:post_execute) + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(handler).to receive(:bind_raw) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + file_double = double('File', write: nil, close: nil) + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') + allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end + + it 'runs branch path when in BRANCH_COVERAGE' do + branch = BRANCH_COVERAGE[branch_key] + next unless described_class.method_defined?(:post_execute) && branch + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(handler).to receive(:bind_raw) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + file_double = double('File', write: nil, close: nil) + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') + allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') + instance = build_command_instance(described_class, branch[:datastore]) + expect { run_post_execute(instance) }.not_to raise_error + end + end + end +end diff --git a/spec/beef/modules/network/network_spec.rb b/spec/beef/modules/network/network_spec.rb new file mode 100644 index 000000000..0d987198d --- /dev/null +++ b/spec/beef/modules/network/network_spec.rb @@ -0,0 +1,199 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Unit tests for every module under modules/network/ (including ADC). +# + +require_relative '../../../spec_helper' + +# Stub Dns extension so network/dns_rebinding/module.rb can load (references BeEF::Extension::Dns::Server). +unless BeEF::Extension.const_defined?(:Dns) + BeEF::Extension.const_set(:Dns, Module.new) +end +unless BeEF::Extension::Dns.const_defined?(:Server) + dns_server_instance = Object.new + def dns_server_instance.add_rule(*) 1 end + def dns_server_instance.remove_rule!(*) nil end + dns_server = Class.new do + define_singleton_method(:instance) { dns_server_instance } + end + BeEF::Extension::Dns.const_set(:Server, dns_server) +end + +# Per-module datastore + config to hit conditional branches in post_execute (network extension + regex paths). +BRANCH_COVERAGE = { + 'modules/network/port_scanner' => { + datastore: { 'results' => 'ip=1.2.3.4&port=HTTP: Port 80 is OPEN Apache', 'beefhook' => '1', 'result' => '', 'port' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/ping_sweep' => { + datastore: { 'results' => 'ip=192.168.1.1&ping=10ms', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/ping_sweep_ff' => { + datastore: { 'results' => 'host=192.168.1.1 is alive', 'beefhook' => '1', 'result' => '', 'host' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/get_http_servers' => { + datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&url=http://1.2.3.4/', 'beefhook' => '1', 'result' => '', 'url' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/cross_origin_scanner_cors' => { + datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&status=200', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/detect_burp' => { + datastore: { 'results' => 'has_burp=true&response=PROXY 127.0.0.1:8080', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/get_ntop_network_hosts' => { + datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=3000&data={"hostNumIpAddress":"192.168.1.1"}', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/internal_network_fingerprinting' => { + datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&discovered=Apache&url=http://test/', 'beefhook' => '1', 'result' => '', 'discovered' => '', 'url' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/get_proxy_servers_wpad' => [ + { datastore: { 'results' => 'proxies=PROXY 192.168.1.1:3128', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }, + { datastore: { 'results' => 'proxies=SOCKS 192.168.1.1:1080', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } } + ], + 'modules/network/jslanscanner' => [ + { datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&service=HTTP', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }, + { datastore: { 'results' => 'ip=1.2.3.4&device=Router', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } } + ], + 'modules/network/fetch_port_scanner' => { + datastore: { 'result' => 'ok', 'beefhook' => '1', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/cross_origin_scanner_flash' => [ + { datastore: { 'results' => 'ip=1.2.3.4&status=alive', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }, + { datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&title=Apache', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } } + ] +}.freeze + +project_root = File.expand_path('../../../../', __dir__) +paths = Dir[File.join(project_root, 'modules/network/**/module.rb')].sort + +paths.each do |path| + rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') + branch_key = File.dirname(path).sub("#{project_root}/", '') + require_path = File.join('../../../../', rel) + class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ } + next unless class_line + + klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1] + require_relative require_path + mod = Object.const_get(klass_name) + + RSpec.describe mod do + # Irc_nat_pinning calls sleep 30 in post_execute; stub so the suite doesn't block. + before(:each) do + allow(Kernel).to receive(:sleep) + allow_any_instance_of(described_class).to receive(:sleep).and_return(nil) + end + + describe '.options' do + it 'returns an Array when defined' do + next unless described_class.respond_to?(:options) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + expect(described_class.options).to be_an(Array) + end + end + + describe '#pre_send' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:pre_send) + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:bind_socket).and_return(nil) + allow(handler).to receive(:unbind_socket).and_return(nil) + allow(handler).to receive(:bind_cached).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything) do |key| + case key + when 'beef.extension.dns_rebinding' then { 'address_http_external' => '127.0.0.1', 'port_proxy' => '1234' } + when 'beef.module.dns_rebinding.domain' then 'example.com' + else '127.0.0.1' + end + end + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + # Dns_rebinding expects @datastore as Array of option hashes (target, domain, url_callback) + datastore = described_class.name.include?('Dns_rebinding') ? [{ 'value' => '192.168.0.1' }, { 'value' => 'example.com' }, {}] : { 'result' => '', 'results' => '', 'cid' => '0' } + instance = build_command_instance(described_class, datastore) + expect { run_pre_send(instance) }.not_to raise_error + end + end + + describe '#post_execute' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:post_execute) + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:bind_socket) + allow(handler).to receive(:unbind_socket) + allow(handler).to receive(:remap) + allow(handler).to receive(:bind_cached) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + file_double = double('File', write: nil, close: nil) + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') + allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') + allow(Kernel).to receive(:sleep) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end + + it 'runs branch path when in BRANCH_COVERAGE' do + raw = BRANCH_COVERAGE[branch_key] + next unless described_class.method_defined?(:post_execute) && raw + + branches = raw.is_a?(Array) ? raw : [raw] + branches.each do |branch| + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:bind_socket) + allow(handler).to receive(:unbind_socket) + allow(handler).to receive(:remap) + allow(handler).to receive(:bind_cached) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + file_double = double('File', write: nil, close: nil) + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') + allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') + allow(Kernel).to receive(:sleep) + allow(BeEF::Core::Models::NetworkService).to receive(:create) + allow(BeEF::Core::Models::NetworkHost).to receive(:create) + allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) + + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything) do |key| + branch[:config_get] && branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1' + end + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + + instance = build_command_instance(described_class, branch[:datastore]) + expect { run_post_execute(instance) }.not_to raise_error + end + end + end + end +end diff --git a/spec/beef/modules/persistence/persistence_spec.rb b/spec/beef/modules/persistence/persistence_spec.rb new file mode 100644 index 000000000..138340499 --- /dev/null +++ b/spec/beef/modules/persistence/persistence_spec.rb @@ -0,0 +1,72 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Unit tests for every module under modules/persistence/. +# + +require_relative '../../../spec_helper' + +project_root = File.expand_path('../../../../', __dir__) +paths = Dir[File.join(project_root, 'modules/persistence/**/module.rb')].sort + +paths.each do |path| + rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') + require_path = File.join('../../../../', rel) + class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ } + next unless class_line + + klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1] + require_relative require_path + mod = Object.const_get(klass_name) + + RSpec.describe mod do + describe '.options' do + it 'returns an Array when defined' do + next unless described_class.respond_to?(:options) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + expect(described_class.options).to be_an(Array) + end + end + + describe '#pre_send' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:pre_send) + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('/hook.js') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_pre_send(instance) }.not_to raise_error + end + end + + describe '#post_execute' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:post_execute) + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(handler).to receive(:bind_raw) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + file_double = double('File', write: nil, close: nil) + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end + end + end +end diff --git a/spec/beef/modules/phonegap/phonegap_spec.rb b/spec/beef/modules/phonegap/phonegap_spec.rb new file mode 100644 index 000000000..642d8519d --- /dev/null +++ b/spec/beef/modules/phonegap/phonegap_spec.rb @@ -0,0 +1,69 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Unit tests for every module under modules/phonegap/. +# Some modules use #callback instead of #post_execute; only post_execute is tested here. +# + +require_relative '../../../spec_helper' + +project_root = File.expand_path('../../../../', __dir__) +paths = Dir[File.join(project_root, 'modules/phonegap/**/module.rb')].sort + +paths.each do |path| + rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') + require_path = File.join('../../../../', rel) + class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ } + next unless class_line + + klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1] + require_relative require_path + mod = Object.const_get(klass_name) + + RSpec.describe mod do + describe '.options' do + it 'returns an Array when defined' do + next unless described_class.respond_to?(:options) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:hook_file_path).and_return('/hook.js') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + expect(described_class.options).to be_an(Array) + end + end + + describe '#pre_send' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:pre_send) + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, 'result' => '', 'Result' => '', 'cid' => '0') + expect { run_pre_send(instance) }.not_to raise_error + end + end + + describe '#post_execute' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:post_execute) + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + instance = build_command_instance(described_class, 'result' => '', 'Result' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end + end + end +end diff --git a/spec/beef/modules/social_engineering/social_engineering_spec.rb b/spec/beef/modules/social_engineering/social_engineering_spec.rb new file mode 100644 index 000000000..0ac827b94 --- /dev/null +++ b/spec/beef/modules/social_engineering/social_engineering_spec.rb @@ -0,0 +1,108 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Unit tests for every module under modules/social_engineering/. +# Branch coverage: extra post_execute for KILLFRAME / conditional paths. +# + +require_relative '../../../spec_helper' + +project_root = File.expand_path('../../../../', __dir__) +paths = Dir[File.join(project_root, 'modules/social_engineering/**/module.rb')].sort + +BRANCH_COVERAGE = { + 'modules/social_engineering/fake_lastpass' => { datastore: { 'meta' => 'KILLFRAME', 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0' } }, + 'modules/social_engineering/fake_evernote_clipper' => { datastore: { 'meta' => 'KILLFRAME', 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0' } } +}.freeze + +# Modules whose pre_send needs Zip, real filesystem, or logger; skip generic pre_send example +PRE_SEND_SKIP = %w[ + Firefox_extension_bindshell Firefox_extension_dropper Firefox_extension_reverse_shell + Text_to_voice Ui_abuse_ie +].freeze + +paths.each do |path| + rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') + branch_key = File.dirname(path).sub("#{project_root}/", '') + require_path = File.join('../../../../', rel) + class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ } + next unless class_line + + klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1] + require_relative require_path + mod = Object.const_get(klass_name) + + RSpec.describe mod do + describe '.options' do + it 'returns an Array when defined' do + next unless described_class.respond_to?(:options) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000') + allow(config).to receive(:get).with(anything) do |key| + key == 'beef.module.simple_hijacker.templates' ? [] : '127.0.0.1' + end + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + expect(described_class.options).to be_an(Array) + end + end + + describe '#pre_send' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:pre_send) + next if PRE_SEND_SKIP.include?(described_class.name) + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).with(anything, anything, anything, anything, anything).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + allow(IO).to receive(:popen).and_return(StringIO.new('')) + # Many pre_send implementations expect @datastore to be an Array of option hashes (name/value) + pre_send_datastore = [ + { 'name' => 'payload', 'value' => 'cmd' }, + { 'name' => 'payload_handler', 'value' => 'http://127.0.0.1:8080' }, + { 'name' => 'extension_name', 'value' => 'TestExt' }, + { 'name' => 'xpi_name', 'value' => 'test.xpi' }, + { 'name' => 'lport', 'value' => '4444' } + ] + instance = build_command_instance(described_class, pre_send_datastore) + expect { run_pre_send(instance) }.not_to raise_error + end + end + + describe '#post_execute' do + it 'runs without error when defined' do + next unless described_class.method_defined?(:post_execute) + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end + + it 'runs branch path when in BRANCH_COVERAGE' do + branch = BRANCH_COVERAGE[branch_key] + next unless described_class.method_defined?(:post_execute) && branch + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + instance = build_command_instance(described_class, branch[:datastore]) + expect { run_post_execute(instance) }.not_to raise_error + end + end + end +end diff --git a/spec/support/module_spec_helper.rb b/spec/support/module_spec_helper.rb new file mode 100644 index 000000000..8a86d17fd --- /dev/null +++ b/spec/support/module_spec_helper.rb @@ -0,0 +1,58 @@ +# +# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - https://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +# Helpers for unit specs of command modules (modules/*/module.rb). +# Use with stubbed config; no REST/API or real server. +# + +module ModuleSpecHelper + # + # Build a command instance for testing post_execute (or other instance methods). + # Uses allocate so we avoid Command#initialize reading from config. + # Set @datastore before calling post_execute; results are stored in @results via save(). + # + # @param klass [Class] The command module class (e.g. Test_beef_debug) + # @param datastore [Hash] Data to set on @datastore (simulates callback data) + # @return [Object] Instance with @datastore set; call post_execute then read instance_variable_get(:@results) + # + def build_command_instance(klass, datastore = {}) + instance = klass.allocate + instance.instance_variable_set(:@datastore, datastore) + instance + end + + # + # Call post_execute on a command instance and return the saved results. + # Use after build_command_instance(klass, datastore). + # + # @param instance [Object] Command instance from build_command_instance + # @return [Hash, nil] The value passed to save() (stored in @results) + # + def run_post_execute(instance) + instance.post_execute + instance.instance_variable_get(:@results) + end + + # + # Call pre_send on a command instance (e.g. before sending the command to the hooked browser). + # Use after build_command_instance(klass, datastore). Stub AssetHandler, Configuration, etc. before calling. + # + # @param instance [Object] Command instance from build_command_instance + # + def run_pre_send(instance) + instance.pre_send + end +end + +RSpec.configure do |config| + config.include ModuleSpecHelper + + # Stub Kernel.sleep for all module specs so modules that call sleep (e.g. Irc_nat_pinning's post_execute sleep 30) don't slow the suite. + config.before(:each) do |example| + if example.metadata[:file_path]&.include?('spec/beef/modules') + allow(Kernel).to receive(:sleep) + end + end +end From ad2fed571a0d1448312fc9e45419519b975912b5 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Thu, 26 Feb 2026 10:30:15 +1000 Subject: [PATCH 25/37] UPDATE: simplecov config --- .bundle/config | 1 - .rspec | 3 + .simplecov | 32 ++++ Rakefile | 76 ++++++++- .../bounce_to_ie_configured.pdf | 2 +- spec/README.md | 146 ++++++++++++++++++ spec/beef/modules/browser/browser_spec.rb | 9 +- spec/beef/modules/misc/misc_spec.rb | 66 +++++++- spec/beef/modules/network/network_spec.rb | 11 +- spec/spec_helper.rb | 112 +++++++++++++- 10 files changed, 434 insertions(+), 24 deletions(-) create mode 100644 .simplecov create mode 100644 spec/README.md diff --git a/.bundle/config b/.bundle/config index 4c8cd7f74..b6ce7f0ea 100644 --- a/.bundle/config +++ b/.bundle/config @@ -1,3 +1,2 @@ --- -BUNDLE_WITHOUT: "development:test" BUNDLE_WITH: "geoip:ext_msf:ext_notifications:ext_dns:ext_qrcode" diff --git a/.rspec b/.rspec index 9619a10b3..9e7a5ec96 100644 --- a/.rspec +++ b/.rspec @@ -2,3 +2,6 @@ --color --require spec_helper -I . +--order defined +--tag ~run_on_browserstack +--tag ~run_on_long_tests \ No newline at end of file diff --git a/.simplecov b/.simplecov new file mode 100644 index 000000000..9ecf5c4e0 --- /dev/null +++ b/.simplecov @@ -0,0 +1,32 @@ +# SimpleCov configuration file +# This provides a cleaner alternative to configuring SimpleCov in spec_helper.rb + +SimpleCov.configure do + # Basic filters + add_filter '/spec/' + add_filter '/config/' + add_filter '/test/' + + # Group coverage by component + add_group 'Core', 'core' + add_group 'Extensions', 'extensions' + add_group 'Modules', 'modules' + + # Track files based on coverage focus + if ENV['COVERAGE'] == 'core' + track_files 'core/**/*.rb' + elsif ENV['COVERAGE'] == 'extensions' + track_files 'extensions/**/*.rb' + elsif ENV['COVERAGE'] == 'modules' + track_files 'modules/**/*.rb' + else + # Default: track everything + track_files '{core,extensions,modules}/**/*.rb' + end + + # Coverage thresholds + minimum_coverage 80 if ENV['CI'] + + # Formatters + formatter SimpleCov::Formatter::HTMLFormatter +end \ No newline at end of file diff --git a/Rakefile b/Rakefile index 6a80c1b39..2709cd268 100644 --- a/Rakefile +++ b/Rakefile @@ -5,12 +5,82 @@ # require 'rspec/core/rake_task' -task :default => ["short"] +task default: ['short'] -RSpec::Core::RakeTask.new(:short) do |task| - task.rspec_opts = ['--tag ~run_on_browserstack', '--tag ~run_on_long_tests'] +# Run rspec with an explicit file list (avoids envs that only run 557). +# Note: when run with all 81 files, module specs load after extensions; the Dns stub in +# network_spec must not run before the real Dns extension (dns_spec) or you get "superclass mismatch". +desc 'Run short spec suite (all specs except browserstack/long)' +task :short do + short_files = Dir[File.join(Dir.pwd, 'spec', '**', '*_spec.rb')].sort + $stderr.puts "[rake short] spec files=#{short_files.size}" + abort '[rake short] Expected 81+ spec files; check you are in project root.' if short_files.size < 80 + opts = [ + '--tag', '~run_on_browserstack', + '--tag', '~run_on_long_tests' + ] + ok = system('bundle', 'exec', 'rspec', *short_files, *opts) + abort 'rspec failed' unless ok end +# Legacy namespace for backward compatibility +namespace :coverage do + task :modules => 'coverage_modules' + task :core => 'coverage_core' + task :extensions => 'coverage_extensions' + task :all => 'coverage' +end + +# Base spec tasks +RSpec::Core::RakeTask.new(:spec) do |t| + t.pattern = 'spec/**/*_spec.rb' + t.rspec_opts = ['--tag', '~run_on_browserstack', '--tag', '~run_on_long_tests'] +end + +RSpec::Core::RakeTask.new(:spec_core) do |t| + t.pattern = 'spec/beef/core/**/*_spec.rb' + t.rspec_opts = ['--tag', '~run_on_browserstack', '--tag', '~run_on_long_tests'] +end + +RSpec::Core::RakeTask.new(:spec_extensions) do |t| + t.pattern = 'spec/beef/extensions/**/*_spec.rb' + t.rspec_opts = ['--tag', '~run_on_browserstack', '--tag', '~run_on_long_tests'] +end + +RSpec::Core::RakeTask.new(:spec_modules) do |t| + t.pattern = 'spec/beef/modules/**/*_spec.rb' + t.rspec_opts = ['--tag', '~run_on_browserstack', '--tag', '~run_on_long_tests'] +end + +# Coverage tasks using environment variables for cleaner configuration +desc 'Run all specs with complete coverage tracking' +task :coverage do + ENV['COVERAGE'] = 'all' + Rake::Task['spec'].invoke +end + +desc 'Run core specs with coverage' +task :coverage_core do + ENV['COVERAGE'] = 'core' + Rake::Task['spec_core'].invoke +end + +desc 'Run extensions specs with coverage' +task :coverage_extensions do + ENV['COVERAGE'] = 'extensions' + Rake::Task['spec_extensions'].invoke +end + +desc 'Run modules specs with coverage' +task :coverage_modules do + ENV['COVERAGE'] = 'modules' + Rake::Task['spec_modules'].invoke +end + +# Alias for backward compatibility +task :coverage_complete => :coverage +task :coverage_all => :coverage + RSpec::Core::RakeTask.new(:long) do |task| task.rspec_opts = ['--tag ~run_on_browserstack'] end diff --git a/modules/host/hook_default_browser/bounce_to_ie_configured.pdf b/modules/host/hook_default_browser/bounce_to_ie_configured.pdf index 21549a4b0..372b6e4d7 100644 --- a/modules/host/hook_default_browser/bounce_to_ie_configured.pdf +++ b/modules/host/hook_default_browser/bounce_to_ie_configured.pdf @@ -46,7 +46,7 @@ endobj << /Length 1708 >>stream -app.launchURL("http://0.0.0.0:3000/demos/report.html",true); +app.launchURL("http://127.0.0.1:127.0.0.1/demos/report.html",true); endstream endobj 7 0 obj diff --git a/spec/README.md b/spec/README.md new file mode 100644 index 000000000..f6b1bbdee --- /dev/null +++ b/spec/README.md @@ -0,0 +1,146 @@ +# BeEF Test Suite + +This directory contains the BeEF test suite using RSpec and SimpleCov for comprehensive testing and coverage reporting. + +## Setup + +### Prerequisites +- Ruby 3.0+ +- Bundler +- All gems from `Gemfile` + +### Configuration Files +- `spec/spec_helper.rb` - Main test configuration +- `.simplecov` - Coverage configuration +- `spec/support/` - Test helpers and utilities + +## Running Tests + +### Basic Commands + +```bash +# Run all tests (fast, no coverage) +bundle exec rake short +# or +bundle exec rspec spec/ --tag '~run_on_browserstack' --tag '~run_on_long_tests' + +# Run specific component tests +bundle exec rspec spec/beef/core/ +bundle exec rspec spec/beef/extensions/ +bundle exec rspec spec/beef/modules/ +``` + +### Coverage Commands + +```bash +# Complete coverage (recommended) +bundle exec rake coverage +# or +COVERAGE=all bundle exec rspec spec/ --tag '~run_on_browserstack' --tag '~run_on_long_tests' + +# Component-specific coverage +bundle exec rake coverage_core # Core only +bundle exec rake coverage_modules # Modules only +bundle exec rake coverage_extensions # Extensions only +``` + +### Legacy Commands (Still Supported) + +```bash +# Old coverage commands still work +bundle exec rake coverage:modules +bundle exec rake coverage:all +bundle exec rake coverage_complete +``` + +## Architecture + +### SimpleCov Configuration + +- **Environment-based**: Uses `COVERAGE=core|modules|extensions|all` environment variable +- **Grouped reporting**: Separate groups for Core, Extensions, and Modules +- **Filtered tracking**: Only tracks relevant files based on focus area +- **HTML reports**: Generated in `coverage/` directory + +### Test Organization + +- **Centralized config**: `BeefTestConfig` module provides test data instead of global constants +- **Component isolation**: Each component (core/extensions/modules) has dedicated specs +- **Branch coverage**: Realistic test data for conditional logic testing +- **Mock management**: Proper mocking of external dependencies + +### Rake Tasks + +- **Clean separation**: Base `spec*` tasks vs coverage `coverage*` tasks +- **Environment variables**: Coverage controlled via `COVERAGE` env var +- **No sequential execution**: Single test runs with proper filtering +- **Backward compatibility**: Old task names still work + +## Key Improvements + +### ✅ **Eliminated Global Constants** +- Replaced `BRANCH_COVERAGE` constants with centralized `BeefTestConfig` module +- No more "already initialized constant" warnings + +### ✅ **Simplified Coverage Logic** +- Cleaner filtering using `track_files` instead of complex `add_filter` logic +- Environment variable `COVERAGE` instead of `COVERAGE_FOCUS` + +### ✅ **Better Test Organization** +- Centralized test configuration in `BeefTestConfig` +- Component-specific test data loading +- Reduced code duplication + +### ✅ **Cleaner Rake Tasks** +- Single execution instead of sequential runs +- Proper environment variable usage +- Backward compatibility maintained + +### ✅ **Standard Patterns** +- Uses `.simplecov` config file (standard practice) +- Follows RSpec best practices +- Better separation of concerns + +## Coverage Focus Areas + +- **core**: Framework core functionality +- **extensions**: Extension modules +- **modules**: Command modules (main focus) +- **all**: Complete coverage across all areas + +## Troubleshooting + +### Common Issues + +1. **"already initialized constant" warnings** + - Fixed by using centralized config instead of global constants + +2. **Low coverage percentages** + - Use `COVERAGE=all` for complete coverage + - Ensure realistic test data triggers conditional paths + +3. **Test failures** + - Check that mocks are properly configured + - Verify test data matches module expectations + +### Debug Commands + +```bash +# Run with debug output +bundle exec rspec --format documentation + +# Run single test file +bundle exec rspec spec/beef/modules/browser/browser_spec.rb + +# Check coverage report +open coverage/index.html +``` + +## Contributing + +When adding new tests: + +1. Use `BeefTestConfig.branch_coverage_for(:component)` for branch test data +2. Add realistic datastore values that trigger conditional logic +3. Mock external dependencies (database, network, etc.) +4. Follow existing patterns for consistency \ No newline at end of file diff --git a/spec/beef/modules/browser/browser_spec.rb b/spec/beef/modules/browser/browser_spec.rb index 637899572..319457051 100644 --- a/spec/beef/modules/browser/browser_spec.rb +++ b/spec/beef/modules/browser/browser_spec.rb @@ -12,13 +12,8 @@ require_relative '../../../spec_helper' project_root = File.expand_path('../../../../', __dir__) browser_module_paths = Dir[File.join(project_root, 'modules/browser/**/module.rb')].sort -BRANCH_COVERAGE = { - 'modules/browser/detect_wmp' => { datastore: { 'results' => 'wmp=Yes', 'wmp' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }, - 'modules/browser/detect_vlc' => { datastore: { 'results' => 'vlc=Yes', 'vlc' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }, - 'modules/browser/detect_office' => { datastore: { 'results' => 'office=Office 2016', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }, - 'modules/browser/detect_foxit' => { datastore: { 'results' => 'foxit=Yes', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }, - 'modules/browser/detect_activex' => { datastore: { 'results' => 'activex=Yes', 'activex' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } } -}.freeze +# Load branch coverage data from centralized config +BRANCH_COVERAGE = BeefTestConfig.branch_coverage_for(:browser) browser_module_paths.each do |path| rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') diff --git a/spec/beef/modules/misc/misc_spec.rb b/spec/beef/modules/misc/misc_spec.rb index 64afe56be..bfee72efc 100644 --- a/spec/beef/modules/misc/misc_spec.rb +++ b/spec/beef/modules/misc/misc_spec.rb @@ -20,10 +20,10 @@ paths.each do |path| rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') branch_key = File.dirname(path).sub("#{project_root}/", '') require_path = File.join('../../../../', rel) - class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ } + class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+(?:\w+|BeEF::\w+(?:::\w+)*)/ } next unless class_line - klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1] + klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+(?:\w+|BeEF::\w+(?:::\w+)*)/)[1] require_relative require_path mod = Object.const_get(klass_name) @@ -31,6 +31,7 @@ paths.each do |path| describe '.options' do it 'returns an Array when defined' do next unless described_class.respond_to?(:options) + config = instance_double(BeEF::Core::Configuration) allow(config).to receive(:beef_host).and_return('127.0.0.1') allow(config).to receive(:beef_port).and_return('3000') @@ -41,9 +42,69 @@ paths.each do |path| end end + # Specific test for Wordpress_add_user to ensure options method executes fully + if klass_name == 'Wordpress_add_user' + describe '.options' do + it 'includes wordpress path and user options' do + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + + options = described_class.options + expect(options).to be_an(Array) + expect(options.length).to eq(6) # wp_path + 5 user options + + # Check that wp_path option exists (from parent) + wp_path_option = options.find { |opt| opt['name'] == 'wp_path' } + expect(wp_path_option).to be_present + expect(wp_path_option['value']).to eq('/') + + # Check that username option exists + username_option = options.find { |opt| opt['name'] == 'username' } + expect(username_option).to be_present + expect(username_option['value']).to eq('beef') + + # Check that password option exists and has a generated value + password_option = options.find { |opt| opt['name'] == 'password' } + expect(password_option).to be_present + expect(password_option['value']).to be_a(String) + expect(password_option['value'].length).to eq(10) # SecureRandom.hex(5) = 10 chars + end + end + end + + # Specific test for Test_get_variable to ensure options method executes fully + if klass_name == 'Test_get_variable' + describe '.options' do + it 'returns payload_name option with correct structure' do + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + + options = described_class.options + expect(options).to be_an(Array) + expect(options.length).to eq(1) + + option = options.first + expect(option['name']).to eq('payload_name') + expect(option['ui_label']).to eq('Payload Name') + expect(option['type']).to eq('text') + expect(option['value']).to eq('message') + expect(option['width']).to eq('400px') + end + end + end + describe '#pre_send' do it 'runs without error when defined' do next unless described_class.method_defined?(:pre_send) + handler = instance_double('AssetHandler') allow(handler).to receive(:unbind).and_return(nil) allow(handler).to receive(:bind).and_return(nil) @@ -62,6 +123,7 @@ paths.each do |path| describe '#post_execute' do it 'runs without error when defined' do next unless described_class.method_defined?(:post_execute) + handler = instance_double('AssetHandler') allow(handler).to receive(:unbind) allow(handler).to receive(:bind) diff --git a/spec/beef/modules/network/network_spec.rb b/spec/beef/modules/network/network_spec.rb index 0d987198d..d26ba6f13 100644 --- a/spec/beef/modules/network/network_spec.rb +++ b/spec/beef/modules/network/network_spec.rb @@ -8,11 +8,11 @@ require_relative '../../../spec_helper' -# Stub Dns extension so network/dns_rebinding/module.rb can load (references BeEF::Extension::Dns::Server). +# Stub Dns extension only when the real one is not loaded (e.g. rake coverage:modules). +# If we stub when the real extension loads later (e.g. dns_spec), we get "superclass mismatch for class Server". +# When the real Dns is already loaded (rake short), we stub Server.instance in the pre_send example instead. unless BeEF::Extension.const_defined?(:Dns) BeEF::Extension.const_set(:Dns, Module.new) -end -unless BeEF::Extension::Dns.const_defined?(:Server) dns_server_instance = Object.new def dns_server_instance.add_rule(*) 1 end def dns_server_instance.remove_rule!(*) nil end @@ -111,6 +111,11 @@ paths.each do |path| describe '#pre_send' do it 'runs without error when defined' do next unless described_class.method_defined?(:pre_send) + # When real Dns extension is loaded (rake short), stub Server.instance so Dns_rebinding works + if BeEF::Extension.const_defined?(:Dns) && BeEF::Extension::Dns.const_defined?(:Server) + dns_instance = double('DnsServer', add_rule: 1, remove_rule!: nil) + allow(BeEF::Extension::Dns::Server).to receive(:instance).and_return(dns_instance) + end handler = instance_double('AssetHandler') allow(handler).to receive(:unbind).and_return(nil) allow(handler).to receive(:bind).and_return(nil) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b4a4e7df2..4afa5bb52 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,14 +3,112 @@ # Browser Exploitation Framework (BeEF) - https://beefproject.com # See the file 'doc/COPYING' for copying permission # + # Coverage must start before loading application code. -require 'simplecov' -SimpleCov.start do - add_filter '/spec/' - add_group 'Core', 'core' - add_group 'Extensions', 'extensions' - add_group 'Modules', 'modules' - track_files '{core,extensions,modules}/**/*.rb' +require 'simplecov' and SimpleCov.start if ENV['COVERAGE'] + +# Load test configuration for branch coverage data +module BeefTestConfig + def self.branch_coverage_for(component) + case component + when :browser + { + 'modules/browser/detect_wmp' => { datastore: { 'results' => 'wmp=Yes', 'wmp' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }, + 'modules/browser/detect_vlc' => { datastore: { 'results' => 'vlc=Yes', 'vlc' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }, + 'modules/browser/detect_office' => { datastore: { 'results' => 'office=Office 2016', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }, + 'modules/browser/detect_foxit' => { datastore: { 'results' => 'foxit=Yes', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }, + 'modules/browser/detect_activex' => { datastore: { 'results' => 'activex=Yes', 'activex' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } } + } + when :network + { + 'modules/network/port_scanner' => { + datastore: { 'results' => 'ip=1.2.3.4&port=HTTP: Port 80 is OPEN Apache', 'beefhook' => '1', 'result' => '', 'port' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/ping_sweep' => { + datastore: { 'results' => 'ip=192.168.1.1&ping=10ms', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + } + } + else + {} + end + end +end + +# Returns [total_lines, covered_lines] for a group, or nil if no group. +def group_coverage(r, group_name) + group = r.groups[group_name] + return nil unless group&.respond_to?(:each) + + total_lines = 0 + covered_lines = 0 + group.each do |src_file| + total_lines += src_file.lines_of_code + covered_lines += src_file.covered_lines.compact.size + end + [total_lines, covered_lines] +end + +# Print one group's coverage (skip forked children: very low coverage or zero). +def print_group_coverage(r, group_name, color: "\e[36m") + total_lines, covered_lines = group_coverage(r, group_name) + return unless total_lines && total_lines.positive? + return if covered_lines.zero? + + pct = covered_lines.to_f / total_lines * 100 + # Skip noise from forked children (e.g. 0.03% Core). + return if pct < 1.0 + + puts "#{color}#{group_name} coverage: #{pct.round(2)}% (#{covered_lines} / #{total_lines} lines)\e[0m" +end + +# Only print from the main process (skip forked children with tiny coverage). +MIN_COVERAGE_PCT = 5.0 + +def coverage_from_main_process?(r, focus) + case focus + when 'all' + %w[Core Extensions Modules].any? do |name| + total, covered = group_coverage(r, name) + next false unless total && total.positive? + (covered.to_f / total * 100) >= MIN_COVERAGE_PCT + end + when 'core' + total, covered = group_coverage(r, 'Core') + total && total.positive? && (covered.to_f / total * 100) >= MIN_COVERAGE_PCT + when 'extensions' + total, covered = group_coverage(r, 'Extensions') + total && total.positive? && (covered.to_f / total * 100) >= MIN_COVERAGE_PCT + when 'modules', nil + total, covered = group_coverage(r, 'Modules') + total && total.positive? && (covered.to_f / total * 100) >= MIN_COVERAGE_PCT + else + false + end +end + +at_exit do + if defined?(SimpleCov) && SimpleCov.respond_to?(:result) && (r = SimpleCov.result) + focus = ENV['COVERAGE'] + next unless coverage_from_main_process?(r, focus) + + case focus + when 'core' + print_group_coverage(r, 'Core') + when 'extensions' + print_group_coverage(r, 'Extensions') + when 'modules' + print_group_coverage(r, 'Modules') + when 'all' + puts + print_group_coverage(r, 'Core') + print_group_coverage(r, 'Extensions') + print_group_coverage(r, 'Modules') + else + print_group_coverage(r, 'Modules') + end + end end # Set external and internal character encodings to UTF-8 From 82c57b4ea126bc4c6d89a268c6fcbae0bdbf1637 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 27 Feb 2026 11:40:44 +1000 Subject: [PATCH 26/37] FIX: unused variable --- spec/beef/core/main/models/command_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/beef/core/main/models/command_spec.rb b/spec/beef/core/main/models/command_spec.rb index 2cec84b98..0a6d75d03 100644 --- a/spec/beef/core/main/models/command_spec.rb +++ b/spec/beef/core/main/models/command_spec.rb @@ -113,7 +113,7 @@ RSpec.describe BeEF::Core::Models::Command do end it 'raises TypeError when command is not found for id and hooked_browser' do - other_hb = BeEF::Core::Models::HookedBrowser.create!(session: 'other_session', ip: '127.0.0.1') + BeEF::Core::Models::HookedBrowser.create!(session: 'other_session', ip: '127.0.0.1') expect do described_class.save_result('other_session', command.id, 'Name', {}, 1) From f0e47a822d2c02b007010e625f5802044c608881 Mon Sep 17 00:00:00 2001 From: Jake Webster Date: Fri, 27 Feb 2026 14:18:28 +1000 Subject: [PATCH 27/37] FIX: unused variable --- spec/beef/core/main/models/command_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/beef/core/main/models/command_spec.rb b/spec/beef/core/main/models/command_spec.rb index 2cec84b98..0a6d75d03 100644 --- a/spec/beef/core/main/models/command_spec.rb +++ b/spec/beef/core/main/models/command_spec.rb @@ -113,7 +113,7 @@ RSpec.describe BeEF::Core::Models::Command do end it 'raises TypeError when command is not found for id and hooked_browser' do - other_hb = BeEF::Core::Models::HookedBrowser.create!(session: 'other_session', ip: '127.0.0.1') + BeEF::Core::Models::HookedBrowser.create!(session: 'other_session', ip: '127.0.0.1') expect do described_class.save_result('other_session', command.id, 'Name', {}, 1) From c8bd7a7feab0656d5f2f7afa35e79626124851e7 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Mon, 18 May 2026 14:45:48 +1000 Subject: [PATCH 28/37] fix: exclude ERB modules from ESLint, pin actions to latest stable SHAs, bump Node to 24.x --- .eslintrc.json | 3 ++- .github/workflows/eslint.yml | 14 +++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 9d1e4d0aa..0bb6ce9d8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,7 +11,8 @@ }, "ignorePatterns": [ - "core/main/client/lib/**" + "core/main/client/lib/**", + "modules/**" ], "rules": { diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index cfd83503e..54070cf9b 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -15,14 +15,14 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: '20.x' + node-version: '24.x' cache: 'npm' - name: Install dependencies @@ -30,7 +30,7 @@ jobs: - name: Get changed JavaScript files id: changed-js-files - uses: tj-actions/changed-files@v45 + uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 with: files: '**/*.js' files_ignore: | @@ -58,12 +58,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: '20.x' + node-version: '24.x' cache: 'npm' - name: Install dependencies From ae8c5b203feed4dca3748097c1b74c488f1d3009 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 05:48:06 +0000 Subject: [PATCH 29/37] Build(deps): bump rake from 13.4.1 to 13.4.2 Bumps [rake](https://github.com/ruby/rake) from 13.4.1 to 13.4.2. - [Release notes](https://github.com/ruby/rake/releases) - [Changelog](https://github.com/ruby/rake/blob/master/History.rdoc) - [Commits](https://github.com/ruby/rake/compare/v13.4.1...v13.4.2) --- updated-dependencies: - dependency-name: rake dependency-version: 13.4.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index f079e6866..644a306df 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -171,7 +171,7 @@ GEM rack-test (2.2.0) rack (>= 1.3) rainbow (3.1.1) - rake (13.4.1) + rake (13.4.2) rdoc (7.2.0) erb psych (>= 4.0.0) From ecd210eccd995cf6a1c606eabd0ad867e19911d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 06:28:40 +0000 Subject: [PATCH 30/37] Build(deps-dev): bump minimatch from 3.1.2 to 3.1.5 Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.1.2 to 3.1.5. - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5) --- updated-dependencies: - dependency-name: minimatch dependency-version: 3.1.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cf8fbdb7b..9c8d5b007 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1592,9 +1592,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { From d48e253b2912362c1d5d09d6586cdb586f528055 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 06:30:54 +0000 Subject: [PATCH 31/37] Build(deps-dev): bump flatted from 3.3.3 to 3.4.2 Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.3 to 3.4.2. - [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.2) --- updated-dependencies: - dependency-name: flatted dependency-version: 3.4.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9c8d5b007..691c7834e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1081,9 +1081,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, From 824e95695dfc1b790c0aab0d8380a4e6bfc99e2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 07:05:23 +0000 Subject: [PATCH 32/37] Build(deps): bump json from 2.19.4 to 2.19.5 Bumps [json](https://github.com/ruby/json) from 2.19.4 to 2.19.5. - [Release notes](https://github.com/ruby/json/releases) - [Changelog](https://github.com/ruby/json/blob/master/CHANGES.md) - [Commits](https://github.com/ruby/json/compare/v2.19.4...v2.19.5) --- updated-dependencies: - dependency-name: json dependency-version: 2.19.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 870b510f7..28f68c30b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -90,7 +90,7 @@ GEM prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (2.19.4) + json (2.19.5) language_server-protocol (3.17.0.5) lint_roller (1.1.0) logger (1.7.0) From 8af54e0bcab957223745d9323d46e208a83ff12d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 02:36:21 +0000 Subject: [PATCH 33/37] Build(deps-dev): bump fast-uri from 3.1.0 to 3.1.2 Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.0 to 3.1.2. - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](https://github.com/fastify/fast-uri/compare/v3.1.0...v3.1.2) --- updated-dependencies: - dependency-name: fast-uri dependency-version: 3.1.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 691c7834e..972a3242f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -976,9 +976,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "dev": true, "funding": [ { From c4d9211aae7032388a15962d79649e4f4823e3e5 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Tue, 19 May 2026 19:43:57 +1000 Subject: [PATCH 34/37] Upgrade ESLint to v10.4.0 with flat config --- .eslintrc.json | 47 -- .github/workflows/eslint.yml | 2 +- .npmrc | 1 + eslint.config.js | 48 ++ package-lock.json | 1177 ++++++++++++---------------------- package.json | 12 +- 6 files changed, 468 insertions(+), 819 deletions(-) delete mode 100644 .eslintrc.json create mode 100644 .npmrc create mode 100644 eslint.config.js diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 0bb6ce9d8..000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "env": { - "browser": true, - "es6": false - }, - "extends": ["eslint:recommended"], - - "parserOptions": { - "ecmaVersion": 5, - "sourceType": "script" - }, - - "ignorePatterns": [ - "core/main/client/lib/**", - "modules/**" - ], - - "rules": { - /* Keep this minimal at first. Add project-specific rules after seeing CI logs. */ - }, - - "globals": { - // beef - "beef": "readonly", - - // jQuery / alias - "$": "readonly", - "jQuery": "readonly", - "$j": "readonly", - - // BeEF bootstrapping - "beef_init": "readonly", - - // Existing libraries - "MobileEsp": "readonly", - "evercookie": "readonly", - "swfobject": "readonly", - - // Browser-specific globals (old IE / old Firefox) - "XDomainRequest": "readonly", - "MozWebSocket": "readonly", - "clipboardData": "readonly", - - // Debug - "isDebug": "readonly" - } -} \ No newline at end of file diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 54070cf9b..2365fea36 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -73,7 +73,7 @@ jobs: run: | echo "Running ESLint on entire codebase" echo "This is a report-only job and will not block the PR." - npx eslint "**/*.js" || true + npx eslint . || true - name: Summary if: always() diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..ec9e05d8a --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +min-release-age=3 diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..762db77e2 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,48 @@ +"use strict"; + +const js = require("@eslint/js"); +const globals = require("globals"); + +module.exports = [ + { + ignores: [ + "core/main/client/lib/**", + "modules/**", + "node_modules/**", + "extensions/admin_ui/media/javascript-min/**", + "**/*.min.js", + ], + }, + js.configs.recommended, + { + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + globals: { + ...globals.browser, + + // BeEF + beef: "readonly", + beef_init: "readonly", + + // jQuery + $: "readonly", + jQuery: "readonly", + $j: "readonly", + + // Libraries + MobileEsp: "readonly", + evercookie: "readonly", + swfobject: "readonly", + + // Browser-specific (old IE / old Firefox) + XDomainRequest: "readonly", + MozWebSocket: "readonly", + clipboardData: "readonly", + + // Debug + isDebug: "readonly", + }, + }, + }, +]; diff --git a/package-lock.json b/package-lock.json index 972a3242f..5736d91ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,19 +9,11 @@ "version": "0.6.0.0", "license": "GNU General Public License v2.0", "devDependencies": { - "eslint": "^7.32.0", - "jsdoc": "^4.0.5", - "jsdoc-to-markdown": "^9.1.3" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.10.4" + "@eslint/js": "10.0.1", + "eslint": "10.4.0", + "globals": "17.6.0", + "jsdoc": "4.0.5", + "jsdoc-to-markdown": "9.1.3" } }, "node_modules/@babel/helper-string-parser": { @@ -43,100 +35,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", - "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/parser": { "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz", @@ -166,50 +64,199 @@ "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" }, "engines": { - "node": ">=10.10.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", "dev": true, - "license": "BSD-3-Clause" + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@jsdoc/salty": { "version": "0.2.8", @@ -258,6 +305,27 @@ "node": ">= 8" } }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -281,9 +349,9 @@ "dev": true }, "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -304,9 +372,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -320,26 +388,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -370,23 +418,16 @@ "node": ">=12.17" } }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "18 || 20 || >=22" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -394,14 +435,16 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -437,16 +480,6 @@ } } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/catharsis": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", @@ -555,13 +588,6 @@ "node": ">=12.17" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/config-master": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/config-master/-/config-master-3.1.0.tgz", @@ -656,40 +682,6 @@ } } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -712,112 +704,91 @@ } }, "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.0.tgz", + "integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, + "license": "Apache-2.0", "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/escape-string-regexp": { @@ -833,49 +804,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "node": "^20.19.0 || ^22.13.0 || >=24" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -885,16 +848,6 @@ "node": ">=0.10" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -908,7 +861,7 @@ "node": ">=4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { + "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", @@ -918,16 +871,6 @@ "node": ">=4.0" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -975,23 +918,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -1002,16 +928,16 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/file-set": { @@ -1065,19 +991,35 @@ } } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { @@ -1087,42 +1029,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true, - "license": "MIT" - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -1136,16 +1042,13 @@ } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1189,32 +1092,15 @@ } }, "node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { "node": ">= 4" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -1225,25 +1111,6 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "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, - "license": "ISC" - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1253,16 +1120,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -1291,37 +1148,6 @@ "dev": true, "license": "ISC" }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/js-yaml/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, "node_modules/js2xmlparser": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", @@ -1497,6 +1323,22 @@ "uc.micro": "^2.0.0" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", @@ -1510,20 +1352,6 @@ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "license": "MIT" - }, "node_modules/markdown-it": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", @@ -1592,16 +1420,19 @@ } }, "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -1655,16 +1486,6 @@ "node": ">=8.0.0" } }, - "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, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -1683,27 +1504,46 @@ "node": ">= 0.8.0" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { - "callsites": "^3.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/path-key": { @@ -1716,13 +1556,6 @@ "node": ">=8" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, "node_modules/picomatch": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", @@ -1746,16 +1579,6 @@ "node": ">= 0.8.0" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -1795,29 +1618,6 @@ } ] }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/requizzle": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", @@ -1827,16 +1627,6 @@ "lodash": "^4.17.21" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -1847,23 +1637,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -1887,19 +1660,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -1923,24 +1683,6 @@ "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, "node_modules/sort-array": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-5.1.1.tgz", @@ -1972,41 +1714,6 @@ "node": ">=0.10.0" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -2031,23 +1738,6 @@ "node": ">=8" } }, - "node_modules/table": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", - "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/table-layout": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz", @@ -2061,37 +1751,6 @@ "node": ">=12.17" } }, - "node_modules/table/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -2126,19 +1785,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typical": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/typical/-/typical-7.2.0.tgz", @@ -2183,13 +1829,6 @@ "punycode": "^2.1.0" } }, - "node_modules/v8-compile-cache": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", - "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", - "dev": true, - "license": "MIT" - }, "node_modules/walk-back": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.1.tgz", @@ -2240,18 +1879,24 @@ "node": ">=12.17" } }, - "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, - "license": "ISC" - }, "node_modules/xmlcreate": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index e8c1ef461..083939500 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,17 @@ "description": "The Browser Exploitation Framework Project", "scripts": { "docs": "./node_modules/.bin/jsdoc -c conf.json", - "lint": "eslint", - "lint:fix": "eslint --fix" + "lint": "eslint .", + "lint:fix": "eslint --fix ." }, "author": "Wade Alcorn", "license": "GNU General Public License v2.0", "devDependencies": { - "eslint": "^7.32.0", - "jsdoc": "^4.0.5", - "jsdoc-to-markdown": "^9.1.3" + "@eslint/js": "10.0.1", + "eslint": "10.4.0", + "globals": "17.6.0", + "jsdoc": "4.0.5", + "jsdoc-to-markdown": "9.1.3" }, "dependencies": {} } From 793829cccdb3cc53a0178e71b72ef14911d3311a Mon Sep 17 00:00:00 2001 From: zinduolis Date: Tue, 19 May 2026 19:56:33 +1000 Subject: [PATCH 35/37] Fix: lint eslint.config.js with proper ecmaVersion --- eslint.config.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/eslint.config.js b/eslint.config.js index 762db77e2..44cee5d4f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -15,6 +15,8 @@ module.exports = [ }, js.configs.recommended, { + files: ["**/*.js"], + ignores: ["eslint.config.js"], languageOptions: { ecmaVersion: 5, sourceType: "script", @@ -45,4 +47,15 @@ module.exports = [ }, }, }, + { + files: ["eslint.config.js"], + languageOptions: { + ecmaVersion: "latest", + sourceType: "commonjs", + globals: { + module: "writable", + require: "readonly", + }, + }, + }, ]; From c7985cdf51bd6e8396575affe60deea7960ad859 Mon Sep 17 00:00:00 2001 From: zinduolis Date: Fri, 22 May 2026 15:54:11 +1000 Subject: [PATCH 36/37] TEST: remove autorun_engine specs (ARE removed in #3482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #3482 deleted the AutorunEngine source code (engine.rb, parser.rb, rule_loader.rb) along with the Rule and Execution models. The three new spec files added by this PR were testing classes that no longer exist on master, so they would fail at load time once #3482 merged. Also drop a leftover BeEF::Core::Models::Execution stub in hookedbrowsers_spec.rb — the handler no longer queries pending ARE executions, so the stub is dead. Removed: spec/beef/core/main/autorun_engine/engine_spec.rb (359 lines) spec/beef/core/main/autorun_engine/parser_spec.rb (119 lines) spec/beef/core/main/autorun_engine/rule_loader_spec.rb ( 83 lines) Modified: spec/beef/core/main/handlers/hookedbrowsers_spec.rb (-1 dead stub) --- .../core/main/autorun_engine/engine_spec.rb | 359 ------------------ .../core/main/autorun_engine/parser_spec.rb | 119 ------ .../main/autorun_engine/rule_loader_spec.rb | 83 ---- .../core/main/handlers/hookedbrowsers_spec.rb | 1 - 4 files changed, 562 deletions(-) delete mode 100644 spec/beef/core/main/autorun_engine/engine_spec.rb delete mode 100644 spec/beef/core/main/autorun_engine/parser_spec.rb delete mode 100644 spec/beef/core/main/autorun_engine/rule_loader_spec.rb diff --git a/spec/beef/core/main/autorun_engine/engine_spec.rb b/spec/beef/core/main/autorun_engine/engine_spec.rb deleted file mode 100644 index 357af25f7..000000000 --- a/spec/beef/core/main/autorun_engine/engine_spec.rb +++ /dev/null @@ -1,359 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# -# Example: unit specs for AutorunEngine::Engine using mocks instead of a real server/DB. -# - -require 'spec_helper' - -RSpec.describe BeEF::Core::AutorunEngine::Engine do - let(:engine) { described_class.instance } - let(:config) { BeEF::Core::Configuration.instance } - - before do - allow(engine).to receive(:print_debug) - allow(engine).to receive(:print_info) - allow(engine).to receive(:print_more) - allow(engine).to receive(:print_error) - end - - # Fake rule object (could be a double or a persisted Rule with minimal attributes) - def rule_with(browser: 'ALL', browser_version: 'ALL', os: 'ALL', os_version: 'ALL') - double( - 'Rule', - id: 1, - browser: browser, - browser_version: browser_version, - os: os, - os_version: os_version - ) - end - - describe '#zombie_matches_rule?' do - it 'returns false when rule is nil' do - expect(engine.zombie_matches_rule?('FF', '41', 'Windows', '7', nil)).to be false - end - - it 'returns true when rule is ALL for browser and OS' do - rule = rule_with(browser: 'ALL', browser_version: 'ALL', os: 'ALL', os_version: 'ALL') - allow(engine).to receive(:zombie_browser_matches_rule?).with('FF', '41', rule).and_return(true) - allow(engine).to receive(:zombie_os_matches_rule?).with('Windows', '7', rule).and_return(true) - expect(engine.zombie_matches_rule?('FF', '41', 'Windows', '7', rule)).to be true - end - - it 'returns false when browser does not match' do - rule = rule_with(browser: 'FF', browser_version: '>= 41', os: 'ALL', os_version: 'ALL') - allow(engine).to receive(:zombie_browser_matches_rule?).with('FF', '41', rule).and_return(false) - expect(engine.zombie_matches_rule?('FF', '41', 'Windows', '7', rule)).to be false - end - - it 'returns false when OS does not match' do - rule = rule_with(browser: 'ALL', browser_version: 'ALL', os: 'Windows', os_version: '7') - allow(engine).to receive(:zombie_browser_matches_rule?).with('FF', '41', rule).and_return(true) - allow(engine).to receive(:zombie_os_matches_rule?).with('Windows', '7', rule).and_return(false) - expect(engine.zombie_matches_rule?('FF', '41', 'Windows', '7', rule)).to be false - end - end - - describe '#zombie_os_matches_rule?' do - it 'returns false when rule is nil' do - expect(engine.zombie_os_matches_rule?('Windows', '7', nil)).to be false - end - - it 'returns true when rule os is ALL' do - rule = double('Rule', os: 'ALL', os_version: 'ALL') - expect(engine.zombie_os_matches_rule?('Windows', '7', rule)).to be true - end - - it 'returns false when hook os does not match rule os' do - rule = double('Rule', os: 'Linux', os_version: 'ALL') - expect(engine.zombie_os_matches_rule?('Windows', '7', rule)).to be false - end - - it 'returns true when rule os matches and os_version is ALL' do - rule = double('Rule', os: 'Windows', os_version: 'ALL') - expect(engine.zombie_os_matches_rule?('Windows', '7', rule)).to be true - end - end - - describe '#zombie_browser_matches_rule?' do - it 'returns false when rule is nil' do - expect(engine.zombie_browser_matches_rule?('FF', '41', nil)).to be false - end - - it 'returns true when rule browser is ALL and version is ALL' do - rule = double('Rule', browser: 'ALL', browser_version: 'ALL') - expect(engine.zombie_browser_matches_rule?('FF', '41', rule)).to be true - end - - it 'returns true when rule browser matches and version is ALL' do - rule = double('Rule', browser: 'FF', browser_version: 'ALL') - expect(engine.zombie_browser_matches_rule?('FF', '41', rule)).to be true - end - - it 'returns false when rule browser does not match' do - rule = double('Rule', browser: 'IE', browser_version: 'ALL') - expect(engine.zombie_browser_matches_rule?('FF', '41', rule)).to be false - end - end - - describe '#find_matching_rules_for_zombie' do - it 'returns nil when no rules exist' do - allow(BeEF::Core::Models::Rule).to receive(:all).and_return([]) - expect(engine.find_matching_rules_for_zombie('FF', '41', 'Windows', '7')).to be_nil - end - - it 'returns matching rule ids when rules match zombie' do - rule1 = double('Rule', id: 1, name: 'Rule1', browser: 'ALL', browser_version: 'ALL', os: 'ALL', os_version: 'ALL') - rule2 = double('Rule', id: 2, name: 'Rule2', browser: 'IE', browser_version: 'ALL', os: 'ALL', os_version: 'ALL') - allow(BeEF::Core::Models::Rule).to receive(:all).and_return([rule1, rule2]) - allow(engine).to receive(:zombie_matches_rule?).with('FF', '41', 'Windows', '7', rule1).and_return(true) - allow(engine).to receive(:zombie_matches_rule?).with('FF', '41', 'Windows', '7', rule2).and_return(false) - expect(engine.find_matching_rules_for_zombie('FF', '41', 'Windows', '7')).to eq([1]) - end - end - - describe '#compare_versions' do - it 'returns true when cond is ALL' do - expect(engine.send(:compare_versions, '7', 'ALL', '8')).to be true - end - - it 'returns true when cond is == and versions equal' do - expect(engine.send(:compare_versions, '41', '==', '41')).to be true - end - - it 'returns false when cond is == and versions differ' do - expect(engine.send(:compare_versions, '41', '==', '42')).to be false - end - - it 'returns true when cond is <= and ver_a <= ver_b' do - expect(engine.send(:compare_versions, '41', '<=', '42')).to be true - expect(engine.send(:compare_versions, '41', '<=', '41')).to be true - end - - it 'returns false when cond is <= and ver_a > ver_b' do - expect(engine.send(:compare_versions, '42', '<=', '41')).to be false - end - - it 'returns true when cond is < and ver_a < ver_b' do - expect(engine.send(:compare_versions, '41', '<', '42')).to be true - end - - it 'returns false when cond is < and ver_a >= ver_b' do - expect(engine.send(:compare_versions, '42', '<', '41')).to be false - expect(engine.send(:compare_versions, '41', '<', '41')).to be false - end - - it 'returns true when cond is >= and ver_a >= ver_b' do - expect(engine.send(:compare_versions, '42', '>=', '41')).to be true - expect(engine.send(:compare_versions, '41', '>=', '41')).to be true - end - - it 'returns true when cond is > and ver_a > ver_b' do - expect(engine.send(:compare_versions, '42', '>', '41')).to be true - end - - it 'returns false when cond is > and ver_a <= ver_b' do - expect(engine.send(:compare_versions, '41', '>', '42')).to be false - expect(engine.send(:compare_versions, '41', '>', '41')).to be false - end - - it 'returns false for unknown cond' do - expect(engine.send(:compare_versions, '41', '!=', '42')).to be false - end - end - - describe '#clean_command_body' do - it 'extracts body range and replaces single-quoted mod_input when replace_input is true' do - body = "beef.execute(function(){\n alert('<>');\n});\n" - result = engine.send(:clean_command_body, body, true) - expect(result).to include('alert(mod_input)') - expect(result).to include('beef.execute(function(){') - end - - it 'returns cleaned body without mod_input replacement when replace_input is false' do - body = "beef.execute(function(){\n doSomething('<>');\n});\n" - result = engine.send(:clean_command_body, body, false) - expect(result).to include('<>') - end - - it 'replaces double-quoted <> with mod_input when replace_input is true' do - body = "beef.execute(function(){\n x(\"<>\");\n});\n" - result = engine.send(:clean_command_body, body, true) - expect(result).to include('mod_input') - expect(result).not_to include('"<>"') - end - - it 'replaces single-quoted <> with mod_input when replace_input is true' do - body = "beef.execute(function(){\n x('<>');\n});\n" - result = engine.send(:clean_command_body, body, true) - expect(result).to include('mod_input') - end - end - - describe '#prepare_sequential_wrapper' do - it 'builds wrapper with mod bodies and setTimeout calls in order' do - mods = [ - { mod_name: 'mod_a', mod_body: 'var mod_a_mod_output = 1;' }, - { mod_name: 'mod_b', mod_body: 'var mod_b_mod_output = 2;' } - ] - order = [0, 1] - delay = [0, 500] - token = 't1' - result = engine.send(:prepare_sequential_wrapper, mods, order, delay, token) - expect(result).to include('mod_a_t1') - expect(result).to include('mod_b_t1') - expect(result).to include('setTimeout(function(){mod_a_t1();}, 0)') - expect(result).to include('setTimeout(function(){mod_b_t1();}, 500)') - expect(result).to include('mod_a_t1_mod_output') - expect(result).to include('mod_b_t1_mod_output') - end - - it 'handles single module' do - mods = [{ mod_name: 'single', mod_body: 'x();' }] - order = [0] - delay = [0] - result = engine.send(:prepare_sequential_wrapper, mods, order, delay, 'tk') - expect(result).to include('single_tk') - expect(result).to include('setTimeout(function(){single_tk();}, 0)') - end - end - - describe '#prepare_nested_forward_wrapper' do - it 'builds wrapper for single module' do - mods = [{ mod_name: 'only', mod_body: 'only();' }] - code = ['null'] - conditions = [true] - order = [0] - token = 'nf1' - result = engine.send(:prepare_nested_forward_wrapper, mods, code, conditions, order, token) - expect(result).to include('only_nf1') - expect(result).to include('only_nf1_f') - expect(result).to include('only_nf1_mod_output') - end - end - - describe '#find_and_run_all_matching_rules_for_zombie' do - it 'returns without calling run_rules when hb_id is nil' do - expect(engine).not_to receive(:run_rules_on_zombie) - engine.find_and_run_all_matching_rules_for_zombie(nil) - end - - it 'returns without calling run_rules when find_matching_rules returns nil' do - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7') - allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return(nil) - expect(engine).not_to receive(:run_rules_on_zombie) - engine.find_and_run_all_matching_rules_for_zombie(1) - end - - it 'returns without calling run_rules when find_matching_rules returns empty' do - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7') - allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return([]) - expect(engine).not_to receive(:run_rules_on_zombie) - engine.find_and_run_all_matching_rules_for_zombie(1) - end - - it 'calls run_rules_on_zombie with matching rule ids when rules match' do - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7') - allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return([1, 2]) - expect(engine).to receive(:run_rules_on_zombie).with([1, 2], 1) - engine.find_and_run_all_matching_rules_for_zombie(1) - end - end - - describe '#run_matching_rules_on_zombie' do - it 'returns when rule_ids is nil' do - expect(engine).not_to receive(:run_rules_on_zombie) - engine.run_matching_rules_on_zombie(nil, 1) - end - - it 'returns when hb_id is nil' do - expect(engine).not_to receive(:run_rules_on_zombie) - engine.run_matching_rules_on_zombie([1], nil) - end - - it 'returns without calling run_rules when find_matching_rules returns nil' do - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7') - allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return(nil) - expect(engine).not_to receive(:run_rules_on_zombie) - engine.run_matching_rules_on_zombie([1], 1) - end - - it 'calls run_rules_on_zombie with intersection of rule_ids and matching rules' do - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7') - allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return([1, 2]) - expect(engine).to receive(:run_rules_on_zombie).with([1], 1) - engine.run_matching_rules_on_zombie([1], 1) - end - - it 'does not call run_rules_on_zombie when no rule_ids overlap matching rules' do - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.name').and_return('FF') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'browser.version').and_return('41') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.name').and_return('Windows') - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).with(1, 'host.os.version').and_return('7') - allow(engine).to receive(:find_matching_rules_for_zombie).with('FF', '41', 'Windows', '7').and_return([1, 2]) - expect(engine).not_to receive(:run_rules_on_zombie) - engine.run_matching_rules_on_zombie([99], 1) - end - end - - describe '#run_rules_on_zombie' do - it 'returns when rule_ids is nil' do - expect(BeEF::HBManager).not_to receive(:get_by_id) - engine.run_rules_on_zombie(nil, 1) - end - - it 'returns when hb_id is nil' do - expect(BeEF::HBManager).not_to receive(:get_by_id) - engine.run_rules_on_zombie([1], nil) - end - - it 'normalizes single Integer rule_id to array and processes rule' do - hb = double('HookedBrowser', session: 'sess1') - allow(BeEF::HBManager).to receive(:get_by_id).with(1).and_return(hb) - rule = double( - 'Rule', - modules: '[]', - execution_order: '[]', - execution_delay: '[]', - chain_mode: 'invalid' - ) - allow(BeEF::Core::Models::Rule).to receive(:find).with(1).and_return(rule) - engine.run_rules_on_zombie(1, 1) - expect(BeEF::Core::Models::Rule).to have_received(:find).with(1) - expect(engine).to have_received(:print_error).with(/Invalid chain mode 'invalid'/) - end - - it 'prints error and returns when rule has invalid chain_mode' do - hb = double('HookedBrowser', session: 'sess1') - allow(BeEF::HBManager).to receive(:get_by_id).with(1).and_return(hb) - rule = double( - 'Rule', - modules: '[]', - execution_order: '[]', - execution_delay: '[]', - chain_mode: 'invalid' - ) - allow(BeEF::Core::Models::Rule).to receive(:find).with(1).and_return(rule) - engine.run_rules_on_zombie([1], 1) - expect(engine).to have_received(:print_error).with(/Invalid chain mode 'invalid'/) - end - end -end diff --git a/spec/beef/core/main/autorun_engine/parser_spec.rb b/spec/beef/core/main/autorun_engine/parser_spec.rb deleted file mode 100644 index 9ab37d85a..000000000 --- a/spec/beef/core/main/autorun_engine/parser_spec.rb +++ /dev/null @@ -1,119 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require 'spec_helper' - -RSpec.describe BeEF::Core::AutorunEngine::Parser do - let(:parser) { described_class.instance } - - def valid_minimal_args - { - name: 'Test Rule', - author: 'Test Author', - browser: 'ALL', - browser_version: 'ALL', - os: 'Windows', - os_version: 'ALL', - modules: [], - execution_order: [], - execution_delay: [], - chain_mode: 'sequential' - } - end - - describe '#parse' do - it 'returns true for valid minimal args (empty modules)' do - result = parser.parse( - valid_minimal_args[:name], - valid_minimal_args[:author], - valid_minimal_args[:browser], - valid_minimal_args[:browser_version], - valid_minimal_args[:os], - valid_minimal_args[:os_version], - valid_minimal_args[:modules], - valid_minimal_args[:execution_order], - valid_minimal_args[:execution_delay], - valid_minimal_args[:chain_mode] - ) - - expect(result).to be true - end - - it 'raises ArgumentError for empty name' do - expect do - parser.parse('', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'sequential') - end.to raise_error(ArgumentError, /Invalid rule name/) - end - - it 'raises ArgumentError for nil name' do - expect do - parser.parse(nil, 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'sequential') - end.to raise_error(ArgumentError, /Invalid rule name/) - end - - it 'raises ArgumentError for empty author' do - expect do - parser.parse('Name', '', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'sequential') - end.to raise_error(ArgumentError, /Invalid author name/) - end - - it 'raises ArgumentError for invalid chain_mode' do - expect do - parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'invalid') - end.to raise_error(ArgumentError, /Invalid chain_mode definition/) - end - - it 'raises ArgumentError for invalid os' do - expect do - parser.parse('Name', 'Author', 'ALL', 'ALL', 'InvalidOS', 'ALL', [], [], [], 'sequential') - end.to raise_error(ArgumentError, /Invalid os definition/) - end - - it 'raises ArgumentError when execution_delay size does not match modules size' do - expect do - parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [{ 'name' => 'a' }], [1], [], 'sequential') - end.to raise_error(ArgumentError, /execution_delay.*consistent with number of modules/) - end - - it 'raises ArgumentError when execution_order size does not match modules size' do - expect do - parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [{ 'name' => 'a' }], [], [0], 'sequential') - end.to raise_error(ArgumentError, /execution_order.*consistent with number of modules/) - end - - it 'raises TypeError when execution_delay contains non-Integer' do - # Use one module so sizes match; then type check runs on execution_delay - expect do - parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [{}], [1], ['not_an_int'], 'sequential') - end.to raise_error(TypeError, /execution_delay.*Integers/) - end - - it 'raises TypeError when execution_order contains non-Integer' do - # Use one module so sizes match; then type check runs on execution_order - expect do - parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [{}], ['x'], [0], 'sequential') - end.to raise_error(TypeError, /execution_order.*Integers/) - end - - it 'raises ArgumentError for invalid browser' do - expect do - parser.parse('Name', 'Author', 'XX', 'ALL', 'Windows', 'ALL', [], [], [], 'sequential') - end.to raise_error(ArgumentError, /Invalid browser definition/) - end - - it 'accepts nested-forward as chain_mode' do - result = parser.parse('Name', 'Author', 'ALL', 'ALL', 'Windows', 'ALL', [], [], [], 'nested-forward') - expect(result).to be true - end - - it 'accepts valid os values' do - %w[Linux Windows OSX Android iOS BlackBerry ALL].each do |os| - result = parser.parse('Name', 'Author', 'ALL', 'ALL', os, 'ALL', [], [], [], 'sequential') - expect(result).to be true - end - end - end -end diff --git a/spec/beef/core/main/autorun_engine/rule_loader_spec.rb b/spec/beef/core/main/autorun_engine/rule_loader_spec.rb deleted file mode 100644 index a27560b68..000000000 --- a/spec/beef/core/main/autorun_engine/rule_loader_spec.rb +++ /dev/null @@ -1,83 +0,0 @@ -# -# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - https://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -require 'spec_helper' - -RSpec.describe BeEF::Core::AutorunEngine::RuleLoader do - let(:loader) { described_class.instance } - - def valid_rule_data - { - 'name' => 'Test Rule', - 'author' => 'Test Author', - 'browser' => 'ALL', - 'browser_version' => 'ALL', - 'os' => 'Windows', - 'os_version' => 'ALL', - 'modules' => [], - 'execution_order' => [], - 'execution_delay' => [], - 'chain_mode' => 'sequential' - } - end - - before do - allow(loader).to receive(:print_error) - allow(loader).to receive(:print_info) - allow(loader).to receive(:print_more) - end - - describe '#load_rule_json' do - it 'returns success and rule_id when parse succeeds and rule is new' do - # Parser will succeed with valid minimal data; no existing rule - result = loader.load_rule_json(valid_rule_data) - - expect(result['success']).to be true - expect(result).to have_key('rule_id') - expect(result['rule_id']).to be_a(Integer) - end - - it 'returns success false and error when parse raises' do - allow(BeEF::Core::AutorunEngine::Parser.instance).to receive(:parse).and_raise(ArgumentError.new('Invalid rule name')) - - result = loader.load_rule_json(valid_rule_data.merge('name' => 'x')) - - expect(result['success']).to be false - expect(result['error']).to include('Invalid rule name') - end - - it 'returns success false and error when rule already exists' do - # Create the rule first so it already exists - BeEF::Core::Models::Rule.create!( - name: 'Duplicate Rule', - author: 'Test Author', - browser: 'ALL', - browser_version: 'ALL', - os: 'Windows', - os_version: 'ALL', - modules: [].to_json, - execution_order: [].to_s, - execution_delay: [].to_s, - chain_mode: 'sequential' - ) - - result = loader.load_rule_json( - valid_rule_data.merge('name' => 'Duplicate Rule') - ) - - expect(result['success']).to be false - expect(result['error']).to include('Duplicate rule already exists') - end - - it 'uses default chain_mode sequential when missing' do - data = valid_rule_data.except('chain_mode') - result = loader.load_rule_json(data) - - expect(result['success']).to be true - expect(result).to have_key('rule_id') - end - end -end diff --git a/spec/beef/core/main/handlers/hookedbrowsers_spec.rb b/spec/beef/core/main/handlers/hookedbrowsers_spec.rb index aae0d90f0..d3953f6c8 100644 --- a/spec/beef/core/main/handlers/hookedbrowsers_spec.rb +++ b/spec/beef/core/main/handlers/hookedbrowsers_spec.rb @@ -111,7 +111,6 @@ RSpec.describe BeEF::Core::Handlers::HookedBrowsers do relation = double('Relation', first: hooked_browser) allow(BeEF::Core::Models::HookedBrowser).to receive(:where).with(session: 'existing_session').and_return(relation) allow(BeEF::Core::Models::Command).to receive(:where).with(hooked_browser_id: 1, instructions_sent: false).and_return([]) - allow(BeEF::Core::Models::Execution).to receive(:where).with(is_sent: false, session_id: 'existing_session').and_return([]) allow(BeEF::API::Registrar.instance).to receive(:fire) end From 36b41e1aef642808df439879766c0ff4d9bd615e Mon Sep 17 00:00:00 2001 From: zinduolis Date: Mon, 25 May 2026 15:08:14 +1000 Subject: [PATCH 37/37] FIX: revert accidental PDF mutation; stub File.open in Hook_default_browser pre_send; consolidate BRANCH_COVERAGE into BeefTestConfig; replace 'next unless' guards with describe-level conditionals; per-example stub_const for extension constants; correct has_valid_browser_details_chars? assertions --- .gitignore | 6 + .rspec | 3 +- Rakefile | 4 +- .../bounce_to_ie_configured.pdf | 2 +- spec/README.md | 28 +- spec/beef/core/filter/base_spec.rb | 12 +- spec/beef/modules/browser/browser_spec.rb | 116 ++++---- .../chrome_extensions_spec.rb | 69 +++-- spec/beef/modules/debug/debug_spec.rb | 75 +++-- spec/beef/modules/exploits/exploits_spec.rb | 134 +++++---- spec/beef/modules/host/host_spec.rb | 198 +++++++------ spec/beef/modules/ipec/ipec_spec.rb | 143 +++++----- spec/beef/modules/misc/misc_spec.rb | 217 +++++++-------- spec/beef/modules/network/network_spec.rb | 260 ++++++++---------- .../modules/persistence/persistence_spec.rb | 79 +++--- spec/beef/modules/phonegap/phonegap_spec.rb | 71 ++--- .../social_engineering_spec.rb | 133 +++++---- spec/spec_helper.rb | 106 ++++++- spec/support/module_spec_helper.rb | 3 +- 19 files changed, 886 insertions(+), 773 deletions(-) diff --git a/.gitignore b/.gitignore index 64282a1c5..c261e2701 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,12 @@ beef.log test/msf-test extensions/admin_ui/media/javascript-min/ custom-config.yaml + +# Generated at runtime by Hook_default_browser#pre_send +modules/host/hook_default_browser/bounce_to_ie_configured.pdf + +# Generated at runtime by browser/spyder_eye#post_execute (and possibly other capture modules) +screenshot_*.png .DS_Store .gitignore .rvmrc diff --git a/.rspec b/.rspec index 9e7a5ec96..7fe3cb532 100644 --- a/.rspec +++ b/.rspec @@ -2,6 +2,5 @@ --color --require spec_helper -I . ---order defined --tag ~run_on_browserstack ---tag ~run_on_long_tests \ No newline at end of file +--tag ~run_on_long_tests diff --git a/Rakefile b/Rakefile index 2709cd268..9b25479d6 100644 --- a/Rakefile +++ b/Rakefile @@ -8,13 +8,13 @@ require 'rspec/core/rake_task' task default: ['short'] # Run rspec with an explicit file list (avoids envs that only run 557). -# Note: when run with all 81 files, module specs load after extensions; the Dns stub in +# Note: when run with the full file list, module specs load after extensions; the Dns stub in # network_spec must not run before the real Dns extension (dns_spec) or you get "superclass mismatch". desc 'Run short spec suite (all specs except browserstack/long)' task :short do short_files = Dir[File.join(Dir.pwd, 'spec', '**', '*_spec.rb')].sort $stderr.puts "[rake short] spec files=#{short_files.size}" - abort '[rake short] Expected 81+ spec files; check you are in project root.' if short_files.size < 80 + abort '[rake short] Expected 60+ spec files; check you are in project root.' if short_files.size < 60 opts = [ '--tag', '~run_on_browserstack', '--tag', '~run_on_long_tests' diff --git a/modules/host/hook_default_browser/bounce_to_ie_configured.pdf b/modules/host/hook_default_browser/bounce_to_ie_configured.pdf index 372b6e4d7..21549a4b0 100644 --- a/modules/host/hook_default_browser/bounce_to_ie_configured.pdf +++ b/modules/host/hook_default_browser/bounce_to_ie_configured.pdf @@ -46,7 +46,7 @@ endobj << /Length 1708 >>stream -app.launchURL("http://127.0.0.1:127.0.0.1/demos/report.html",true); +app.launchURL("http://0.0.0.0:3000/demos/report.html",true); endstream endobj 7 0 obj diff --git a/spec/README.md b/spec/README.md index f6b1bbdee..2a524a6cd 100644 --- a/spec/README.md +++ b/spec/README.md @@ -78,9 +78,13 @@ bundle exec rake coverage_complete ## Key Improvements -### ✅ **Eliminated Global Constants** -- Replaced `BRANCH_COVERAGE` constants with centralized `BeefTestConfig` module -- No more "already initialized constant" warnings +### ✅ **Eliminated Global Constant Collisions** +- Branch-coverage data for the dynamic module spec loaders lives in + `BeefTestConfig.branch_coverage_for(:component)` (in `spec/spec_helper.rb`). +- Each module spec file pulls its own component (`:browser`, `:exploits`, `:host`, + `:misc`, `:network`, `:social_engineering`) into a uniquely-named local + constant (e.g. `BRANCH_COVERAGE_HOST`), so loading the full suite no longer + triggers "already initialized constant" warnings or silently overwrites data. ### ✅ **Simplified Coverage Logic** - Cleaner filtering using `track_files` instead of complex `add_filter` logic @@ -101,6 +105,24 @@ bundle exec rake coverage_complete - Follows RSpec best practices - Better separation of concerns +## Module Spec Pattern (dynamic loaders) + +Specs under `spec/beef/modules/**/_spec.rb` walk every +`modules//**/module.rb` file and generate a `RSpec.describe` block per +class. They cover: + +- `.options` returns an `Array` (when defined) +- `#pre_send` runs without raising (when defined) +- `#post_execute` runs without raising (when defined) +- An extra `#post_execute` example with a realistic datastore for any module + listed in `BeefTestConfig.branch_coverage_for(:category)` + +Trade-off: this is a coverage-driving baseline. The `expect { ... }.not_to +raise_error` assertion catches load failures, missing constants, nil crashes, +and undefined methods, but it does not assert behavioural correctness. Add +targeted assertions for high-value modules (see the `Wordpress_add_user` and +`Test_get_variable` examples in `spec/beef/modules/misc/misc_spec.rb`). + ## Coverage Focus Areas - **core**: Framework core functionality diff --git a/spec/beef/core/filter/base_spec.rb b/spec/beef/core/filter/base_spec.rb index 04275ecc8..d818bf9e4 100644 --- a/spec/beef/core/filter/base_spec.rb +++ b/spec/beef/core/filter/base_spec.rb @@ -400,14 +400,14 @@ RSpec.describe BeEF::Filters do expect(BeEF::Filters.has_valid_browser_details_chars?('')).to be(false) end - it 'returns false when string only has allowed chars' do - # Method returns true when regex matches (invalid char found); false when only valid chars - expect(BeEF::Filters.has_valid_browser_details_chars?('abc')).to be(false) - expect(BeEF::Filters.has_valid_browser_details_chars?('a-b (c)')).to be(false) + it 'returns true when string only has allowed chars' do + # Method returns nil-on-no-match: nil.nil? == true, so a fully-valid string returns true. + expect(BeEF::Filters.has_valid_browser_details_chars?('abc')).to be(true) + expect(BeEF::Filters.has_valid_browser_details_chars?('a-b (c)')).to be(true) end - it 'returns true when string contains disallowed character' do - expect(BeEF::Filters.has_valid_browser_details_chars?('ab@c')).to be(true) + it 'returns false when string contains disallowed character' do + expect(BeEF::Filters.has_valid_browser_details_chars?('ab@c')).to be(false) end end diff --git a/spec/beef/modules/browser/browser_spec.rb b/spec/beef/modules/browser/browser_spec.rb index 319457051..df19163c2 100644 --- a/spec/beef/modules/browser/browser_spec.rb +++ b/spec/beef/modules/browser/browser_spec.rb @@ -12,8 +12,8 @@ require_relative '../../../spec_helper' project_root = File.expand_path('../../../../', __dir__) browser_module_paths = Dir[File.join(project_root, 'modules/browser/**/module.rb')].sort -# Load branch coverage data from centralized config -BRANCH_COVERAGE = BeefTestConfig.branch_coverage_for(:browser) +# Load branch coverage data from centralised config +BRANCH_COVERAGE_BROWSER = BeefTestConfig.branch_coverage_for(:browser) browser_module_paths.each do |path| rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') @@ -27,69 +27,75 @@ browser_module_paths.each do |path| mod = Object.const_get(klass_name) RSpec.describe mod do - describe '.options' do - it 'returns an Array when defined' do - next unless described_class.respond_to?(:options) + if described_class.respond_to?(:options) + describe '.options' do + it 'returns an Array' do + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:beef_proto).and_return('http') - allow(config).to receive(:beef_port).and_return('3000') - allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000') - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - - opts = described_class.options - expect(opts).to be_an(Array) + expect(described_class.options).to be_an(Array) + end end end - describe '#pre_send' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:pre_send) - - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind).and_return(nil) - allow(handler).to receive(:bind).and_return(nil) - allow(handler).to receive(:bind_raw).and_return(nil) - allow(handler).to receive(:remap).and_return(nil) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') - expect { run_pre_send(instance) }.not_to raise_error + if described_class.method_defined?(:pre_send) + describe '#pre_send' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_pre_send(instance) }.not_to raise_error + end end end - describe '#post_execute' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:post_execute) + if described_class.method_defined?(:post_execute) + describe '#post_execute' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(handler).to receive(:bind_raw) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + # Some modules (e.g. Spyder_eye) write screenshot files to $home_dir on the success path. + # Stub File.open with a block-aware double so the test never touches the real filesystem. + file_double = double('File').as_null_object + allow(File).to receive(:open) do |*_args, &block| + block ? block.call(file_double) : file_double + end - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(handler).to receive(:remap) - allow(handler).to receive(:bind_raw) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end - instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') - expect { run_post_execute(instance) }.not_to raise_error - end + if BRANCH_COVERAGE_BROWSER[branch_key] + it 'runs branch path with realistic datastore' do + branch = BRANCH_COVERAGE_BROWSER[branch_key] - it 'runs branch path when in BRANCH_COVERAGE' do - branch = BRANCH_COVERAGE[branch_key] - next unless described_class.method_defined?(:post_execute) && branch - - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(handler).to receive(:remap) - allow(handler).to receive(:bind_raw) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - allow(BeEF::Core::Models::BrowserDetails).to receive(:set) - instance = build_command_instance(described_class, branch[:datastore]) - expect { run_post_execute(instance) }.not_to raise_error + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(handler).to receive(:bind_raw) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + instance = build_command_instance(described_class, branch[:datastore]) + expect { run_post_execute(instance) }.not_to raise_error + end + end end end end diff --git a/spec/beef/modules/chrome_extensions/chrome_extensions_spec.rb b/spec/beef/modules/chrome_extensions/chrome_extensions_spec.rb index f6a74c149..9f19b73b2 100644 --- a/spec/beef/modules/chrome_extensions/chrome_extensions_spec.rb +++ b/spec/beef/modules/chrome_extensions/chrome_extensions_spec.rb @@ -23,49 +23,48 @@ chrome_module_paths.each do |path| mod = Object.const_get(klass_name) RSpec.describe mod do - describe '.options' do - it 'returns an Array when defined' do - next unless described_class.respond_to?(:options) + if described_class.respond_to?(:options) + describe '.options' do + it 'returns an Array' do + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - - opts = described_class.options - expect(opts).to be_an(Array) + expect(described_class.options).to be_an(Array) + end end end - describe '#pre_send' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:pre_send) - - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind).and_return(nil) - allow(handler).to receive(:bind).and_return(nil) - allow(handler).to receive(:bind_raw).and_return(nil) - allow(handler).to receive(:remap).and_return(nil) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - instance = build_command_instance(described_class, 'return' => '', 'result' => '', 'cid' => '0') - expect { run_pre_send(instance) }.not_to raise_error + if described_class.method_defined?(:pre_send) + describe '#pre_send' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, 'return' => '', 'result' => '', 'cid' => '0') + expect { run_pre_send(instance) }.not_to raise_error + end end end - describe '#post_execute' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:post_execute) + if described_class.method_defined?(:post_execute) + describe '#post_execute' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - - instance = build_command_instance(described_class, 'return' => '', 'result' => '', 'cid' => '0') - expect { run_post_execute(instance) }.not_to raise_error + instance = build_command_instance(described_class, 'return' => '', 'result' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end end end end diff --git a/spec/beef/modules/debug/debug_spec.rb b/spec/beef/modules/debug/debug_spec.rb index 722d8e7d9..003481c50 100644 --- a/spec/beef/modules/debug/debug_spec.rb +++ b/spec/beef/modules/debug/debug_spec.rb @@ -24,52 +24,51 @@ debug_module_paths.each do |path| mod = Object.const_get(klass_name) RSpec.describe mod do - describe '.options' do - it 'returns an Array when defined' do - next unless described_class.respond_to?(:options) + if described_class.respond_to?(:options) + describe '.options' do + it 'returns an Array' do + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:beef_port).and_return('3000') - allow(config).to receive(:beef_proto).and_return('http') - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - - opts = described_class.options - expect(opts).to be_an(Array) + expect(described_class.options).to be_an(Array) + end end end - describe '#pre_send' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:pre_send) - - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind).and_return(nil) - allow(handler).to receive(:bind).and_return(nil) - allow(handler).to receive(:bind_raw).and_return(nil) - allow(handler).to receive(:bind_redirect).with(anything, anything).and_return(nil) - allow(handler).to receive(:remap).and_return(nil) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') - expect { run_pre_send(instance) }.not_to raise_error + if described_class.method_defined?(:pre_send) + describe '#pre_send' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:bind_redirect).with(anything, anything).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_pre_send(instance) }.not_to raise_error + end end end - describe '#post_execute' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:post_execute) + if described_class.method_defined?(:post_execute) + describe '#post_execute' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - - instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') - expect { run_post_execute(instance) }.not_to raise_error + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end end end end diff --git a/spec/beef/modules/exploits/exploits_spec.rb b/spec/beef/modules/exploits/exploits_spec.rb index b94953986..2690be841 100644 --- a/spec/beef/modules/exploits/exploits_spec.rb +++ b/spec/beef/modules/exploits/exploits_spec.rb @@ -4,7 +4,8 @@ # See the file 'doc/COPYING' for copying permission # # Unit tests for every module under modules/exploits/. -# Branch coverage: extra post_execute for modules with conditional branches. +# Branch coverage: extra post_execute for modules with conditional branches +# (driven by BeefTestConfig.branch_coverage_for(:exploits) in spec_helper.rb). # require_relative '../../../spec_helper' @@ -12,16 +13,7 @@ require_relative '../../../spec_helper' project_root = File.expand_path('../../../../', __dir__) exploit_module_paths = Dir[File.join(project_root, 'modules/exploits/**/module.rb')].sort -BRANCH_COVERAGE = { - 'modules/exploits/vtiger_crm_upload_exploit' => { datastore: { 'result' => 'ok', 'cid' => '0' } }, - 'modules/exploits/router/asus_rt_n12e_get_info' => { - datastore: { - 'result' => '', 'results' => 'ip=192.168.1.1&clients=192.168.1.2,00:11:22:33:44:55&wanip=1.2.3.4&netmask=255.255.255.0&gateway=192.168.1.1&dns=8.8.8.8 8.8.4.4', - 'beefhook' => '1', 'cid' => '0' - }, - config_get: { 'beef.extension.network.enable' => true } - } -}.freeze +BRANCH_COVERAGE_EXPLOITS = BeefTestConfig.branch_coverage_for(:exploits) exploit_module_paths.each do |path| rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') @@ -35,77 +27,83 @@ exploit_module_paths.each do |path| mod = Object.const_get(klass_name) RSpec.describe mod do - describe '.options' do - it 'returns an Array when defined' do - next unless described_class.respond_to?(:options) + if described_class.respond_to?(:options) + describe '.options' do + it 'returns an Array' do + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - - opts = described_class.options - expect(opts).to be_an(Array) + expect(described_class.options).to be_an(Array) + end end end - describe '#pre_send' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:pre_send) - - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind).and_return(nil) - allow(handler).to receive(:bind).and_return(nil) - allow(handler).to receive(:bind_raw).and_return(nil) - allow(handler).to receive(:remap).and_return(nil) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - instance = build_command_instance(described_class, {}) - expect { run_pre_send(instance) }.not_to raise_error + if described_class.method_defined?(:pre_send) + describe '#pre_send' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, {}) + expect { run_pre_send(instance) }.not_to raise_error + end end end - describe '#post_execute' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:post_execute) - - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(handler).to receive(:remap) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - - instance = build_command_instance(described_class, {}) - expect { run_post_execute(instance) }.not_to raise_error - end - - it 'runs branch path when in BRANCH_COVERAGE' do - raw = BRANCH_COVERAGE[branch_key] - next unless described_class.method_defined?(:post_execute) && raw - - branches = raw.is_a?(Array) ? raw : [raw] - branches.each do |branch| + if described_class.method_defined?(:post_execute) + describe '#post_execute' do + it 'runs without error' do handler = instance_double('AssetHandler') allow(handler).to receive(:unbind) allow(handler).to receive(:bind) allow(handler).to receive(:remap) allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - allow(BeEF::Core::Models::NetworkService).to receive(:create) - allow(BeEF::Core::Models::NetworkHost).to receive(:create) - allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) - if branch[:config_get] - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:get).with(anything) do |key| - branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1' - end - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - end - instance = build_command_instance(described_class, branch[:datastore]) + + instance = build_command_instance(described_class, {}) expect { run_post_execute(instance) }.not_to raise_error end + + if BRANCH_COVERAGE_EXPLOITS[branch_key] + it 'runs branch path with realistic datastore' do + raw = BRANCH_COVERAGE_EXPLOITS[branch_key] + branches = raw.is_a?(Array) ? raw : [raw] + + # NetworkService / NetworkHost belong to the network extension and may not be + # loaded during a modules-only spec run. Provide class doubles per-example + # so `allow(...).to receive(:create)` resolves regardless. + stub_const('BeEF::Core::Models::NetworkService', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkService) + stub_const('BeEF::Core::Models::NetworkHost', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkHost) + + branches.each do |branch| + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + allow(BeEF::Core::Models::NetworkService).to receive(:create) + allow(BeEF::Core::Models::NetworkHost).to receive(:create) + allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) + if branch[:config_get] + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:get).with(anything) do |key| + branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1' + end + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + end + instance = build_command_instance(described_class, branch[:datastore]) + expect { run_post_execute(instance) }.not_to raise_error + end + end + end end end end diff --git a/spec/beef/modules/host/host_spec.rb b/spec/beef/modules/host/host_spec.rb index f75dc71e7..9b865315e 100644 --- a/spec/beef/modules/host/host_spec.rb +++ b/spec/beef/modules/host/host_spec.rb @@ -5,7 +5,8 @@ # # Unit tests for every module under modules/host/. # Each directory is tested for .options (when present), #pre_send, and #post_execute. -# Branch coverage: extra post_execute runs for modules listed in BRANCH_COVERAGE. +# Branch coverage: extra post_execute runs for modules listed in +# BeefTestConfig.branch_coverage_for(:host) (defined in spec_helper.rb). # require_relative '../../../spec_helper' @@ -13,25 +14,7 @@ require_relative '../../../spec_helper' project_root = File.expand_path('../../../../', __dir__) host_module_paths = Dir[File.join(project_root, 'modules/host/**/module.rb')].sort -# Per-module datastore + config stubs to hit conditional branches in post_execute (e.g. NetworkService/NetworkHost path). -BRANCH_COVERAGE = { - 'modules/host/detect_cups' => { - datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=631&cups=Installed', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, - config_get: { 'beef.extension.network.enable' => true } - }, - 'modules/host/detect_airdroid' => { - datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=8888&airdroid=Installed', 'airdroid' => '', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, - config_get: { 'beef.extension.network.enable' => true } - }, - 'modules/host/get_internal_ip_java' => { - datastore: { 'results' => '192.168.1.1', 'Result' => '', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, - config_get: { 'beef.extension.network.enable' => true } - }, - 'modules/host/get_internal_ip_webrtc' => { - datastore: { 'results' => 'IP is 192.168.1.1', 'Result' => '', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, - config_get: { 'beef.extension.network.enable' => true } - } -}.freeze +BRANCH_COVERAGE_HOST = BeefTestConfig.branch_coverage_for(:host) host_module_paths.each do |path| rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') @@ -45,90 +28,103 @@ host_module_paths.each do |path| mod = Object.const_get(klass_name) RSpec.describe mod do - describe '.options' do - it 'returns an Array when defined' do - next unless described_class.respond_to?(:options) + if described_class.respond_to?(:options) + describe '.options' do + it 'returns an Array' do + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000') - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - - opts = described_class.options - expect(opts).to be_an(Array) - end - end - - describe '#pre_send' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:pre_send) - - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind).and_return(nil) - allow(handler).to receive(:bind).and_return(nil) - allow(handler).to receive(:bind_raw).and_return(nil) - allow(handler).to receive(:remap).and_return(nil) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') - expect { run_pre_send(instance) }.not_to raise_error - end - end - - describe '#post_execute' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:post_execute) - - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(handler).to receive(:remap) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - - file_double = double('File', write: nil, close: nil) - allow(File).to receive(:open).and_return(file_double) - allow(BeEF::Core::Models::Command).to receive(:save_result) - allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') - allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') - - # Minimal datastore so modules that call .sub/.to_s on keys don't get nil - instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') - expect { run_post_execute(instance) }.not_to raise_error - end - - it 'runs branch path when in BRANCH_COVERAGE' do - branch = BRANCH_COVERAGE[branch_key] - next unless described_class.method_defined?(:post_execute) && branch - - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(handler).to receive(:remap) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - file_double = double('File', write: nil, close: nil) - allow(File).to receive(:open).and_return(file_double) - allow(BeEF::Core::Models::Command).to receive(:save_result) - allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') - allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') - allow(BeEF::Core::Models::NetworkService).to receive(:create) - allow(BeEF::Core::Models::NetworkHost).to receive(:create) - allow(BeEF::Core::Models::BrowserDetails).to receive(:get).and_return('Linux') - allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) - - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000') - allow(config).to receive(:get).with(anything) do |key| - branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1' + expect(described_class.options).to be_an(Array) end - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + end + end - instance = build_command_instance(described_class, branch[:datastore]) - expect { run_post_execute(instance) }.not_to raise_error + if described_class.method_defined?(:pre_send) + describe '#pre_send' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + # Stub File.open so Hook_default_browser#pre_send doesn't read/write the real + # bounce_to_ie_configured.pdf in modules/host/hook_default_browser/. + # The source calls File.open(path, 'w') without a block and File.open(path, 'r') { |f| ... } with one. + file_double = double('File').as_null_object + allow(File).to receive(:open) do |*_args, &block| + block ? block.call(file_double) : file_double + end + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_pre_send(instance) }.not_to raise_error + end + end + end + + if described_class.method_defined?(:post_execute) + describe '#post_execute' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + + file_double = double('File').as_null_object + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') + allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') + + # Minimal datastore so modules that call .sub/.to_s on keys don't get nil + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end + + if BRANCH_COVERAGE_HOST[branch_key] + it 'runs branch path with realistic datastore' do + branch = BRANCH_COVERAGE_HOST[branch_key] + + # NetworkService / NetworkHost belong to the network extension and may not be + # loaded during a modules-only spec run. Provide class doubles per-example + # so `allow(...).to receive(:create)` resolves regardless. + stub_const('BeEF::Core::Models::NetworkService', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkService) + stub_const('BeEF::Core::Models::NetworkHost', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkHost) + + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + file_double = double('File').as_null_object + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') + allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') + allow(BeEF::Core::Models::NetworkService).to receive(:create) + allow(BeEF::Core::Models::NetworkHost).to receive(:create) + allow(BeEF::Core::Models::BrowserDetails).to receive(:get).and_return('Linux') + allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) + + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000') + allow(config).to receive(:get).with(anything) do |key| + branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1' + end + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + + instance = build_command_instance(described_class, branch[:datastore]) + expect { run_post_execute(instance) }.not_to raise_error + end + end end end end diff --git a/spec/beef/modules/ipec/ipec_spec.rb b/spec/beef/modules/ipec/ipec_spec.rb index c9be33f20..b86116849 100644 --- a/spec/beef/modules/ipec/ipec_spec.rb +++ b/spec/beef/modules/ipec/ipec_spec.rb @@ -5,30 +5,34 @@ # # Unit tests for every module under modules/ipec/. # +# Some ipec modules reference BeEF::Extension::ETag and +# BeEF::Extension::ServerClientDnsTunnel which are not loaded in unit tests. +# We stub these per-example with stub_const so the stubs don't leak across +# the suite or shadow the real extensions when they happen to be loaded. +# require_relative '../../../spec_helper' -# Stub extensions that some ipec modules reference (not loaded in unit tests) -module BeEF - module Extension - ETag = ::Module.new unless const_defined?(:ETag) - ServerClientDnsTunnel = ::Module.new unless const_defined?(:ServerClientDnsTunnel) - end -end -BeEF::Extension::ETag.const_set(:ETagMessages, ::Class.new do - def self.instance - @instance ||= Struct.new(:messages).new({}) - end -end) unless BeEF::Extension::ETag.const_defined?(:ETagMessages) -BeEF::Extension::ServerClientDnsTunnel.const_set(:Server, ::Class.new do - def self.instance - @instance ||= Struct.new(:messages).new({}) - end -end) unless BeEF::Extension::ServerClientDnsTunnel.const_defined?(:Server) - project_root = File.expand_path('../../../../', __dir__) paths = Dir[File.join(project_root, 'modules/ipec/**/module.rb')].sort +# Lightweight stand-ins for the extension singletons; no real behaviour required. +def stub_etag_messages_class + Class.new do + def self.instance + @instance ||= Struct.new(:messages).new({}) + end + end +end + +def stub_dns_tunnel_server_class + Class.new do + def self.instance + @instance ||= Struct.new(:messages).new({}) + end + end +end + paths.each do |path| rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') require_path = File.join('../../../../', rel) @@ -40,58 +44,69 @@ paths.each do |path| mod = Object.const_get(klass_name) RSpec.describe mod do - describe '.options' do - it 'returns an Array when defined' do - next unless described_class.respond_to?(:options) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:beef_port).and_return('3000') - allow(config).to receive(:beef_proto).and_return('http') - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - expect(described_class.options).to be_an(Array) - end + before(:each) do + # Per-example stubs (auto-removed after each example). + BeEF::Extension.const_set(:ETag, Module.new) unless BeEF::Extension.const_defined?(:ETag) + BeEF::Extension.const_set(:ServerClientDnsTunnel, Module.new) unless BeEF::Extension.const_defined?(:ServerClientDnsTunnel) + stub_const('BeEF::Extension::ETag::ETagMessages', stub_etag_messages_class) unless BeEF::Extension::ETag.const_defined?(:ETagMessages) + stub_const('BeEF::Extension::ServerClientDnsTunnel::Server', stub_dns_tunnel_server_class) unless BeEF::Extension::ServerClientDnsTunnel.const_defined?(:Server) end - describe '#pre_send' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:pre_send) - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind).and_return(nil) - allow(handler).to receive(:bind).and_return(nil) - allow(handler).to receive(:bind_raw).and_return(nil) - allow(handler).to receive(:remap).and_return(nil) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:get).with(anything) do |key| - case key - when 'beef.extension.etag.enable' then true - when 'beef.extension.s2c_dns_tunnel.enable' then true - when 'beef.extension.s2c_dns_tunnel.zone' then 'example.com' - else '127.0.0.1' - end + if described_class.respond_to?(:options) + describe '.options' do + it 'returns an Array' do + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + expect(described_class.options).to be_an(Array) end - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - instance = build_command_instance(described_class, [{'name' => 'data', 'value' => 'test'}]) - expect { run_pre_send(instance) }.not_to raise_error end end - describe '#post_execute' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:post_execute) - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(handler).to receive(:remap) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - file_double = double('File', write: nil, close: nil) - allow(File).to receive(:open).and_return(file_double) - allow(BeEF::Core::Models::Command).to receive(:save_result) - allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') - allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') - instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') - expect { run_post_execute(instance) }.not_to raise_error + if described_class.method_defined?(:pre_send) + describe '#pre_send' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything) do |key| + case key + when 'beef.extension.etag.enable' then true + when 'beef.extension.s2c_dns_tunnel.enable' then true + when 'beef.extension.s2c_dns_tunnel.zone' then 'example.com' + else '127.0.0.1' + end + end + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, [{ 'name' => 'data', 'value' => 'test' }]) + expect { run_pre_send(instance) }.not_to raise_error + end + end + end + + if described_class.method_defined?(:post_execute) + describe '#post_execute' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + file_double = double('File').as_null_object + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') + allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end end end end diff --git a/spec/beef/modules/misc/misc_spec.rb b/spec/beef/modules/misc/misc_spec.rb index bfee72efc..afb4499ea 100644 --- a/spec/beef/modules/misc/misc_spec.rb +++ b/spec/beef/modules/misc/misc_spec.rb @@ -4,7 +4,8 @@ # See the file 'doc/COPYING' for copying permission # # Unit tests for every module under modules/misc/ (including subdirs). -# Branch coverage: extra post_execute runs for modules in BRANCH_COVERAGE. +# Branch coverage: extra post_execute runs for modules in +# BeefTestConfig.branch_coverage_for(:misc) (defined in spec_helper.rb). # require_relative '../../../spec_helper' @@ -12,9 +13,7 @@ require_relative '../../../spec_helper' project_root = File.expand_path('../../../../', __dir__) paths = Dir[File.join(project_root, 'modules/misc/**/module.rb')].sort -BRANCH_COVERAGE = { - 'modules/misc/wordpress_post_auth_rce' => { datastore: { 'result' => 'ok', 'cid' => '0' } } -}.freeze +BRANCH_COVERAGE_MISC = BeefTestConfig.branch_coverage_for(:misc) paths.each do |path| rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') @@ -28,134 +27,136 @@ paths.each do |path| mod = Object.const_get(klass_name) RSpec.describe mod do - describe '.options' do - it 'returns an Array when defined' do - next unless described_class.respond_to?(:options) - - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:beef_port).and_return('3000') - allow(config).to receive(:beef_proto).and_return('http') - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - expect(described_class.options).to be_an(Array) - end - end - - # Specific test for Wordpress_add_user to ensure options method executes fully - if klass_name == 'Wordpress_add_user' + if described_class.respond_to?(:options) describe '.options' do - it 'includes wordpress path and user options' do + it 'returns an Array' do config = instance_double(BeEF::Core::Configuration) allow(config).to receive(:beef_host).and_return('127.0.0.1') allow(config).to receive(:beef_port).and_return('3000') allow(config).to receive(:beef_proto).and_return('http') allow(config).to receive(:get).with(anything).and_return('127.0.0.1') allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + expect(described_class.options).to be_an(Array) + end + end - options = described_class.options - expect(options).to be_an(Array) - expect(options.length).to eq(6) # wp_path + 5 user options + # Targeted structural assertions for selected misc modules. Higher value than + # the generic smoke test: checks option count, names, types and shape. + if klass_name == 'Wordpress_add_user' + describe '.options (Wordpress_add_user specifics)' do + it 'includes wordpress path and user options' do + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - # Check that wp_path option exists (from parent) - wp_path_option = options.find { |opt| opt['name'] == 'wp_path' } - expect(wp_path_option).to be_present - expect(wp_path_option['value']).to eq('/') + options = described_class.options + expect(options).to be_an(Array) + expect(options.length).to eq(5) # wp_path (parent) + username, password, email, role - # Check that username option exists - username_option = options.find { |opt| opt['name'] == 'username' } - expect(username_option).to be_present - expect(username_option['value']).to eq('beef') + wp_path_option = options.find { |opt| opt['name'] == 'wp_path' } + expect(wp_path_option).not_to be_nil + expect(wp_path_option['value']).to eq('/') - # Check that password option exists and has a generated value - password_option = options.find { |opt| opt['name'] == 'password' } - expect(password_option).to be_present - expect(password_option['value']).to be_a(String) - expect(password_option['value'].length).to eq(10) # SecureRandom.hex(5) = 10 chars + username_option = options.find { |opt| opt['name'] == 'username' } + expect(username_option).not_to be_nil + expect(username_option['value']).to eq('beef') + + password_option = options.find { |opt| opt['name'] == 'password' } + expect(password_option).not_to be_nil + expect(password_option['value']).to be_a(String) + expect(password_option['value'].length).to eq(10) # SecureRandom.hex(5) = 10 chars + + role_option = options.find { |opt| opt['name'] == 'role' } + expect(role_option).not_to be_nil + expect(role_option['value']).to eq('administrator') + end + end + end + + if klass_name == 'Test_get_variable' + describe '.options (Test_get_variable specifics)' do + it 'returns payload_name option with correct structure' do + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + + options = described_class.options + expect(options).to be_an(Array) + expect(options.length).to eq(1) + + option = options.first + expect(option['name']).to eq('payload_name') + expect(option['ui_label']).to eq('Payload Name') + expect(option['type']).to eq('text') + expect(option['value']).to eq('message') + expect(option['width']).to eq('400px') + end end end end - # Specific test for Test_get_variable to ensure options method executes fully - if klass_name == 'Test_get_variable' - describe '.options' do - it 'returns payload_name option with correct structure' do + if described_class.method_defined?(:pre_send) + describe '#pre_send' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:beef_port).and_return('3000') - allow(config).to receive(:beef_proto).and_return('http') allow(config).to receive(:get).with(anything).and_return('127.0.0.1') allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - - options = described_class.options - expect(options).to be_an(Array) - expect(options.length).to eq(1) - - option = options.first - expect(option['name']).to eq('payload_name') - expect(option['ui_label']).to eq('Payload Name') - expect(option['type']).to eq('text') - expect(option['value']).to eq('message') - expect(option['width']).to eq('400px') + allow(IO).to receive(:popen).and_return(StringIO.new('')) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_pre_send(instance) }.not_to raise_error end end end - describe '#pre_send' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:pre_send) + if described_class.method_defined?(:post_execute) + describe '#post_execute' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(handler).to receive(:bind_raw) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + file_double = double('File').as_null_object + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') + allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind).and_return(nil) - allow(handler).to receive(:bind).and_return(nil) - allow(handler).to receive(:bind_raw).and_return(nil) - allow(handler).to receive(:remap).and_return(nil) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - allow(IO).to receive(:popen).and_return(StringIO.new('')) - instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') - expect { run_pre_send(instance) }.not_to raise_error - end - end + if BRANCH_COVERAGE_MISC[branch_key] + it 'runs branch path with realistic datastore' do + branch = BRANCH_COVERAGE_MISC[branch_key] - describe '#post_execute' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:post_execute) - - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(handler).to receive(:remap) - allow(handler).to receive(:bind_raw) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - file_double = double('File', write: nil, close: nil) - allow(File).to receive(:open).and_return(file_double) - allow(BeEF::Core::Models::Command).to receive(:save_result) - allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') - allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') - instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') - expect { run_post_execute(instance) }.not_to raise_error - end - - it 'runs branch path when in BRANCH_COVERAGE' do - branch = BRANCH_COVERAGE[branch_key] - next unless described_class.method_defined?(:post_execute) && branch - - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(handler).to receive(:remap) - allow(handler).to receive(:bind_raw) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - file_double = double('File', write: nil, close: nil) - allow(File).to receive(:open).and_return(file_double) - allow(BeEF::Core::Models::Command).to receive(:save_result) - allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') - allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') - instance = build_command_instance(described_class, branch[:datastore]) - expect { run_post_execute(instance) }.not_to raise_error + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(handler).to receive(:bind_raw) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + file_double = double('File').as_null_object + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') + allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') + instance = build_command_instance(described_class, branch[:datastore]) + expect { run_post_execute(instance) }.not_to raise_error + end + end end end end diff --git a/spec/beef/modules/network/network_spec.rb b/spec/beef/modules/network/network_spec.rb index d26ba6f13..cf79799e4 100644 --- a/spec/beef/modules/network/network_spec.rb +++ b/spec/beef/modules/network/network_spec.rb @@ -5,74 +5,27 @@ # # Unit tests for every module under modules/network/ (including ADC). # +# Branch coverage data lives in BeefTestConfig.branch_coverage_for(:network) +# (see spec_helper.rb). Per-example stub_const calls inject a Dns::Server +# stand-in only when the real Dns extension is not loaded, avoiding the +# "superclass mismatch for class Server" hazard that arises if the stub +# leaks into the suite before the real extension's spec loads. +# require_relative '../../../spec_helper' -# Stub Dns extension only when the real one is not loaded (e.g. rake coverage:modules). -# If we stub when the real extension loads later (e.g. dns_spec), we get "superclass mismatch for class Server". -# When the real Dns is already loaded (rake short), we stub Server.instance in the pre_send example instead. -unless BeEF::Extension.const_defined?(:Dns) - BeEF::Extension.const_set(:Dns, Module.new) - dns_server_instance = Object.new - def dns_server_instance.add_rule(*) 1 end - def dns_server_instance.remove_rule!(*) nil end - dns_server = Class.new do - define_singleton_method(:instance) { dns_server_instance } - end - BeEF::Extension::Dns.const_set(:Server, dns_server) -end +BRANCH_COVERAGE_NETWORK = BeefTestConfig.branch_coverage_for(:network) -# Per-module datastore + config to hit conditional branches in post_execute (network extension + regex paths). -BRANCH_COVERAGE = { - 'modules/network/port_scanner' => { - datastore: { 'results' => 'ip=1.2.3.4&port=HTTP: Port 80 is OPEN Apache', 'beefhook' => '1', 'result' => '', 'port' => '', 'cid' => '0' }, - config_get: { 'beef.extension.network.enable' => true } - }, - 'modules/network/ping_sweep' => { - datastore: { 'results' => 'ip=192.168.1.1&ping=10ms', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, - config_get: { 'beef.extension.network.enable' => true } - }, - 'modules/network/ping_sweep_ff' => { - datastore: { 'results' => 'host=192.168.1.1 is alive', 'beefhook' => '1', 'result' => '', 'host' => '', 'cid' => '0' }, - config_get: { 'beef.extension.network.enable' => true } - }, - 'modules/network/get_http_servers' => { - datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&url=http://1.2.3.4/', 'beefhook' => '1', 'result' => '', 'url' => '', 'cid' => '0' }, - config_get: { 'beef.extension.network.enable' => true } - }, - 'modules/network/cross_origin_scanner_cors' => { - datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&status=200', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, - config_get: { 'beef.extension.network.enable' => true } - }, - 'modules/network/detect_burp' => { - datastore: { 'results' => 'has_burp=true&response=PROXY 127.0.0.1:8080', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, - config_get: { 'beef.extension.network.enable' => true } - }, - 'modules/network/get_ntop_network_hosts' => { - datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=3000&data={"hostNumIpAddress":"192.168.1.1"}', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, - config_get: { 'beef.extension.network.enable' => true } - }, - 'modules/network/internal_network_fingerprinting' => { - datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&discovered=Apache&url=http://test/', 'beefhook' => '1', 'result' => '', 'discovered' => '', 'url' => '', 'cid' => '0' }, - config_get: { 'beef.extension.network.enable' => true } - }, - 'modules/network/get_proxy_servers_wpad' => [ - { datastore: { 'results' => 'proxies=PROXY 192.168.1.1:3128', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }, - { datastore: { 'results' => 'proxies=SOCKS 192.168.1.1:1080', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } } - ], - 'modules/network/jslanscanner' => [ - { datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&service=HTTP', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }, - { datastore: { 'results' => 'ip=1.2.3.4&device=Router', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } } - ], - 'modules/network/fetch_port_scanner' => { - datastore: { 'result' => 'ok', 'beefhook' => '1', 'cid' => '0' }, - config_get: { 'beef.extension.network.enable' => true } - }, - 'modules/network/cross_origin_scanner_flash' => [ - { datastore: { 'results' => 'ip=1.2.3.4&status=alive', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }, - { datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&title=Apache', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } } - ] -}.freeze +# Lightweight Dns server stand-in used when the real BeEF::Extension::Dns is +# not loaded (e.g. when running modules-only specs). +DNS_SERVER_STUB = Class.new do + def self.instance + @instance ||= Object.new.tap do |o| + def o.add_rule(*) 1 end + def o.remove_rule!(*) nil end + end + end +end project_root = File.expand_path('../../../../', __dir__) paths = Dir[File.join(project_root, 'modules/network/**/module.rb')].sort @@ -89,85 +42,70 @@ paths.each do |path| mod = Object.const_get(klass_name) RSpec.describe mod do - # Irc_nat_pinning calls sleep 30 in post_execute; stub so the suite doesn't block. before(:each) do + # Irc_nat_pinning calls sleep 30 in post_execute; suppress so the suite doesn't block. allow(Kernel).to receive(:sleep) allow_any_instance_of(described_class).to receive(:sleep).and_return(nil) - end - describe '.options' do - it 'returns an Array when defined' do - next unless described_class.respond_to?(:options) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:beef_port).and_return('3000') - allow(config).to receive(:beef_proto).and_return('http') - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - expect(described_class.options).to be_an(Array) + # Provide a per-example Dns::Server stand-in only if the real extension hasn't been loaded. + # Using stub_const keeps the stub scoped to this example only. + unless defined?(BeEF::Extension::Dns) && BeEF::Extension::Dns.const_defined?(:Server) + BeEF::Extension.const_set(:Dns, Module.new) unless BeEF::Extension.const_defined?(:Dns) + stub_const('BeEF::Extension::Dns::Server', DNS_SERVER_STUB) end end - describe '#pre_send' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:pre_send) - # When real Dns extension is loaded (rake short), stub Server.instance so Dns_rebinding works - if BeEF::Extension.const_defined?(:Dns) && BeEF::Extension::Dns.const_defined?(:Server) - dns_instance = double('DnsServer', add_rule: 1, remove_rule!: nil) - allow(BeEF::Extension::Dns::Server).to receive(:instance).and_return(dns_instance) + if described_class.respond_to?(:options) + describe '.options' do + it 'returns an Array' do + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + expect(described_class.options).to be_an(Array) end - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind).and_return(nil) - allow(handler).to receive(:bind).and_return(nil) - allow(handler).to receive(:bind_raw).and_return(nil) - allow(handler).to receive(:bind_socket).and_return(nil) - allow(handler).to receive(:unbind_socket).and_return(nil) - allow(handler).to receive(:bind_cached).and_return(nil) - allow(handler).to receive(:remap).and_return(nil) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:get).with(anything) do |key| - case key - when 'beef.extension.dns_rebinding' then { 'address_http_external' => '127.0.0.1', 'port_proxy' => '1234' } - when 'beef.module.dns_rebinding.domain' then 'example.com' - else '127.0.0.1' + end + end + + if described_class.method_defined?(:pre_send) + describe '#pre_send' do + it 'runs without error' do + # If the real Dns extension is loaded (e.g. rake short), stub Server.instance so Dns_rebinding works. + if defined?(BeEF::Extension::Dns) && BeEF::Extension::Dns.const_defined?(:Server) + dns_instance = double('DnsServer', add_rule: 1, remove_rule!: nil) + allow(BeEF::Extension::Dns::Server).to receive(:instance).and_return(dns_instance) end + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:bind_socket).and_return(nil) + allow(handler).to receive(:unbind_socket).and_return(nil) + allow(handler).to receive(:bind_cached).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything) do |key| + case key + when 'beef.extension.dns_rebinding' then { 'address_http_external' => '127.0.0.1', 'port_proxy' => '1234' } + when 'beef.module.dns_rebinding.domain' then 'example.com' + else '127.0.0.1' + end + end + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + # Dns_rebinding expects @datastore as Array of option hashes (target, domain, url_callback). + datastore = described_class.name.include?('Dns_rebinding') ? [{ 'value' => '192.168.0.1' }, { 'value' => 'example.com' }, {}] : { 'result' => '', 'results' => '', 'cid' => '0' } + instance = build_command_instance(described_class, datastore) + expect { run_pre_send(instance) }.not_to raise_error end - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - # Dns_rebinding expects @datastore as Array of option hashes (target, domain, url_callback) - datastore = described_class.name.include?('Dns_rebinding') ? [{ 'value' => '192.168.0.1' }, { 'value' => 'example.com' }, {}] : { 'result' => '', 'results' => '', 'cid' => '0' } - instance = build_command_instance(described_class, datastore) - expect { run_pre_send(instance) }.not_to raise_error end end - describe '#post_execute' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:post_execute) - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(handler).to receive(:bind_socket) - allow(handler).to receive(:unbind_socket) - allow(handler).to receive(:remap) - allow(handler).to receive(:bind_cached) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - file_double = double('File', write: nil, close: nil) - allow(File).to receive(:open).and_return(file_double) - allow(BeEF::Core::Models::Command).to receive(:save_result) - allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') - allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') - allow(Kernel).to receive(:sleep) - instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') - expect { run_post_execute(instance) }.not_to raise_error - end - - it 'runs branch path when in BRANCH_COVERAGE' do - raw = BRANCH_COVERAGE[branch_key] - next unless described_class.method_defined?(:post_execute) && raw - - branches = raw.is_a?(Array) ? raw : [raw] - branches.each do |branch| + if described_class.method_defined?(:post_execute) + describe '#post_execute' do + it 'runs without error' do handler = instance_double('AssetHandler') allow(handler).to receive(:unbind) allow(handler).to receive(:bind) @@ -176,28 +114,58 @@ paths.each do |path| allow(handler).to receive(:remap) allow(handler).to receive(:bind_cached) allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - file_double = double('File', write: nil, close: nil) + file_double = double('File').as_null_object allow(File).to receive(:open).and_return(file_double) allow(BeEF::Core::Models::Command).to receive(:save_result) allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') - allow(Kernel).to receive(:sleep) - allow(BeEF::Core::Models::NetworkService).to receive(:create) - allow(BeEF::Core::Models::NetworkHost).to receive(:create) - allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) - - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:beef_port).and_return('3000') - allow(config).to receive(:beef_proto).and_return('http') - allow(config).to receive(:get).with(anything) do |key| - branch[:config_get] && branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1' - end - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - - instance = build_command_instance(described_class, branch[:datastore]) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') expect { run_post_execute(instance) }.not_to raise_error end + + if BRANCH_COVERAGE_NETWORK[branch_key] + it 'runs branch path with realistic datastore' do + raw = BRANCH_COVERAGE_NETWORK[branch_key] + branches = raw.is_a?(Array) ? raw : [raw] + + # NetworkService / NetworkHost belong to the network extension and may not be + # loaded during a modules-only spec run. Provide class doubles per-example + # so `allow(...).to receive(:create)` resolves regardless. + stub_const('BeEF::Core::Models::NetworkService', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkService) + stub_const('BeEF::Core::Models::NetworkHost', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkHost) + + branches.each do |branch| + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:bind_socket) + allow(handler).to receive(:unbind_socket) + allow(handler).to receive(:remap) + allow(handler).to receive(:bind_cached) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + file_double = double('File').as_null_object + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0') + allow_any_instance_of(described_class).to receive(:timestamp).and_return('0') + allow(BeEF::Core::Models::NetworkService).to receive(:create) + allow(BeEF::Core::Models::NetworkHost).to receive(:create) + allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) + + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything) do |key| + branch[:config_get] && branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1' + end + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + + instance = build_command_instance(described_class, branch[:datastore]) + expect { run_post_execute(instance) }.not_to raise_error + end + end + end end end end diff --git a/spec/beef/modules/persistence/persistence_spec.rb b/spec/beef/modules/persistence/persistence_spec.rb index 138340499..b05f990f2 100644 --- a/spec/beef/modules/persistence/persistence_spec.rb +++ b/spec/beef/modules/persistence/persistence_spec.rb @@ -22,50 +22,53 @@ paths.each do |path| mod = Object.const_get(klass_name) RSpec.describe mod do - describe '.options' do - it 'returns an Array when defined' do - next unless described_class.respond_to?(:options) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:beef_port).and_return('3000') - allow(config).to receive(:beef_proto).and_return('http') - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - expect(described_class.options).to be_an(Array) + if described_class.respond_to?(:options) + describe '.options' do + it 'returns an Array' do + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + expect(described_class.options).to be_an(Array) + end end end - describe '#pre_send' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:pre_send) - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind).and_return(nil) - allow(handler).to receive(:bind).and_return(nil) - allow(handler).to receive(:bind_raw).and_return(nil) - allow(handler).to receive(:remap).and_return(nil) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:get).with(anything).and_return('/hook.js') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') - expect { run_pre_send(instance) }.not_to raise_error + if described_class.method_defined?(:pre_send) + describe '#pre_send' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('/hook.js') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_pre_send(instance) }.not_to raise_error + end end end - describe '#post_execute' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:post_execute) - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(handler).to receive(:remap) - allow(handler).to receive(:bind_raw) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - file_double = double('File', write: nil, close: nil) - allow(File).to receive(:open).and_return(file_double) - allow(BeEF::Core::Models::Command).to receive(:save_result) - instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') - expect { run_post_execute(instance) }.not_to raise_error + if described_class.method_defined?(:post_execute) + describe '#post_execute' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(handler).to receive(:bind_raw) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + file_double = double('File').as_null_object + allow(File).to receive(:open).and_return(file_double) + allow(BeEF::Core::Models::Command).to receive(:save_result) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end end end end diff --git a/spec/beef/modules/phonegap/phonegap_spec.rb b/spec/beef/modules/phonegap/phonegap_spec.rb index 642d8519d..9d9956714 100644 --- a/spec/beef/modules/phonegap/phonegap_spec.rb +++ b/spec/beef/modules/phonegap/phonegap_spec.rb @@ -23,46 +23,49 @@ paths.each do |path| mod = Object.const_get(klass_name) RSpec.describe mod do - describe '.options' do - it 'returns an Array when defined' do - next unless described_class.respond_to?(:options) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:beef_proto).and_return('http') - allow(config).to receive(:beef_port).and_return('3000') - allow(config).to receive(:hook_file_path).and_return('/hook.js') - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - expect(described_class.options).to be_an(Array) + if described_class.respond_to?(:options) + describe '.options' do + it 'returns an Array' do + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:hook_file_path).and_return('/hook.js') + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + expect(described_class.options).to be_an(Array) + end end end - describe '#pre_send' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:pre_send) - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind).and_return(nil) - allow(handler).to receive(:bind).and_return(nil) - allow(handler).to receive(:bind_raw).and_return(nil) - allow(handler).to receive(:remap).and_return(nil) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - instance = build_command_instance(described_class, 'result' => '', 'Result' => '', 'cid' => '0') - expect { run_pre_send(instance) }.not_to raise_error + if described_class.method_defined?(:pre_send) + describe '#pre_send' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + instance = build_command_instance(described_class, 'result' => '', 'Result' => '', 'cid' => '0') + expect { run_pre_send(instance) }.not_to raise_error + end end end - describe '#post_execute' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:post_execute) - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - instance = build_command_instance(described_class, 'result' => '', 'Result' => '', 'cid' => '0') - expect { run_post_execute(instance) }.not_to raise_error + if described_class.method_defined?(:post_execute) + describe '#post_execute' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + instance = build_command_instance(described_class, 'result' => '', 'Result' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end end end end diff --git a/spec/beef/modules/social_engineering/social_engineering_spec.rb b/spec/beef/modules/social_engineering/social_engineering_spec.rb index 0ac827b94..628737860 100644 --- a/spec/beef/modules/social_engineering/social_engineering_spec.rb +++ b/spec/beef/modules/social_engineering/social_engineering_spec.rb @@ -4,7 +4,8 @@ # See the file 'doc/COPYING' for copying permission # # Unit tests for every module under modules/social_engineering/. -# Branch coverage: extra post_execute for KILLFRAME / conditional paths. +# Branch coverage data and pre_send skip list live in BeefTestConfig +# (see spec_helper.rb). # require_relative '../../../spec_helper' @@ -12,16 +13,8 @@ require_relative '../../../spec_helper' project_root = File.expand_path('../../../../', __dir__) paths = Dir[File.join(project_root, 'modules/social_engineering/**/module.rb')].sort -BRANCH_COVERAGE = { - 'modules/social_engineering/fake_lastpass' => { datastore: { 'meta' => 'KILLFRAME', 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0' } }, - 'modules/social_engineering/fake_evernote_clipper' => { datastore: { 'meta' => 'KILLFRAME', 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0' } } -}.freeze - -# Modules whose pre_send needs Zip, real filesystem, or logger; skip generic pre_send example -PRE_SEND_SKIP = %w[ - Firefox_extension_bindshell Firefox_extension_dropper Firefox_extension_reverse_shell - Text_to_voice Ui_abuse_ie -].freeze +BRANCH_COVERAGE_SOCENG = BeefTestConfig.branch_coverage_for(:social_engineering) +PRE_SEND_SKIP_SOCENG = BeefTestConfig.pre_send_skip_for(:social_engineering) paths.each do |path| rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '') @@ -35,73 +28,75 @@ paths.each do |path| mod = Object.const_get(klass_name) RSpec.describe mod do - describe '.options' do - it 'returns an Array when defined' do - next unless described_class.respond_to?(:options) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:beef_host).and_return('127.0.0.1') - allow(config).to receive(:beef_proto).and_return('http') - allow(config).to receive(:beef_port).and_return('3000') - allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000') - allow(config).to receive(:get).with(anything) do |key| - key == 'beef.module.simple_hijacker.templates' ? [] : '127.0.0.1' + if described_class.respond_to?(:options) + describe '.options' do + it 'returns an Array' do + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:beef_host).and_return('127.0.0.1') + allow(config).to receive(:beef_proto).and_return('http') + allow(config).to receive(:beef_port).and_return('3000') + allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000') + allow(config).to receive(:get).with(anything) do |key| + key == 'beef.module.simple_hijacker.templates' ? [] : '127.0.0.1' + end + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + expect(described_class.options).to be_an(Array) end - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - expect(described_class.options).to be_an(Array) end end - describe '#pre_send' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:pre_send) - next if PRE_SEND_SKIP.include?(described_class.name) - - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind).and_return(nil) - allow(handler).to receive(:bind).and_return(nil) - allow(handler).to receive(:bind_raw).with(anything, anything, anything, anything, anything).and_return(nil) - allow(handler).to receive(:remap).and_return(nil) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - config = instance_double(BeEF::Core::Configuration) - allow(config).to receive(:get).with(anything).and_return('127.0.0.1') - allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) - allow(IO).to receive(:popen).and_return(StringIO.new('')) - # Many pre_send implementations expect @datastore to be an Array of option hashes (name/value) - pre_send_datastore = [ - { 'name' => 'payload', 'value' => 'cmd' }, - { 'name' => 'payload_handler', 'value' => 'http://127.0.0.1:8080' }, - { 'name' => 'extension_name', 'value' => 'TestExt' }, - { 'name' => 'xpi_name', 'value' => 'test.xpi' }, - { 'name' => 'lport', 'value' => '4444' } - ] - instance = build_command_instance(described_class, pre_send_datastore) - expect { run_pre_send(instance) }.not_to raise_error + if described_class.method_defined?(:pre_send) && !PRE_SEND_SKIP_SOCENG.include?(klass_name) + describe '#pre_send' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind).and_return(nil) + allow(handler).to receive(:bind).and_return(nil) + allow(handler).to receive(:bind_raw).with(anything, anything, anything, anything, anything).and_return(nil) + allow(handler).to receive(:remap).and_return(nil) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + config = instance_double(BeEF::Core::Configuration) + allow(config).to receive(:get).with(anything).and_return('127.0.0.1') + allow(BeEF::Core::Configuration).to receive(:instance).and_return(config) + allow(IO).to receive(:popen).and_return(StringIO.new('')) + # Many pre_send implementations expect @datastore to be an Array of option hashes (name/value) + pre_send_datastore = [ + { 'name' => 'payload', 'value' => 'cmd' }, + { 'name' => 'payload_handler', 'value' => 'http://127.0.0.1:8080' }, + { 'name' => 'extension_name', 'value' => 'TestExt' }, + { 'name' => 'xpi_name', 'value' => 'test.xpi' }, + { 'name' => 'lport', 'value' => '4444' } + ] + instance = build_command_instance(described_class, pre_send_datastore) + expect { run_pre_send(instance) }.not_to raise_error + end end end - describe '#post_execute' do - it 'runs without error when defined' do - next unless described_class.method_defined?(:post_execute) - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(handler).to receive(:remap) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0') - expect { run_post_execute(instance) }.not_to raise_error - end + if described_class.method_defined?(:post_execute) + describe '#post_execute' do + it 'runs without error' do + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0') + expect { run_post_execute(instance) }.not_to raise_error + end - it 'runs branch path when in BRANCH_COVERAGE' do - branch = BRANCH_COVERAGE[branch_key] - next unless described_class.method_defined?(:post_execute) && branch + if BRANCH_COVERAGE_SOCENG[branch_key] + it 'runs branch path with realistic datastore' do + branch = BRANCH_COVERAGE_SOCENG[branch_key] - handler = instance_double('AssetHandler') - allow(handler).to receive(:unbind) - allow(handler).to receive(:bind) - allow(handler).to receive(:remap) - allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) - instance = build_command_instance(described_class, branch[:datastore]) - expect { run_post_execute(instance) }.not_to raise_error + handler = instance_double('AssetHandler') + allow(handler).to receive(:unbind) + allow(handler).to receive(:bind) + allow(handler).to receive(:remap) + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler) + instance = build_command_instance(described_class, branch[:datastore]) + expect { run_post_execute(instance) }.not_to raise_error + end + end end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4afa5bb52..35d307118 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,8 +7,31 @@ # Coverage must start before loading application code. require 'simplecov' and SimpleCov.start if ENV['COVERAGE'] -# Load test configuration for branch coverage data +# Branch-coverage datastore/config fixtures for the dynamic module spec loaders. +# Each spec file under spec/beef/modules/* uses BeefTestConfig.branch_coverage_for(:component) +# to drive an extra post_execute example with realistic data, hitting conditional branches +# that the generic smoke test misses. +# +# Centralising these here: +# - removes duplicate top-level BRANCH_COVERAGE constant definitions across spec files +# (which previously triggered "already initialized constant" warnings and silent overrides), +# - makes the test data discoverable from one place, and +# - matches what spec/README.md claims. module BeefTestConfig + PRE_SEND_SKIP = { + # Modules whose pre_send needs Zip, real filesystem access, or speech tooling we + # don't want to invoke in unit tests; the dynamic loader skips the generic pre_send + # example for these described_class names. + social_engineering: %w[ + Firefox_extension_bindshell Firefox_extension_dropper Firefox_extension_reverse_shell + Text_to_voice Ui_abuse_ie + ].freeze + }.freeze + + def self.pre_send_skip_for(component) + PRE_SEND_SKIP[component] || [] + end + def self.branch_coverage_for(component) case component when :browser @@ -19,6 +42,40 @@ module BeefTestConfig 'modules/browser/detect_foxit' => { datastore: { 'results' => 'foxit=Yes', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }, 'modules/browser/detect_activex' => { datastore: { 'results' => 'activex=Yes', 'activex' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } } } + when :exploits + { + 'modules/exploits/vtiger_crm_upload_exploit' => { datastore: { 'result' => 'ok', 'cid' => '0' } }, + 'modules/exploits/router/asus_rt_n12e_get_info' => { + datastore: { + 'result' => '', 'results' => 'ip=192.168.1.1&clients=192.168.1.2,00:11:22:33:44:55&wanip=1.2.3.4&netmask=255.255.255.0&gateway=192.168.1.1&dns=8.8.8.8 8.8.4.4', + 'beefhook' => '1', 'cid' => '0' + }, + config_get: { 'beef.extension.network.enable' => true } + } + } + when :host + { + 'modules/host/detect_cups' => { + datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=631&cups=Installed', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/host/detect_airdroid' => { + datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=8888&airdroid=Installed', 'airdroid' => '', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/host/get_internal_ip_java' => { + datastore: { 'results' => '192.168.1.1', 'Result' => '', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/host/get_internal_ip_webrtc' => { + datastore: { 'results' => 'IP is 192.168.1.1', 'Result' => '', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + } + } + when :misc + { + 'modules/misc/wordpress_post_auth_rce' => { datastore: { 'result' => 'ok', 'cid' => '0' } } + } when :network { 'modules/network/port_scanner' => { @@ -28,7 +85,52 @@ module BeefTestConfig 'modules/network/ping_sweep' => { datastore: { 'results' => 'ip=192.168.1.1&ping=10ms', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } - } + }, + 'modules/network/ping_sweep_ff' => { + datastore: { 'results' => 'host=192.168.1.1 is alive', 'beefhook' => '1', 'result' => '', 'host' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/get_http_servers' => { + datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&url=http://1.2.3.4/', 'beefhook' => '1', 'result' => '', 'url' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/cross_origin_scanner_cors' => { + datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&status=200', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/detect_burp' => { + datastore: { 'results' => 'has_burp=true&response=PROXY 127.0.0.1:8080', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/get_ntop_network_hosts' => { + datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=3000&data={"hostNumIpAddress":"192.168.1.1"}', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/internal_network_fingerprinting' => { + datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&discovered=Apache&url=http://test/', 'beefhook' => '1', 'result' => '', 'discovered' => '', 'url' => '', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/get_proxy_servers_wpad' => [ + { datastore: { 'results' => 'proxies=PROXY 192.168.1.1:3128', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }, + { datastore: { 'results' => 'proxies=SOCKS 192.168.1.1:1080', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } } + ], + 'modules/network/jslanscanner' => [ + { datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&service=HTTP', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }, + { datastore: { 'results' => 'ip=1.2.3.4&device=Router', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } } + ], + 'modules/network/fetch_port_scanner' => { + datastore: { 'result' => 'ok', 'beefhook' => '1', 'cid' => '0' }, + config_get: { 'beef.extension.network.enable' => true } + }, + 'modules/network/cross_origin_scanner_flash' => [ + { datastore: { 'results' => 'ip=1.2.3.4&status=alive', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }, + { datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&title=Apache', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } } + ] + } + when :social_engineering + { + 'modules/social_engineering/fake_lastpass' => { datastore: { 'meta' => 'KILLFRAME', 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0' } }, + 'modules/social_engineering/fake_evernote_clipper' => { datastore: { 'meta' => 'KILLFRAME', 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0' } } } else {} diff --git a/spec/support/module_spec_helper.rb b/spec/support/module_spec_helper.rb index 8a86d17fd..552ee7915 100644 --- a/spec/support/module_spec_helper.rb +++ b/spec/support/module_spec_helper.rb @@ -49,7 +49,8 @@ end RSpec.configure do |config| config.include ModuleSpecHelper - # Stub Kernel.sleep for all module specs so modules that call sleep (e.g. Irc_nat_pinning's post_execute sleep 30) don't slow the suite. + # Stub Kernel.sleep for all module specs so modules that call sleep + # (e.g. Irc_nat_pinning's post_execute sleep 30) don't slow the suite. config.before(:each) do |example| if example.metadata[:file_path]&.include?('spec/beef/modules') allow(Kernel).to receive(:sleep)